MDL-35073 badges: Open badges integration
authorYuliya Bozhko <yuliya.bozhko@totaralms.com>
Tue, 2 Apr 2013 00:57:39 +0000 (13:57 +1300)
committerDamyon Wiese <damyon@moodle.com>
Tue, 2 Apr 2013 07:49:02 +0000 (15:49 +0800)
The badges feature allows to integrate Mozilla "Open Badges" to issue, assign,
manage and display digital badges in Moodle. This feature supports:
-- badge creation and issuing based on criteria
-- badge baking and verification service
-- direct pushing of internal badges to external backpack
-- interanl and external badge display in Moodle
-- Moodle block to display latest badges

75 files changed:
admin/registration/forms.php
admin/registration/lib.php
admin/registration/register.php
admin/settings/badges.php [new file with mode: 0644]
admin/settings/subsystems.php
admin/settings/top.php
badges/action.php [new file with mode: 0644]
badges/ajax.php [new file with mode: 0644]
badges/assertion.php [new file with mode: 0644]
badges/award.php [new file with mode: 0644]
badges/backpack.js [new file with mode: 0644]
badges/backpack_form.php [new file with mode: 0644]
badges/badge.php [new file with mode: 0644]
badges/criteria.php [new file with mode: 0644]
badges/criteria/award_criteria.php [new file with mode: 0644]
badges/criteria/award_criteria_activity.php [new file with mode: 0644]
badges/criteria/award_criteria_course.php [new file with mode: 0644]
badges/criteria/award_criteria_courseset.php [new file with mode: 0644]
badges/criteria/award_criteria_manual.php [new file with mode: 0644]
badges/criteria/award_criteria_overall.php [new file with mode: 0644]
badges/criteria/award_criteria_profile.php [new file with mode: 0644]
badges/criteria_action.php [new file with mode: 0644]
badges/criteria_form.php [new file with mode: 0644]
badges/criteria_settings.php [new file with mode: 0644]
badges/cron.php [new file with mode: 0644]
badges/edit.php [new file with mode: 0644]
badges/edit_form.php [new file with mode: 0644]
badges/external.php [new file with mode: 0644]
badges/index.php [new file with mode: 0644]
badges/lib/awardlib.php [new file with mode: 0644]
badges/lib/backpacklib.php [new file with mode: 0644]
badges/lib/bakerlib.php [new file with mode: 0644]
badges/mybackpack.php [new file with mode: 0644]
badges/mybadges.php [new file with mode: 0644]
badges/newbadge.php [new file with mode: 0644]
badges/overview.php [new file with mode: 0644]
badges/recipients.php [new file with mode: 0644]
badges/renderer.php [new file with mode: 0644]
badges/tests/badgeslib_test.php [new file with mode: 0644]
badges/tests/behat/add_badge.feature [new file with mode: 0644]
badges/tests/behat/award_badge.feature [new file with mode: 0644]
badges/tests/behat/badge.png [new file with mode: 0644]
badges/view.php [new file with mode: 0644]
blocks/badges/block_badges.php [new file with mode: 0644]
blocks/badges/db/access.php [new file with mode: 0644]
blocks/badges/edit_form.php [new file with mode: 0644]
blocks/badges/lang/en/block_badges.php [new file with mode: 0644]
blocks/badges/version.php [new file with mode: 0644]
lang/en/badges.php [new file with mode: 0644]
lang/en/hub.php
lang/en/moodle.php
lang/en/role.php
lib/badgeslib.php [new file with mode: 0644]
lib/completionlib.php
lib/cronlib.php
lib/db/access.php
lib/db/events.php
lib/db/install.xml
lib/db/upgrade.php
lib/filelib.php
lib/moodlelib.php
lib/navigationlib.php
phpunit.xml.dist
pix/i/badge.png [new file with mode: 0644]
pix/i/badge.svg [new file with mode: 0644]
pix/i/expired.png [new file with mode: 0644]
pix/i/expired.svg [new file with mode: 0644]
pix/t/award.png [new file with mode: 0644]
pix/t/award.svg [new file with mode: 0644]
pix/t/backpack.png [new file with mode: 0644]
pix/t/backpack.svg [new file with mode: 0644]
pix/t/download.svg [new file with mode: 0644]
theme/base/style/core.css
user/profile.php
version.php

index 47a450f..863af66 100644 (file)
@@ -243,6 +243,8 @@ class site_registration_form extends moodleform {
         $postsnumber = get_config('hub', 'site_postsnumber_' . $cleanhuburl);
         $questionsnumber = get_config('hub', 'site_questionsnumber_' . $cleanhuburl);
         $resourcesnumber = get_config('hub', 'site_resourcesnumber_' . $cleanhuburl);
+        $badges = get_config('hub', 'site_badges_' . $cleanhuburl);
+        $issuedbadges = get_config('hub', 'site_issuedbadges_' . $cleanhuburl);
         $mediancoursesize = get_config('hub', 'site_mediancoursesize_' . $cleanhuburl);
 
         //hidden parameters
@@ -372,6 +374,9 @@ class site_registration_form extends moodleform {
         require_once($CFG->dirroot . "/course/lib.php");
         $participantnumberaverage = number_format(average_number_of_participants(), 2);
         $modulenumberaverage = number_format(average_number_of_courses_modules(), 2);
+        require_once($CFG->libdir . '/badgeslib.php');
+        $badges = $DB->count_records_select('badge', 'status <> ' . BADGE_STATUS_ARCHIVED);
+        $issuedbadges = $DB->count_records('badge_issued');
 
         if (HUB_MOODLEORGHUBURL != $huburl) {
             $mform->addElement('checkbox', 'courses', get_string('sendfollowinginfo', 'hub'),
@@ -399,6 +404,14 @@ class site_registration_form extends moodleform {
                     " " . get_string('resourcesnumber', 'hub', $resourcecount));
             $mform->setDefault('resources', true);
 
+            $mform->addElement('checkbox', 'badges', '',
+                    " " . get_string('badgesnumber', 'hub', $badges));
+            $mform->setDefault('badges', true);
+
+            $mform->addElement('checkbox', 'issuedbadges', '',
+                    " " . get_string('issuedbadgesnumber', 'hub', $issuedbadges));
+            $mform->setDefault('issuedbadges', true);
+
             $mform->addElement('checkbox', 'participantnumberaverage', '',
                     " " . get_string('participantnumberaverage', 'hub', $participantnumberaverage));
             $mform->setDefault('participantnumberaverage', true);
@@ -438,6 +451,16 @@ class site_registration_form extends moodleform {
             $mform->addElement('hidden', 'resources', true);
             $mform->setType('resources', PARAM_FLOAT);
 
+            $mform->addElement('static', 'badgeslabel', '',
+                    " " . get_string('badgesnumber', 'hub', $badges));
+            $mform->addElement('hidden', 'badges', true);
+            $mform->setType('badges', PARAM_INT);
+
+            $mform->addElement('static', 'issuedbadgeslabel', '',
+                    " " . get_string('issuedbadgesnumber', 'hub', $issuedbadges));
+            $mform->addElement('hidden', 'issuedbadges', true);
+            $mform->setType('issuedbadges', PARAM_INT);
+
             $mform->addElement('static', 'participantnumberaveragelabel', '',
                     " " . get_string('participantnumberaverage', 'hub', $participantnumberaverage));
             $mform->addElement('hidden', 'participantnumberaverage', true);
index 4179d79..a8086e1 100644 (file)
@@ -247,6 +247,20 @@ class registration_manager {
             $resourcecount = $DB->count_records('resource');
         }
         $siteinfo['resources'] = $resourcecount;
+        // Badge statistics.
+        require_once($CFG->libdir . '/badgeslib.php');
+        if (get_config('hub', 'site_badges_' . $cleanhuburl) == -1) {
+            $badges = -1;
+        } else {
+            $badges = $DB->count_records_select('badge', 'status <> ' . BADGE_STATUS_ARCHIVED);
+        }
+        $siteinfo['badges'] = $badges;
+        if (get_config('hub', 'site_issuedbadges_' . $cleanhuburl) == -1) {
+            $issuedbadges = -1;
+        } else {
+            $issuedbadges = $DB->count_records('badge_issued');
+        }
+        $siteinfo['issuedbadges'] = $issuedbadges;
         //TODO
         require_once($CFG->dirroot . "/course/lib.php");
         if (get_config('hub', 'site_participantnumberaverage_' . $cleanhuburl) == -1) {
index 7a6bc92..760dd1f 100644 (file)
@@ -84,6 +84,8 @@ if (!empty($fromform) and confirm_sesskey()) {
     set_config('site_postsnumber_' . $cleanhuburl, $fromform->posts, 'hub');
     set_config('site_questionsnumber_' . $cleanhuburl, $fromform->questions, 'hub');
     set_config('site_resourcesnumber_' . $cleanhuburl, $fromform->resources, 'hub');
+    set_config('site_badges_' . $cleanhuburl, $fromform->badges, 'hub');
+    set_config('site_issuedbadges_' . $cleanhuburl, $fromform->issuedbadges, 'hub');
     set_config('site_modulenumberaverage_' . $cleanhuburl, $fromform->modulenumberaverage, 'hub');
     set_config('site_participantnumberaverage_' . $cleanhuburl, $fromform->participantnumberaverage, 'hub');
 }
diff --git a/admin/settings/badges.php b/admin/settings/badges.php
new file mode 100644 (file)
index 0000000..cda847d
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+* This file defines settingpages and externalpages under the "badges" section
+*
+* @package    core
+* @subpackage badges
+* @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+* @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+* @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+*/
+
+global $SITE;
+require_once($CFG->libdir . '/badgeslib.php');
+
+if (!empty($CFG->enablebadges) && ($hassiteconfig || has_any_capability(array(
+            'moodle/badges:viewawarded',
+            'moodle/badges:createbadge',
+            'moodle/badges:manageglobalsettings',
+            'moodle/badges:awardbadge',
+            'moodle/badges:configuremessages',
+            'moodle/badges:configuredetails',
+            'moodle/badges:deletebadge'), $systemcontext))) {
+
+    $globalsettings = new admin_settingpage('badgesettings', new lang_string('badgesettings', 'badges'),
+            array('moodle/badges:manageglobalsettings'));
+
+    $globalsettings->add(new admin_setting_configtext('badges_defaultissuername',
+            new lang_string('defaultissuername', 'badges'),
+            new lang_string('defaultissuername_desc', 'badges'),
+            $SITE->fullname ? $SITE->fullname : $SITE->shortname, PARAM_TEXT));
+
+    $globalsettings->add(new admin_setting_configtext('badges_defaultissuercontact',
+            new lang_string('defaultissuercontact', 'badges'),
+            new lang_string('defaultissuercontact_desc', 'badges'),
+            get_config('moodle','supportemail'), PARAM_EMAIL));
+
+    $globalsettings->add(new admin_setting_configtext('badges_badgesalt',
+            new lang_string('badgesalt', 'badges'),
+            new lang_string('badgesalt_desc', 'badges'),
+            'badges' . $SITE->timecreated, PARAM_ALPHANUM));
+
+    $globalsettings->add(new admin_setting_configcheckbox('badges_allowexternalbackpack',
+            new lang_string('allowexternalbackpack', 'badges'),
+            new lang_string('allowexternalbackpack_desc', 'badges'), 1));
+
+    $globalsettings->add(new admin_setting_configcheckbox('badges_allowcoursebadges',
+            new lang_string('allowcoursebadges', 'badges'),
+            new lang_string('allowcoursebadges_desc', 'badges'), 1));
+
+    $ADMIN->add('badges', $globalsettings);
+
+    $ADMIN->add('badges',
+        new admin_externalpage('managebadges',
+            new lang_string('managebadges', 'badges'),
+            new moodle_url($CFG->wwwroot . '/badges/index.php', array('type' => BADGE_TYPE_SITE)),
+            array('moodle/badges:viewawarded')
+        )
+    );
+
+    $ADMIN->add('badges',
+        new admin_externalpage('newbadge',
+            new lang_string('newbadge', 'badges'),
+            new moodle_url($CFG->wwwroot . '/badges/newbadge.php', array('type' => BADGE_TYPE_SITE)),
+            array('moodle/badges:createbadge')
+        )
+    );
+}
index 0d3db2e..961ab76 100644 (file)
@@ -41,4 +41,6 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
     $checkbox->set_affects_modinfo(true);
 
     $optionalsubsystems->add(new admin_setting_configcheckbox('enableplagiarism', new lang_string('enableplagiarism','plagiarism'), new lang_string('configenableplagiarism','plagiarism'), 0));
+
+    $optionalsubsystems->add(new admin_setting_configcheckbox('enablebadges', new lang_string('enablebadges', 'badges'), new lang_string('configenablebadges', 'badges'), 1));
 }
index cd52953..e855519 100644 (file)
@@ -30,6 +30,7 @@ if ($hassiteconfig) {
 $ADMIN->add('root', new admin_category('users', new lang_string('users','admin')));
 $ADMIN->add('root', new admin_category('courses', new lang_string('courses','admin')));
 $ADMIN->add('root', new admin_category('grades', new lang_string('grades')));
+$ADMIN->add('root', new admin_category('badges', new lang_string('badges'), (isset($CFG->enablebadges) && $CFG->enablebadges == 0)));
 $ADMIN->add('root', new admin_category('location', new lang_string('location','admin')));
 $ADMIN->add('root', new admin_category('language', new lang_string('language')));
 $ADMIN->add('root', new admin_category('modules', new lang_string('plugins', 'admin')));
diff --git a/badges/action.php b/badges/action.php
new file mode 100644 (file)
index 0000000..f32e05b
--- /dev/null
@@ -0,0 +1,159 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Page to handle actions associated with badges management.
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+require_once(dirname(dirname(__FILE__)) . '/config.php');
+require_once($CFG->libdir . '/badgeslib.php');
+
+$badgeid = required_param('id', PARAM_INT);
+$copy = optional_param('copy', 0, PARAM_BOOL);
+$delete    = optional_param('delete', 0, PARAM_BOOL);
+$activate = optional_param('activate', 0, PARAM_BOOL);
+$deactivate = optional_param('lock', 0, PARAM_BOOL);
+$confirm   = optional_param('confirm', 0, PARAM_BOOL);
+$return = optional_param('return', 0, PARAM_LOCALURL);
+
+require_login();
+
+$badge = new badge($badgeid);
+$context = $badge->get_context();
+$navurl = new moodle_url('/badges/index.php', array('type' => $badge->type));
+
+if ($badge->type == BADGE_TYPE_COURSE) {
+    require_login($badge->courseid);
+    $navurl = new moodle_url('/badges/index.php', array('type' => $badge->type, 'id' => $badge->courseid));
+}
+
+$PAGE->set_context($context);
+$PAGE->set_url('/badges/action.php', array('id' => $badge->id));
+$PAGE->set_pagelayout('standard');
+navigation_node::override_active_url($navurl);
+
+if ($return !== 0) {
+    $returnurl = new moodle_url($return);
+} else {
+    $returnurl = new moodle_url('/badges/overview.php', array('id' => $badge->id));
+}
+$returnurl->remove_params('awards');
+
+if ($delete) {
+    require_capability('moodle/badges:deletebadge', $context);
+
+    $PAGE->url->param('delete', 1);
+    if ($confirm) {
+        require_sesskey();
+        $badge->delete();
+        redirect(new moodle_url('/badges/index.php', array('type' => $badge->type, 'id' => $badge->courseid)));
+    }
+
+    $strheading = get_string('delbadge', 'badges');
+    $PAGE->navbar->add($strheading);
+    $PAGE->set_title($strheading);
+    $PAGE->set_heading($badge->name);
+    echo $OUTPUT->header();
+    echo $OUTPUT->heading($strheading);
+
+    $urlparams = array(
+        'id' => $badge->id,
+        'delete' => 1,
+        'confirm' => 1,
+        'sesskey' => sesskey()
+    );
+    $continue = new moodle_url('/badges/action.php', $urlparams);
+    $cancel = new moodle_url('/badges/index.php', array('type' => $badge->type, 'id' => $badge->courseid));
+
+    $message = get_string('delconfirm', 'badges', $badge->name);
+    echo $OUTPUT->confirm($message, $continue, $cancel);
+    echo $OUTPUT->footer();
+    die;
+}
+
+if ($copy) {
+    require_capability('moodle/badges:createbadge', $context);
+
+    $cloneid = $badge->make_clone();
+    redirect(new moodle_url('/badges/edit.php', array('id' => $cloneid, 'action' => 'details')));
+}
+
+if ($activate) {
+    require_capability('moodle/badges:configurecriteria', $context);
+
+    $PAGE->url->param('activate', 1);
+    $status = ($badge->status == BADGE_STATUS_INACTIVE) ? BADGE_STATUS_ACTIVE : BADGE_STATUS_ACTIVE_LOCKED;
+    if ($confirm == 1) {
+        require_sesskey();
+        $badge->set_status($status);
+
+        if ($badge->type == BADGE_TYPE_SITE) {
+            // Review on cron if there are more than 1000 users who can earn a site-level badge.
+            $sql = 'SELECT COUNT(u.id) as num
+                        FROM {user} u
+                        LEFT JOIN {badge_issued} bi
+                            ON u.id = bi.userid AND bi.badgeid = :badgeid
+                        WHERE bi.badgeid IS NULL AND u.id != :guestid AND u.deleted = 0';
+            $toearn = $DB->get_record_sql($sql, array('badgeid' => $badge->id, 'guestid' => $CFG->siteguest));
+
+            if ($toearn->num < 1000) {
+                $awards = $badge->review_all_criteria();
+                $returnurl->param('awards', $awards);
+            } else {
+                $returnurl->param('awards', 'cron');
+            }
+        } else {
+            $awards = $badge->review_all_criteria();
+            $returnurl->param('awards', $awards);
+         }
+        redirect($returnurl);
+    }
+
+    $strheading = get_string('reviewbadge', 'badges');
+    $PAGE->navbar->add($strheading);
+    $PAGE->set_title($strheading);
+    $PAGE->set_heading($badge->name);
+    echo $OUTPUT->header();
+    echo $OUTPUT->heading($strheading);
+
+    $params = array('id' => $badge->id, 'activate' => 1, 'sesskey' => sesskey(), 'confirm' => 1, 'return' => $return);
+    $url = new moodle_url('/badges/action.php', $params);
+
+    if (!$badge->has_criteria()) {
+        echo $OUTPUT->notification(get_string('error:cannotact', 'badges') . get_string('nocriteria', 'badges'));
+        echo $OUTPUT->continue_button($returnurl);
+    } else {
+        $message = get_string('reviewconfirm', 'badges', $badge->name);
+        echo $OUTPUT->confirm($message, $url, $returnurl);
+    }
+    echo $OUTPUT->footer();
+    die;
+}
+
+if ($deactivate) {
+    require_sesskey();
+    require_capability('moodle/badges:configurecriteria', $context);
+
+    $status = ($badge->status == BADGE_STATUS_ACTIVE) ? BADGE_STATUS_INACTIVE : BADGE_STATUS_INACTIVE_LOCKED;
+    $badge->set_status($status);
+    redirect($returnurl);
+}
diff --git a/badges/ajax.php b/badges/ajax.php
new file mode 100644 (file)
index 0000000..5f99cb9
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Sends request to check web site availability.
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2013 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+define('AJAX_SCRIPT', true);
+
+require_once(dirname(dirname(__FILE__)) . '/config.php');
+require_once($CFG->libdir . '/badgeslib.php');
+
+require_login();
+$PAGE->set_url('/badges/ajax.php');
+$PAGE->set_context(context_system::instance());
+
+$result = badges_check_backpack_accessibility();
+
+$outcome = new stdClass();
+$outcome->code = $result;
+$outcome->response = get_string('error:backpacknotavailable', 'badges') . $OUTPUT->help_icon('backpackavailability', 'badges');
+echo json_encode($outcome);
+
+die();
\ No newline at end of file
diff --git a/badges/assertion.php b/badges/assertion.php
new file mode 100644 (file)
index 0000000..24b8e60
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Serve assertion JSON by unique hash of issued badge
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+require_once(dirname(dirname(__FILE__)) . '/config.php');
+require_once($CFG->libdir . '/badgeslib.php');
+
+if (empty($CFG->enablebadges)) {
+    print_error('badgesdisabled', 'badges');
+}
+
+$hash = required_param('b', PARAM_ALPHANUM);
+
+$badge = badges_get_issued_badge_info($hash);
+
+header('Content-type: application/json; charset=utf-8');
+
+echo json_encode($badge);
\ No newline at end of file
diff --git a/badges/award.php b/badges/award.php
new file mode 100644 (file)
index 0000000..b388e36
--- /dev/null
@@ -0,0 +1,143 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Handle manual badge award.
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+require_once(dirname(dirname(__FILE__)) . '/config.php');
+require_once($CFG->libdir . '/badgeslib.php');
+require_once($CFG->dirroot . '/badges/lib/awardlib.php');
+
+$badgeid = required_param('id', PARAM_INT);
+$role = optional_param('role', 0, PARAM_INT);
+
+require_login();
+
+if (empty($CFG->enablebadges)) {
+    print_error('badgesdisabled', 'badges');
+}
+
+$badge = new badge($badgeid);
+$context = $badge->get_context();
+$isadmin = is_siteadmin($USER);
+
+$navurl = new moodle_url('/badges/index.php', array('type' => $badge->type));
+
+if ($badge->type == BADGE_TYPE_COURSE) {
+    require_login($badge->courseid);
+    $navurl = new moodle_url('/badges/index.php', array('type' => $badge->type, 'id' => $badge->courseid));
+}
+
+require_capability('moodle/badges:awardbadge', $context);
+
+$url = new moodle_url('/badges/award.php', array('id' => $badgeid));
+$PAGE->set_url($url);
+$PAGE->set_context($context);
+
+// Set up navigation and breadcrumbs.
+$strrecipients = get_string('recipients', 'badges');
+navigation_node::override_active_url($navurl);
+$PAGE->navbar->add($badge->name, new moodle_url('overview.php', array('id' => $badge->id)))->add($strrecipients);
+$PAGE->set_title($strrecipients);
+$PAGE->set_heading($badge->name);
+$PAGE->set_pagelayout('standard');
+
+if (!$badge->is_active()) {
+    echo $OUTPUT->header();
+    echo $OUTPUT->notification(get_string('donotaward', 'badges'));
+    echo $OUTPUT->footer();
+    die();
+}
+
+$output = $PAGE->get_renderer('core', 'badges');
+
+// Roles that can award this badge.
+$accepted_roles = array_keys($badge->criteria[BADGE_CRITERIA_TYPE_MANUAL]->params);
+
+// If site admin, select a role to award a badge.
+if ($isadmin) {
+    list($usertest, $userparams) = $DB->get_in_or_equal($accepted_roles, SQL_PARAMS_NAMED, 'existing', true);
+    $options = $DB->get_records_sql('SELECT * FROM {role} WHERE id ' . $usertest, $userparams);
+    foreach ($options as $p) {
+        $select[$p->id] = role_get_name($p);
+    }
+    if (!$role) {
+        echo $OUTPUT->header();
+        echo $OUTPUT->box(get_string('adminaward', 'badges') . $OUTPUT->single_select(new moodle_url($PAGE->url), 'role', $select));
+        echo $OUTPUT->footer();
+        die();
+    } else {
+        $issuerrole = new stdClass();
+        $issuerrole->roleid = $role;
+        $roleselect = get_string('adminaward', 'badges') . $OUTPUT->single_select(new moodle_url($PAGE->url), 'role', $select, $role);
+    }
+} else {
+    // Current user's role.
+    $issuerrole = array_shift(get_user_roles($context, $USER->id));
+    if (!isset($issuerrole->roleid) || !in_array($issuerrole->roleid, $accepted_roles)) {
+        echo $OUTPUT->header();
+        $rlink = html_writer::link(new moodle_url('recipients.php', array('id' => $badge->id)), get_string('recipients', 'badges'));
+        echo $OUTPUT->notification(get_string('notacceptedrole', 'badges', $rlink));
+        echo $OUTPUT->footer();
+        die();
+    }
+}
+$options = array(
+        'badgeid' => $badge->id,
+        'context' => $context,
+        'issuerid' => $USER->id,
+        'issuerrole' => $issuerrole->roleid
+        );
+$existingselector = new badge_existing_users_selector('existingrecipients', $options);
+$recipientselector = new badge_potential_users_selector('potentialrecipients', $options);
+$recipientselector->set_existing_recipients($existingselector->find_users(''));
+
+if (optional_param('award', false, PARAM_BOOL) && data_submitted() && has_capability('moodle/badges:awardbadge', $context)) {
+    require_sesskey();
+    $users = $recipientselector->get_selected_users();
+    foreach ($users as $user) {
+        if (process_manual_award($user->id, $USER->id, $issuerrole->roleid, $badgeid)) {
+            // If badge was successfully awarded, review manual badge criteria.
+            $data = new stdClass();
+            $data->crit = $badge->criteria[BADGE_CRITERIA_TYPE_MANUAL];
+            $data->userid = $user->id;
+            badges_award_handle_manual_criteria_review($data);
+        } else {
+            echo $OUTPUT->error_text(get_string('error:cannotawardbadge', 'badges'));
+        }
+    }
+
+    $recipientselector->invalidate_selected_users();
+    $existingselector->invalidate_selected_users();
+    $recipientselector->set_existing_recipients($existingselector->find_users(''));
+}
+
+echo $OUTPUT->header();
+echo $OUTPUT->heading($strrecipients);
+
+if ($isadmin) {
+    echo $OUTPUT->box($roleselect);
+}
+
+echo $output->recipients_selection_form($existingselector, $recipientselector);
+echo $OUTPUT->footer();
diff --git a/badges/backpack.js b/badges/backpack.js
new file mode 100644 (file)
index 0000000..b0caec4
--- /dev/null
@@ -0,0 +1,29 @@
+/**
+ * Push badges to backpack.
+ */
+function addtobackpack(event, args) {
+    OpenBadges.issue([args.assertion], function(errors, successes) { });
+}
+
+/**
+ * Check if website is externally accessible from the backpack.
+ */
+function check_site_access() {
+    var add = Y.one('#check_connection');
+    var callback = {
+        success: function(o) {
+            var data = Y.JSON.parse(o.responseText);
+            if (data.code == 'http-unreachable') {
+                add.setHTML(data.response);
+                add.removeClass('hide');
+            }
+        },
+        failure: function(o) { }
+    };
+
+    YUI().use('yui2-connection', function (Y) {
+        Y.YUI2.util.Connect.asyncRequest('GET', 'ajax.php', callback, null);
+    });
+
+    return false;
+}
\ No newline at end of file
diff --git a/badges/backpack_form.php b/badges/backpack_form.php
new file mode 100644 (file)
index 0000000..e0e25ad
--- /dev/null
@@ -0,0 +1,109 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Form class for mybackpack.php
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir . '/formslib.php');
+require_once($CFG->libdir . '/badgeslib.php');
+
+/**
+ * Form to edit backpack initial details.
+ *
+ */
+class edit_backpack_form extends moodleform {
+
+    /**
+     * Defines the form
+     */
+    public function definition() {
+        global $USER;
+        $mform = $this->_form;
+
+        $mform->addElement('header', 'backpackheader', get_string('backpackdetails', 'badges'));
+        $mform->addElement('static', 'url', get_string('url'), 'http://backpack.openbadges.org');
+
+        $mform->addElement('text', 'email', get_string('email'), array('size' => '50'));
+        $mform->setDefault('email', $USER->email);
+        $mform->setType('email', PARAM_EMAIL);
+        $mform->addRule('email', get_string('required'), 'required', null , 'client');
+        $mform->addRule('email', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
+
+        $mform->addElement('hidden', 'userid', $USER->id);
+        $mform->setType('userid', PARAM_INT);
+
+        $mform->addElement('hidden', 'backpackurl', 'http://backpack.openbadges.org');
+        $mform->setType('backpackurl', PARAM_URL);
+
+        $this->add_action_buttons();
+    }
+}
+
+/**
+ * Form to select backpack group options.
+ *
+ */
+class edit_backpack_group_form extends moodleform {
+
+    /**
+     * Defines the form
+     */
+    public function definition() {
+        global $USER;
+        $mform = $this->_form;
+        $data = $this->_customdata['data'];
+        $groups = $this->_customdata['groups'];
+        $uid = $this->_customdata['backpackuid'];
+
+        $selet = array();
+        foreach ($groups as $group) {
+            $select[$group->groupId] = $group->name;
+        }
+
+        $mform->addElement('header', 'groupheader', get_string('backpackdetails', 'badges'));
+        $mform->addElement('static', 'url', get_string('url'), 'http://backpack.openbadges.org');
+
+        $mform->addElement('text', 'email', get_string('email'), array('size' => '50'));
+        $mform->setDefault('email', $data->email);
+        $mform->freeze(array('email'));
+
+        $mform->addElement('select', 'backpackgid', get_string('selectgroup', 'badges'), $select);
+        $mform->addRule('backpackgid', get_string('required'), 'required', null , 'client');
+        if (isset($data->backpackgid)) {
+            $mform->setDefault('backpackgid', $data->backpackgid);
+        }
+
+        $mform->addElement('hidden', 'userid', $data->userid);
+        $mform->setType('userid', PARAM_INT);
+
+        $mform->addElement('hidden', 'backpackurl', 'http://backpack.openbadges.org');
+        $mform->setType('backpackurl', PARAM_URL);
+
+        $mform->addElement('hidden', 'backpackuid', $uid);
+        $mform->setType('backpackuid', PARAM_INT);
+
+        $this->add_action_buttons();
+    }
+}
\ No newline at end of file
diff --git a/badges/badge.php b/badges/badge.php
new file mode 100644 (file)
index 0000000..1061eb4
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Display details of an issued badge with criteria and evidence
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+require_once(dirname(dirname(__FILE__)) . '/config.php');
+require_once($CFG->libdir . '/badgeslib.php');
+
+$id = required_param('hash', PARAM_ALPHANUM);
+$bake = optional_param('bake', 0, PARAM_BOOL);
+
+$PAGE->set_context(context_system::instance());
+$output = $PAGE->get_renderer('core', 'badges');
+
+$badge = new issued_badge($id);
+
+if ($bake && ($badge->recipient == $USER->id)) {
+    $name = str_replace(' ', '_', $badge->issued['badge']['name']) . '.png';
+    ob_start();
+    $file = badges_bake($id, $badge->badgeid);
+    header('Content-Type: image/png');
+    header('Content-Disposition: attachment; filename="'. $name .'"');
+    readfile($file);
+    ob_flush();
+}
+
+$PAGE->set_url('/badges/badge.php', array('hash' => $id));
+$PAGE->set_pagelayout('base');
+$PAGE->set_title(get_string('issuedbadge', 'badges'));
+
+if (isloggedin()) {
+    $PAGE->set_heading($badge->issued['badge']['name']);
+    $PAGE->navbar->add($badge->issued['badge']['name']);
+    $url = new moodle_url('/badges/mybadges.php');
+    navigation_node::override_active_url($url);
+}
+
+// TODO: Better way of pushing badges to Mozilla backpack?
+if ($CFG->badges_allowexternalbackpack) {
+    $PAGE->requires->js(new moodle_url('http://backpack.openbadges.org/issuer.js'), true);
+}
+
+echo $OUTPUT->header();
+
+echo $output->render($badge);
+
+echo $OUTPUT->footer();
diff --git a/badges/criteria.php b/badges/criteria.php
new file mode 100644 (file)
index 0000000..7844499
--- /dev/null
@@ -0,0 +1,103 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Editing badge details, criteria, messages
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+require_once(dirname(dirname(__FILE__)) . '/config.php');
+require_once($CFG->libdir . '/badgeslib.php');
+
+$badgeid = required_param('id', PARAM_INT);
+$update = optional_param('update', 0, PARAM_INT);
+
+require_login();
+
+if (empty($CFG->enablebadges)) {
+    print_error('badgesdisabled', 'badges');
+}
+
+$badge = new badge($badgeid);
+$context = $badge->get_context();
+$navurl = new moodle_url('/badges/index.php', array('type' => $badge->type));
+
+if ($badge->type == BADGE_TYPE_COURSE) {
+    require_login($badge->courseid);
+    $navurl = new moodle_url('/badges/index.php', array('type' => $badge->type, 'id' => $badge->courseid));
+}
+
+$currenturl = new moodle_url('/badges/criteria.php', array('id' => $badge->id));
+
+$PAGE->set_context($context);
+$PAGE->set_url($currenturl);
+$PAGE->set_pagelayout('standard');
+$PAGE->set_heading($badge->name);
+$PAGE->set_title($badge->name);
+
+// Set up navigation and breadcrumbs.
+navigation_node::override_active_url($navurl);
+$PAGE->navbar->add($badge->name);
+
+$output = $PAGE->get_renderer('core', 'badges');
+$msg = optional_param('msg', '', PARAM_TEXT);
+$emsg = optional_param('emsg', '', PARAM_TEXT);
+
+if ((($update == BADGE_CRITERIA_AGGREGATION_ALL) || ($update == BADGE_CRITERIA_AGGREGATION_ANY))) {
+    require_sesskey();
+    require_capability('moodle/badges:configurecriteria', $context);
+    $obj = new stdClass();
+    $obj->id = $badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->id;
+    $obj->method = $update;
+    if ($DB->update_record('badge_criteria', $obj)) {
+        $msg = get_string('changessaved');
+    } else {
+        $emsg = get_string('error:save', 'badges');
+    }
+}
+
+echo $OUTPUT->header();
+echo $OUTPUT->heading(print_badge_image($badge, $context, 'small') . ' ' . $badge->name);
+
+if ($emsg !== '') {
+    echo $OUTPUT->notification($emsg);
+} else if ($msg !== '') {
+    echo $OUTPUT->notification($msg, 'notifysuccess');
+}
+
+echo $output->print_badge_status_box($badge);
+$output->print_badge_tabs($badgeid, $context, 'criteria');
+
+if (!$badge->is_locked() && !$badge->is_active()) {
+    echo $output->print_criteria_actions($badge);
+}
+
+if ($badge->has_criteria()) {
+    ksort($badge->criteria);
+
+    foreach ($badge->criteria as $crit) {
+        $crit->config_form_criteria($badge);
+    }
+} else {
+    echo $OUTPUT->box(get_string('addcriteriatext', 'badges'));
+}
+
+echo $OUTPUT->footer();
\ No newline at end of file
diff --git a/badges/criteria/award_criteria.php b/badges/criteria/award_criteria.php
new file mode 100644 (file)
index 0000000..9a469e0
--- /dev/null
@@ -0,0 +1,390 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Badge award criteria
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/*
+ * Role completion criteria type
+ * Criteria type constant, primarily for storing criteria type in the database.
+ */
+define('BADGE_CRITERIA_TYPE_OVERALL', 0);
+
+/*
+ * Activity completion criteria type
+ * Criteria type constant, primarily for storing criteria type in the database.
+ */
+define('BADGE_CRITERIA_TYPE_ACTIVITY', 1);
+
+/*
+ * Duration completion criteria type
+ * Criteria type constant, primarily for storing criteria type in the database.
+ */
+define('BADGE_CRITERIA_TYPE_MANUAL', 2);
+
+/*
+ * Grade completion criteria type
+ * Criteria type constant, primarily for storing criteria type in the database.
+ */
+define('BADGE_CRITERIA_TYPE_SOCIAL', 3);
+
+/*
+ * Course completion criteria type
+ * Criteria type constant, primarily for storing criteria type in the database.
+*/
+define('BADGE_CRITERIA_TYPE_COURSE', 4);
+
+/*
+ * Courseset completion criteria type
+ * Criteria type constant, primarily for storing criteria type in the database.
+ */
+define('BADGE_CRITERIA_TYPE_COURSESET', 5);
+
+/*
+ * Course completion criteria type
+ * Criteria type constant, primarily for storing criteria type in the database.
+ */
+define('BADGE_CRITERIA_TYPE_PROFILE', 6);
+
+/*
+ * Criteria type constant to class name mapping
+ */
+global $BADGE_CRITERIA_TYPES;
+$BADGE_CRITERIA_TYPES = array(
+    BADGE_CRITERIA_TYPE_OVERALL   => 'overall',
+    BADGE_CRITERIA_TYPE_ACTIVITY  => 'activity',
+    BADGE_CRITERIA_TYPE_MANUAL    => 'manual',
+    BADGE_CRITERIA_TYPE_SOCIAL    => 'social',
+    BADGE_CRITERIA_TYPE_COURSE    => 'course',
+    BADGE_CRITERIA_TYPE_COURSESET => 'courseset',
+    BADGE_CRITERIA_TYPE_PROFILE   => 'profile'
+);
+
+/**
+ * Award criteria abstract definition
+ *
+ */
+abstract class award_criteria {
+
+    public $id;
+    public $method;
+    public $badgeid;
+    public $params = array();
+
+    /**
+     * The base constructor
+     *
+     * @param array $params
+     */
+    public function __construct($params) {
+        $this->id = isset($params['id']) ? $params['id'] : 0;
+        $this->method = isset($params['method']) ? $params['method'] : BADGE_CRITERIA_AGGREGATION_ANY;
+        $this->badgeid = $params['badgeid'];
+        if (isset($params['id'])) {
+            $this->params = $this->get_params($params['id']);
+        }
+    }
+
+    /**
+     * Factory method for creating criteria class object
+     *
+     * @param array $params associative arrays varname => value
+     * @return award_criteria
+     */
+    public static function build($params) {
+        global $CFG, $BADGE_CRITERIA_TYPES;
+
+        if (!isset($params['criteriatype']) || !isset($BADGE_CRITERIA_TYPES[$params['criteriatype']])) {
+            print_error('error:invalidcriteriatype', 'badges');
+        }
+
+        $class = 'award_criteria_' . $BADGE_CRITERIA_TYPES[$params['criteriatype']];
+        require_once($CFG->dirroot . '/badges/criteria/' . $class . '.php');
+
+        return new $class($params);
+    }
+
+    /**
+     * Return criteria title
+     *
+     * @return string
+     */
+    public function get_title() {
+        return get_string('criteria_' . $this->criteriatype, 'badges');
+    }
+
+    /**
+     * Get criteria details for displaying to users
+     *
+     * @param string $short Print short version of criteria
+     * @return string
+     */
+    abstract public function get_details($short = '');
+
+    /**
+     * Add appropriate criteria options to the form
+     *
+     */
+    abstract public function get_options(&$mform);
+
+    /**
+     * Add appropriate parameter elements to the criteria form
+     *
+     */
+    public function config_options(&$mform, $param) {
+        global $OUTPUT;
+        $prefix = $this->required_param . '_';
+
+        if ($param['error']) {
+            $parameter[] =& $mform->createElement('advcheckbox', $prefix . $param['id'], '',
+                    $OUTPUT->error_text($param['name']), null, array(0, $param['id']));
+            $mform->addGroup($parameter, 'param_' . $prefix . $param['id'], '', array(' '), false);
+        } else {
+            $parameter[] =& $mform->createElement('advcheckbox', $prefix . $param['id'], '', $param['name'], null, array(0, $param['id']));
+            $parameter[] =& $mform->createElement('static', 'break_start_' . $param['id'], null, '<div style="margin-left: 3em;">');
+
+            if (in_array('grade', $this->optional_params)) {
+                $parameter[] =& $mform->createElement('static', 'mgrade_' . $param['id'], null, get_string('mingrade', 'badges'));
+                $parameter[] =& $mform->createElement('text', 'grade_' . $param['id'], '', array('size' => '5'));
+            }
+
+            if (in_array('bydate', $this->optional_params)) {
+                $parameter[] =& $mform->createElement('static', 'complby_' . $param['id'], null, get_string('bydate', 'badges'));
+                $parameter[] =& $mform->createElement('date_selector', 'bydate_' . $param['id'], "", array('optional' => true));
+            }
+
+            $parameter[] =& $mform->createElement('static', 'break_end_' . $param['id'], null, '</div>');
+            $mform->addGroup($parameter, 'param_' . $prefix . $param['id'], '', array(' '), false);
+            if (in_array('grade', $this->optional_params)) {
+                $mform->addGroupRule('param_' . $prefix . $param['id'], array(
+                    'grade_' . $param['id'] => array(array(get_string('err_numeric', 'form'), 'numeric', '', 'client'))));
+            }
+            $mform->disabledIf('bydate_' . $param['id'] . '[day]', 'bydate_' . $param['id'] . '[enabled]', 'notchecked');
+            $mform->disabledIf('bydate_' . $param['id'] . '[month]', 'bydate_' . $param['id'] . '[enabled]', 'notchecked');
+            $mform->disabledIf('bydate_' . $param['id'] . '[year]', 'bydate_' . $param['id'] . '[enabled]', 'notchecked');
+            $mform->disabledIf('param_' . $prefix . $param['id'], $prefix . $param['id'], 'notchecked');
+        }
+
+        // Set default values.
+        $mform->setDefault($prefix . $param['id'], $param['checked']);
+        if (isset($param['bydate'])) {
+            $mform->setDefault('bydate_' . $param['id'], $param['bydate']);
+        }
+        if (isset($param['grade'])) {
+            $mform->setDefault('grade_' . $param['id'], $param['grade']);
+        }
+    }
+
+    /**
+     * Add appropriate criteria elements
+     *
+     * @param stdClass $data details of various criteria
+     */
+    public function config_form_criteria($data) {
+        global $OUTPUT;
+        $agg = $data->get_aggregation_methods();
+
+        $editurl = new moodle_url('/badges/criteria_settings.php',
+                array('badgeid' => $this->badgeid, 'edit' => true, 'type' => $this->criteriatype, 'crit' => $this->id));
+        $deleteurl = new moodle_url('/badges/criteria_action.php',
+                array('badgeid' => $this->badgeid, 'delete' => true, 'type' => $this->criteriatype));
+        $editaction = $OUTPUT->action_icon($editurl, new pix_icon('t/edit', get_string('edit')), null, array('class' => 'criteria-action'));
+        $deleteaction = $OUTPUT->action_icon($deleteurl, new pix_icon('t/delete', get_string('delete')), null, array('class' => 'criteria-action'));
+
+        echo $OUTPUT->box_start();
+        if (!$data->is_locked() && !$data->is_active()) {
+            echo $OUTPUT->box($deleteaction . $editaction, array('criteria-header'));
+        }
+        echo $OUTPUT->heading($this->get_title() . $OUTPUT->help_icon('criteria_' . $this->criteriatype, 'badges'), 3, 'main help');
+
+        if (!empty($this->params)) {
+            if (count($this->params) > 1) {
+                echo $OUTPUT->box(get_string('criteria_descr_' . $this->criteriatype, 'badges',
+                        strtoupper($agg[$data->get_aggregation_method($this->criteriatype)])), array('clearfix'));
+            } else {
+                echo $OUTPUT->box(get_string('criteria_descr_single_' . $this->criteriatype , 'badges'), array('clearfix'));
+            }
+            echo $OUTPUT->box($this->get_details(), array('clearfix'));
+        }
+        echo $OUTPUT->box_end();
+    }
+
+    /**
+     * Review this criteria and decide if the user has completed
+     *
+     * @param int $userid User whose criteria completion needs to be reviewed.
+     * @return bool Whether criteria is complete
+     */
+    abstract public function review($userid);
+
+    /**
+     * Mark this criteria as complete for a user
+     *
+     * @param int $userid User whose criteria is completed.
+     */
+    public function mark_complete($userid) {
+        global $DB;
+        $obj = array();
+        $obj['critid'] = $this->id;
+        $obj['userid'] = $userid;
+        $obj['datemet'] = time();
+        if (!$DB->record_exists('badge_criteria_met', array('critid' => $this->id, 'userid' => $userid))) {
+            $DB->insert_record('badge_criteria_met', $obj);
+        }
+    }
+
+    /**
+     * Return criteria parameters
+     *
+     * @param int $critid Criterion ID
+     * @return array
+     */
+    public function get_params($cid) {
+        global $DB;
+        $params = array();
+
+        $records = $DB->get_records('badge_criteria_param', array('critid' => $cid));
+        foreach ($records as $rec) {
+            $arr = explode('_', $rec->name);
+            $params[$arr[1]][$arr[0]] = $rec->value;
+        }
+
+        return $params;
+    }
+
+    /**
+     * Delete this criterion
+     *
+     */
+    public function delete() {
+        global $DB;
+
+        // Remove any records if it has already been met.
+        $DB->delete_records('badge_criteria_met', array('critid' => $this->id));
+
+        // Remove all parameters records.
+        $DB->delete_records('badge_criteria_param', array('critid' => $this->id));
+
+        // Finally remove criterion itself.
+        $DB->delete_records('badge_criteria', array('id' => $this->id));
+    }
+
+    /**
+     * Saves intial criteria records with required parameters set up.
+     */
+    public function save($params = array()) {
+        global $DB;
+        $fordb = new stdClass();
+        $fordb->criteriatype = $this->criteriatype;
+        $fordb->method = isset($params->agg) ? $params->agg : $params['agg'];
+        $fordb->badgeid = $this->badgeid;
+        $t = $DB->start_delegated_transaction();
+
+        // Unset unnecessary parameters supplied with form.
+        if (isset($params->agg)) {
+            unset($params->agg);
+        } else {
+            unset($params['agg']);
+        }
+        unset($params->submitbutton);
+        $params = array_filter((array)$params);
+
+        if ($this->id !== 0) {
+            $cid = $this->id;
+
+            // Update criteria before doing anything with parameters.
+            $fordb->id = $cid;
+            $DB->update_record('badge_criteria', $fordb, true);
+
+            $existing = $DB->get_fieldset_select('badge_criteria_param', 'name', 'critid = ?', array($cid));
+            $todelete = array_diff($existing, array_keys($params));
+
+            if (!empty($todelete)) {
+                // A workaround to add some disabled elements that are still being submitted from the form.
+                foreach ($todelete as $del) {
+                    $name = explode('_', $del);
+                    if ($name[0] == $this->required_param) {
+                        foreach ($this->optional_params as $opt) {
+                            $todelete[] = $opt . '_' . $name[1];
+                        }
+                    }
+                }
+                $todelete = array_unique($todelete);
+                list($sql, $sqlparams) = $DB->get_in_or_equal($todelete, SQL_PARAMS_NAMED, 'd', true);
+                $sqlparams = array_merge(array('critid' => $cid), $sqlparams);
+                $DB->delete_records_select('badge_criteria_param', 'critid = :critid AND name ' . $sql, $sqlparams);
+            }
+
+            foreach ($params as $key => $value) {
+                if (in_array($key, $existing)) {
+                    $updp = $DB->get_record('badge_criteria_param', array('name' => $key, 'critid' => $cid));
+                    $updp->value = $value;
+                    $DB->update_record('badge_criteria_param', $updp, true);
+                } else {
+                    $newp = new stdClass();
+                    $newp->critid = $cid;
+                    $newp->name = $key;
+                    $newp->value = $value;
+                    $DB->insert_record('badge_criteria_param', $newp);
+                }
+            }
+        } else {
+            $cid = $DB->insert_record('badge_criteria', $fordb, true);
+            if ($cid) {
+                foreach ($params as $key => $value) {
+                    $newp = new stdClass();
+                    $newp->critid = $cid;
+                    $newp->name = $key;
+                    $newp->value = $value;
+                    $DB->insert_record('badge_criteria_param', $newp, false, true);
+                }
+            }
+         }
+         $t->allow_commit();
+    }
+
+    /**
+     * Saves intial criteria records with required parameters set up.
+     */
+    public function make_clone($newbadgeid) {
+        global $DB;
+
+        $fordb = new stdClass();
+        $fordb->criteriatype = $this->criteriatype;
+        $fordb->method = $this->method;
+        $fordb->badgeid = $newbadgeid;
+        if (($newcrit = $DB->insert_record('badge_criteria', $fordb, true)) && isset($this->params)) {
+            foreach ($this->params as $k => $param) {
+                foreach ($param as $key => $value) {
+                    $paramdb = new stdClass();
+                    $paramdb->critid = $newcrit;
+                    $paramdb->name = $key . '_' . $k;
+                    $paramdb->value = $value;
+                    $DB->insert_record('badge_criteria_param', $paramdb);
+                }
+            }
+        }
+    }
+}
diff --git a/badges/criteria/award_criteria_activity.php b/badges/criteria/award_criteria_activity.php
new file mode 100644 (file)
index 0000000..3a14e6b
--- /dev/null
@@ -0,0 +1,232 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains the activity badge award criteria type class
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+defined('MOODLE_INTERNAL') || die();
+require_once($CFG->libdir . '/completionlib.php');
+
+/**
+ * Badge award criteria -- award on activity completion
+ *
+ */
+class award_criteria_activity extends award_criteria {
+
+    /* @var int Criteria [BADGE_CRITERIA_TYPE_ACTIVITY] */
+    public $criteriatype = BADGE_CRITERIA_TYPE_ACTIVITY;
+
+    private $courseid;
+
+    public $required_param = 'module';
+    public $optional_params = array('bydate');
+
+    public function __construct($record) {
+        parent::__construct($record);
+        $this->courseid = self::get_course();
+    }
+
+    /**
+     * Gets the module instance from the database and returns it.
+     * If no module instance exists this function returns false.
+     *
+     * @return stdClass|bool
+     */
+    private function get_mod_instance($cmid) {
+        global $DB;
+        $rec = $DB->get_record_sql("SELECT md.name
+                               FROM {course_modules} cm,
+                                    {modules} md
+                               WHERE cm.id = ? AND
+                                     md.id = cm.module", array($cmid));
+
+        if ($rec) {
+            return get_coursemodule_from_id($rec->name, $cmid);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Get criteria description for displaying to users
+     *
+     * @return string
+     */
+    public function get_details($short = '') {
+        global $DB, $OUTPUT;
+        $output = array();
+        foreach ($this->params as $p) {
+            $mod = self::get_mod_instance($p['module']);
+            if (!$mod) {
+                $str = $OUTPUT->error_text(get_string('error:nosuchmod', 'badges'));
+            } else {
+                $str = html_writer::tag('b', '"' . ucfirst($mod->modname) . ' - ' . $mod->name . '"');
+                if (isset($p['bydate'])) {
+                    $str .= get_string('criteria_descr_bydate', 'badges', userdate($p['bydate'], get_string('strftimedate', 'core_langconfig')));
+                }
+            }
+            $output[] = $str;
+        }
+
+        if ($short) {
+            return implode(', ', $output);
+        } else {
+            return html_writer::alist($output, array(), 'ul');
+        }
+    }
+
+    /**
+     * Return course ID for activities
+     *
+     * @return int
+     */
+    private function get_course() {
+        global $DB;
+        $courseid = $DB->get_field('badge', 'courseid', array('id' => $this->badgeid));
+        return $courseid;
+    }
+
+    /**
+     * Add appropriate new criteria options to the form
+     *
+     */
+    public function get_options(&$mform) {
+        global $DB;
+
+        $none = true;
+        $existing = array();
+        $missing = array();
+
+        $course = $DB->get_record('course', array('id' => $this->courseid));
+        $info = new completion_info($course);
+        $mods = $info->get_activities();
+        $mids = array_map(create_function('$o', 'return $o->id;'), $mods);
+
+        if ($this->id !== 0) {
+            $existing = array_keys($this->params);
+            $missing = array_diff($existing, $mids);
+        }
+
+        if (!empty($missing)) {
+            $mform->addElement('header', 'category_errors', get_string('criterror', 'badges'));
+            $mform->addHelpButton('category_errors', 'criterror', 'badges');
+            foreach ($missing as $m) {
+                $this->config_options($mform, array('id' => $m, 'checked' => true,
+                        'name' => get_string('error:nosuchmod', 'badges'), 'error' => true));
+                $none = false;
+            }
+        }
+
+        if (!empty($mods)) {
+            $mform->addElement('header', 'first_header', $this->get_title());
+            foreach ($mods as $mod) {
+                $checked = false;
+                if (in_array($mod->id, $existing)) {
+                    $checked = true;
+                }
+                $param = array('id' => $mod->id,
+                        'checked' => $checked,
+                        'name' => ucfirst($mod->modname) . ' - ' . $mod->name,
+                        'error' => false
+                        );
+
+                if ($this->id !== 0 && isset($this->params[$mod->id]['bydate'])) {
+                    $param['bydate'] = $this->params[$mod->id]['bydate'];
+                }
+
+                if ($this->id !== 0 && isset($this->params[$mod->id]['grade'])) {
+                    $param['grade'] = $this->params[$mod->id]['grade'];
+                }
+
+                $this->config_options($mform, $param);
+                $none = false;
+            }
+        }
+
+        // Add aggregation.
+        if (!$none) {
+            $mform->addElement('header', 'aggregation', get_string('method', 'badges'));
+            $agg = array();
+            $agg[] =& $mform->createElement('radio', 'agg', '', get_string('allmethodactivity', 'badges'), 1);
+            $agg[] =& $mform->createElement('radio', 'agg', '', get_string('anymethodactivity', 'badges'), 2);
+            $mform->addGroup($agg, 'methodgr', '', array('<br/>'), false);
+            if ($this->id !== 0) {
+                $mform->setDefault('agg', $this->method);
+            } else {
+                $mform->setDefault('agg', BADGE_CRITERIA_AGGREGATION_ANY);
+            }
+        }
+
+        return array($none, get_string('error:noactivities', 'badges'));
+    }
+
+    /**
+     * Review this criteria and decide if it has been completed
+     *
+     * @param int $userid User whose criteria completion needs to be reviewed.
+     * @return bool Whether criteria is complete
+     */
+    public function review($userid) {
+        global $DB;
+        $completionstates = array(COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS);
+        $course = $DB->get_record('course', array('id' => $this->courseid));
+
+        if ($course->startdate > time()) {
+            return false;
+        }
+
+        $info = new completion_info($course);
+
+        $overall = false;
+        foreach ($this->params as $param) {
+            $cm = new stdClass();
+            $cm->id = $param['module'];
+
+            $data = $info->get_data($cm, false, $userid);
+            $check_date = true;
+
+            if (isset($param['bydate'])) {
+                $date = $data->timemodified;
+                $check_date = ($date <= $param['bydate']);
+            }
+
+            if ($this->method == BADGE_CRITERIA_AGGREGATION_ALL) {
+                if (in_array($data->completionstate, $completionstates) && $check_date) {
+                    $overall = true;
+                    continue;
+                } else {
+                    return false;
+                }
+            } else if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+                if (in_array($data->completionstate, $completionstates) && $check_date) {
+                    return true;
+                } else {
+                    $overall = false;
+                    continue;
+                }
+            }
+        }
+
+        return $overall;
+    }
+}
diff --git a/badges/criteria/award_criteria_course.php b/badges/criteria/award_criteria_course.php
new file mode 100644 (file)
index 0000000..1ad58a5
--- /dev/null
@@ -0,0 +1,178 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains the course completion badge award criteria type class
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+defined('MOODLE_INTERNAL') || die();
+require_once($CFG->libdir . '/completionlib.php');
+require_once($CFG->dirroot . '/grade/querylib.php');
+require_once($CFG->libdir . '/gradelib.php');
+
+/**
+ * Badge award criteria -- award on course completion
+ *
+ */
+class award_criteria_course extends award_criteria {
+
+    /* @var int Criteria [BADGE_CRITERIA_TYPE_COURSE] */
+    public $criteriatype = BADGE_CRITERIA_TYPE_COURSE;
+
+    public $required_param = 'course';
+    public $optional_params = array('grade', 'bydate');
+
+    /**
+     * Add appropriate form elements to the criteria form
+     *
+     * @param moodleform $mform  Moodle forms object
+     * @param stdClass $data details of various modules
+     */
+    public function config_form_criteria($data) {
+        global $OUTPUT;
+
+        $editurl = new moodle_url('/badges/criteria_settings.php', array('badgeid' => $this->badgeid, 'edit' => true, 'type' => $this->criteriatype, 'crit' => $this->id));
+        $deleteurl = new moodle_url('/badges/criteria_action.php', array('badgeid' => $this->badgeid, 'delete' => true, 'type' => $this->criteriatype));
+        $editaction = $OUTPUT->action_icon($editurl, new pix_icon('t/edit', get_string('edit')), null, array('class' => 'criteria-action'));
+        $deleteaction = $OUTPUT->action_icon($deleteurl, new pix_icon('t/delete', get_string('delete')), null, array('class' => 'criteria-action'));
+
+        echo $OUTPUT->box_start();
+        if (!$data->is_locked() && !$data->is_active()) {
+            echo $OUTPUT->box($deleteaction . $editaction, array('criteria-header'));
+        }
+        echo $OUTPUT->heading($this->get_title() . $OUTPUT->help_icon('criteria_' . $this->criteriatype, 'badges'), 3, 'main help');
+
+        if (!empty($this->params)) {
+            echo $OUTPUT->box(get_string('criteria_descr_' . $this->criteriatype, 'badges') . $this->get_details(), array('clearfix'));
+        }
+        echo $OUTPUT->box_end();
+    }
+
+    /**
+     * Get criteria details for displaying to users
+     *
+     * @return string
+     */
+    public function get_details($short = '') {
+        global $DB;
+        $param = reset($this->params);
+
+        $course = $DB->get_record('course', array('id' => $param['course']));
+        $str = '"' . $course->fullname . '"';
+        if (isset($param['bydate'])) {
+            $str .= get_string('criteria_descr_bydate', 'badges', userdate($param['bydate'], get_string('strftimedate', 'core_langconfig')));
+        }
+        if (isset($param['grade'])) {
+            $str .= get_string('criteria_descr_grade', 'badges', $param['grade']);
+        }
+        return $str;
+    }
+
+    /**
+     * Add appropriate new criteria options to the form
+     *
+     */
+    public function get_options(&$mform) {
+        global $PAGE, $DB;
+        $param = array_shift($this->params);
+        $course = $DB->get_record('course', array('id' => $PAGE->course->id));
+
+        if (!($course->enablecompletion == COMPLETION_ENABLED)) {
+            $none = true;
+            $message = get_string('completionnotenabled', 'badges');
+        } else {
+            $mform->addElement('header', 'criteria_course', $this->get_title());
+            $mform->addHelpButton('criteria_course', 'criteria_' . $this->criteriatype, 'badges');
+            $parameter = array();
+            $parameter[] =& $mform->createElement('static', 'mgrade_', null, get_string('mingrade', 'badges'));
+            $parameter[] =& $mform->createElement('text', 'grade_' . $param['course'], '', array('size' => '5'));
+            $parameter[] =& $mform->createElement('static', 'complby_' . $param['course'], null, get_string('bydate', 'badges'));
+            $parameter[] =& $mform->createElement('date_selector', 'bydate_' . $param['course'], '', array('optional' => true));
+            $mform->addGroup($parameter, 'param_' . $param['course'], '', array(' '), false);
+
+            $mform->disabledIf('bydate_' . $param['course'] . '[day]', 'bydate_' . $param['course'] . '[enabled]', 'notchecked');
+            $mform->disabledIf('bydate_' . $param['course'] . '[month]', 'bydate_' . $param['course'] . '[enabled]', 'notchecked');
+            $mform->disabledIf('bydate_' . $param['course'] . '[year]', 'bydate_' . $param['course'] . '[enabled]', 'notchecked');
+
+            // Set existing values.
+            if (isset($param['bydate'])) {
+                $mform->setDefault('bydate_' . $param['course'], $param['bydate']);
+            }
+
+            if (isset($param['grade'])) {
+                $mform->setDefault('grade_' . $param['course'], $param['grade']);
+            }
+
+            // Add hidden elements.
+            $mform->addElement('hidden', 'course_' . $course->id, $course->id);
+            $mform->setType('course_' . $course->id, PARAM_INT);
+            $mform->addElement('hidden', 'agg', BADGE_CRITERIA_AGGREGATION_ALL);
+            $mform->setType('agg', PARAM_INT);
+
+            $none = false;
+            $message = '';
+        }
+        return array($none, $message);
+    }
+
+    /**
+     * Review this criteria and decide if it has been completed
+     *
+     * @param int $userid User whose criteria completion needs to be reviewed.
+     * @return bool Whether criteria is complete
+     */
+    public function review($userid) {
+        global $DB;
+        foreach ($this->params as $param) {
+            $course = $DB->get_record('course', array('id' => $param['course']));
+
+            if ($course->startdate > time()) {
+                return false;
+            }
+
+            $info = new completion_info($course);
+            $check_grade = true;
+            $check_date = true;
+
+            if (isset($param['grade'])) {
+                $grade = grade_get_course_grade($userid, $course->id);
+                $check_grade = ($grade->grade >= $param['grade']);
+            }
+
+            if (isset($param['bydate'])) {
+                $cparams = array(
+                        'userid' => $userid,
+                        'course' => $course->id,
+                );
+                $completion = new completion_completion($cparams);
+                $date = $completion->timecompleted;
+                $check_date = ($date <= $param['bydate']);
+            }
+
+            if ($info->is_course_complete($userid) && $check_grade && $check_date) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/badges/criteria/award_criteria_courseset.php b/badges/criteria/award_criteria_courseset.php
new file mode 100644 (file)
index 0000000..6443bd5
--- /dev/null
@@ -0,0 +1,250 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains the courseset completion badge award criteria type class
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+defined('MOODLE_INTERNAL') || die();
+require_once('award_criteria_course.php');
+require_once($CFG->libdir . '/completionlib.php');
+require_once($CFG->dirroot . '/grade/querylib.php');
+require_once($CFG->libdir . '/gradelib.php');
+
+/**
+ * Badge award criteria -- award on courseset completion
+ *
+ */
+class award_criteria_courseset extends award_criteria {
+
+    /* @var int Criteria [BADGE_CRITERIA_TYPE_COURSESET] */
+    public $criteriatype = BADGE_CRITERIA_TYPE_COURSESET;
+
+    public $required_param = 'course';
+    public $optional_params = array('grade', 'bydate');
+
+    /**
+     * Get criteria details for displaying to users
+     *
+     * @return string
+     */
+    public function get_details($short = '') {
+        global $DB, $OUTPUT;
+        $output = array();
+        foreach ($this->params as $p) {
+            $coursename = $DB->get_field('course', 'fullname', array('id' => $p['course']));
+            if (!$coursename) {
+                $str = $OUTPUT->error_text(get_string('error:nosuchcourse', 'badges'));
+            } else {
+                $str = html_writer::tag('b', '"' . $coursename . '"');
+                if (isset($p['bydate'])) {
+                    $str .= get_string('criteria_descr_bydate', 'badges', userdate($p['bydate'], get_string('strftimedate', 'core_langconfig')));
+                }
+                if (isset($p['grade'])) {
+                    $str .= get_string('criteria_descr_grade', 'badges', $p['grade']);
+                }
+            }
+            $output[] = $str;
+        }
+
+        if ($short) {
+            return implode(', ', $output);
+        } else {
+            return html_writer::alist($output, array(), 'ul');
+        }
+    }
+
+    public function get_courses(&$mform) {
+        global $DB, $CFG, $PAGE;
+        require_once($CFG->dirroot . '/course/lib.php');
+        $buttonarray = array();
+
+        // Get courses with enabled completion.
+        $courses = $DB->get_records('course', array('enablecompletion' => COMPLETION_ENABLED));
+        if (!empty($courses)) {
+            $list = array();
+            $parents = array();
+            make_categories_list($list, $parents);
+
+            $select = array();
+            $selected = array();
+            foreach ($courses as $c) {
+                $select[$c->id] = $list[$c->category] . ' / ' . format_string($c->fullname, true, array('context' => context_course::instance($c->id)));
+            }
+
+            if ($this->id !== 0) {
+                $selected = array_keys($this->params);
+            }
+            $settings = array('multiple' => 'multiple', 'size' => 20, 'style' => 'width:300px');
+            $mform->addElement('select', 'courses', get_string('addcourse', 'badges'), $select, $settings);
+            $mform->addRule('courses', get_string('requiredcourse', 'badges'), 'required');
+            $mform->addHelpButton('courses', 'addcourse', 'badges');
+
+            $buttonarray[] =& $mform->createElement('submit', 'submitcourse', get_string('addcourse', 'badges'));
+            $buttonarray[] =& $mform->createElement('submit', 'back', get_string('cancel'));
+            $mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
+
+            $mform->addElement('hidden', 'addcourse', 'addcourse');
+            $mform->setType('addcourse', PARAM_TEXT);
+            if ($this->id !== 0) {
+                $mform->setDefault('courses', $selected);
+            }
+            $mform->setType('agg', PARAM_INT);
+        } else {
+            $mform->addElement('static', 'nocourses', '', get_string('error:nocourses', 'badges'));
+            $buttonarray[] =& $mform->createElement('submit', 'back', get_string('continue'));
+            $mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
+        }
+    }
+
+    public function add_courses($params = array()) {
+        global $DB;
+        $t = $DB->start_delegated_transaction();
+        if ($this->id !== 0) {
+            $critid = $this->id;
+        } else {
+            $fordb = new stdClass();
+            $fordb->criteriatype = $this->criteriatype;
+            $fordb->method = BADGE_CRITERIA_AGGREGATION_ALL;
+            $fordb->badgeid = $this->badgeid;
+            $critid = $DB->insert_record('badge_criteria', $fordb, true, true);
+        }
+        if ($critid) {
+            foreach ($params as $p) {
+                $newp = new stdClass();
+                $newp->critid = $critid;
+                $newp->name = 'course_' . $p;
+                $newp->value = $p;
+                if (!$DB->record_exists('badge_criteria_param', array('critid' => $critid, 'name' => $newp->name))) {
+                    $DB->insert_record('badge_criteria_param', $newp, false, true);
+                }
+            }
+        }
+        $t->allow_commit();
+        return $critid;
+    }
+
+    /**
+     * Add appropriate new criteria options to the form
+     *
+     */
+    public function get_options(&$mform) {
+        global $DB;
+        $none = true;
+
+        $mform->addElement('header', 'first_header', $this->get_title());
+        $mform->addHelpButton('first_header', 'criteria_' . $this->criteriatype, 'badges');
+
+        if ($courses = $DB->get_records('course', array('enablecompletion' => COMPLETION_ENABLED))) {
+            $mform->addElement('submit', 'addcourse', get_string('addcourse', 'badges'), array('class' => 'addcourse'));
+        }
+
+        // In courseset, print out only the ones that were already selected.
+        foreach ($this->params as $p) {
+            if ($course = $DB->get_record('course', array('id' => $p['course']))) {
+                $param = array(
+                        'id' => $course->id,
+                        'checked' => true,
+                        'name' => ucfirst($course->fullname),
+                        'error' => false
+                );
+
+                if (isset($p['bydate'])) {
+                    $param['bydate'] = $p['bydate'];
+                }
+                if (isset($p['grade'])) {
+                    $param['grade'] = $p['grade'];
+                }
+                $this->config_options($mform, $param);
+                $none = false;
+            } else {
+                $this->config_options($mform, array('id' => $p['course'], 'checked' => true,
+                        'name' => get_string('error:nosuchcourse', 'badges'), 'error' => true));
+            }
+        }
+
+        // Add aggregation.
+        if (!$none) {
+            $mform->addElement('header', 'aggregation', get_string('method', 'badges'));
+            $agg = array();
+            $agg[] =& $mform->createElement('radio', 'agg', '', get_string('allmethodcourseset', 'badges'), 1);
+            $agg[] =& $mform->createElement('radio', 'agg', '', get_string('anymethodcourseset', 'badges'), 2);
+            $mform->addGroup($agg, 'methodgr', '', array('<br/>'), false);
+            if ($this->id !== 0) {
+                $mform->setDefault('agg', $this->method);
+            } else {
+                $mform->setDefault('agg', BADGE_CRITERIA_AGGREGATION_ANY);
+            }
+        }
+
+        return array($none, get_string('noparamstoadd', 'badges'));
+    }
+
+    /**
+     * Review this criteria and decide if it has been completed
+     *
+     * @return bool Whether criteria is complete
+     */
+    public function review($userid) {
+        global $DB;
+        foreach ($this->params as $param) {
+            $course = $DB->get_record('course', array('id' => $param['course']));
+            $info = new completion_info($course);
+            $check_grade = true;
+            $check_date = true;
+
+            if (isset($param['grade'])) {
+                $grade = grade_get_course_grade($userid, $course->id);
+                $check_grade = ($grade->grade >= $param['grade']);
+            }
+
+            if (isset($param['bydate'])) {
+                $cparams = array(
+                        'userid' => $userid,
+                        'course' => $course->id,
+                );
+                $completion = new completion_completion($cparams);
+                $date = $completion->timecompleted;
+                $check_date = ($date <= $param['bydate']);
+            }
+
+            $overall = false;
+            if ($this->method == BADGE_CRITERIA_AGGREGATION_ALL) {
+                if ($info->is_course_complete($userid) && $check_grade && $check_date) {
+                    $overall = true;
+                    continue;
+                } else {
+                    return false;
+                }
+            } else if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+                if ($info->is_course_complete($userid) && $check_grade && $check_date) {
+                    return true;
+                } else {
+                    $overall = false;
+                    continue;
+                }
+            }
+        }
+
+        return $overall;
+    }
+}
diff --git a/badges/criteria/award_criteria_manual.php b/badges/criteria/award_criteria_manual.php
new file mode 100644 (file)
index 0000000..672c9f8
--- /dev/null
@@ -0,0 +1,184 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains the manual badge award criteria type class
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Manual badge award criteria
+ *
+ */
+class award_criteria_manual extends award_criteria {
+
+    /* @var int Criteria [BADGE_CRITERIA_TYPE_MANUAL] */
+    public $criteriatype = BADGE_CRITERIA_TYPE_MANUAL;
+
+    public $required_param = 'role';
+    public $optional_params = array();
+
+    /**
+     * Gets role name.
+     * If no such role exists this function returns null.
+     *
+     * @return string|null
+     */
+    private function get_role_name($rid) {
+        global $DB, $PAGE;
+        $rec = $DB->get_record('role', array('id' => $rid));
+
+        if ($rec) {
+            return role_get_name($rec, $PAGE->context, ROLENAME_ALIAS);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Add appropriate new criteria options to the form
+     *
+     */
+    public function get_options(&$mform) {
+        global $PAGE;
+        $options = '';
+        $none = true;
+
+        $roles = get_roles_with_capability('moodle/badges:awardbadge', CAP_ALLOW, $PAGE->context);
+        $roleids = array_map(create_function('$o', 'return $o->id;'), $roles);
+        $existing = array();
+        $missing = array();
+
+        if ($this->id !== 0) {
+            $existing = array_keys($this->params);
+            $missing = array_diff($existing, $roleids);
+        }
+
+        if (!empty($missing)) {
+            $mform->addElement('header', 'category_errors', get_string('criterror', 'badges'));
+            $mform->addHelpButton('category_errors', 'criterror', 'badges');
+            foreach ($missing as $m) {
+                $this->config_options($mform, array('id' => $m, 'checked' => true, 'name' => get_string('error:nosuchrole', 'badges'), 'error' => true));
+                $none = false;
+            }
+        }
+
+        if (!empty($roleids)) {
+            $mform->addElement('header', 'first_header', $this->get_title());
+            $mform->addHelpButton('first_header', 'criteria_' . $this->criteriatype, 'badges');
+            foreach ($roleids as $rid) {
+                $checked = false;
+                if (in_array($rid, $existing)) {
+                    $checked = true;
+                }
+                $this->config_options($mform, array('id' => $rid, 'checked' => $checked, 'name' => self::get_role_name($rid), 'error' => false));
+                $none = false;
+            }
+        }
+
+        // Add aggregation.
+        if (!$none) {
+            $mform->addElement('header', 'aggregation', get_string('method', 'badges'));
+            $agg = array();
+            $agg[] =& $mform->createElement('radio', 'agg', '', get_string('allmethodmanual', 'badges'), 1);
+            $agg[] =& $mform->createElement('static', 'none_break', null, '<br/>');
+            $agg[] =& $mform->createElement('radio', 'agg', '', get_string('anymethodmanual', 'badges'), 2);
+            $mform->addGroup($agg, 'methodgr', '', array(' '), false);
+            if ($this->id !== 0) {
+                $mform->setDefault('agg', $this->method);
+            } else {
+                $mform->setDefault('agg', BADGE_CRITERIA_AGGREGATION_ANY);
+            }
+        }
+
+        return array($none, get_string('noparamstoadd', 'badges'));
+    }
+
+    /**
+     * Get criteria details for displaying to users
+     *
+     * @return string
+     */
+    public function get_details($short = '') {
+        global $OUTPUT;
+        $output = array();
+        foreach ($this->params as $p) {
+            $str = self::get_role_name($p['role']);
+            if (!$str) {
+                $output[] = $OUTPUT->error_text(get_string('error:nosuchrole', 'badges'));
+            } else {
+                $output[] = $str;
+            }
+        }
+
+        if ($short) {
+            return implode(', ', $output);
+        } else {
+            return html_writer::alist($output, array(), 'ul');
+        }
+    }
+
+    /**
+     * Review this criteria and decide if it has been completed
+     *
+     * @param int $userid User whose criteria completion needs to be reviewed.
+     * @return bool Whether criteria is complete
+     */
+    public function review($userid) {
+        global $DB;
+
+        $overall = false;
+        foreach ($this->params as $param) {
+            $crit = $DB->get_record('badge_manual_award', array('issuerrole' => $param['role'], 'recipientid' => $userid, 'badgeid' => $this->badgeid));
+            if ($this->method == BADGE_CRITERIA_AGGREGATION_ALL) {
+                if (!$crit) {
+                    return false;
+                } else {
+                    $overall = true;
+                    continue;
+                }
+            } else if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+                if (!$crit) {
+                    $overall = false;
+                    continue;
+                } else {
+                    return true;
+                }
+            }
+        }
+        return $overall;
+    }
+
+    /**
+     * Delete this criterion
+     *
+     */
+    public function delete() {
+        global $DB;
+
+        // Remove any records of manual award.
+        $DB->delete_records('badge_manual_award', array('badgeid' => $this->badgeid));
+
+        parent::delete();
+    }
+}
diff --git a/badges/criteria/award_criteria_overall.php b/badges/criteria/award_criteria_overall.php
new file mode 100644 (file)
index 0000000..f04e3a4
--- /dev/null
@@ -0,0 +1,145 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains the overall badge award criteria type
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Overall badge award criteria
+ *
+ */
+class award_criteria_overall extends award_criteria {
+
+    /* @var int Criteria [BADGE_CRITERIA_TYPE_OVERALL] */
+    public $criteriatype = BADGE_CRITERIA_TYPE_OVERALL;
+
+    /**
+     * Add appropriate form elements to the criteria form
+     *
+     * @param stdClass $data details of overall criterion
+     */
+    public function config_form_criteria($data) {
+        global $OUTPUT;
+        $prefix = 'criteria-' . $this->id;
+        if (count($data->criteria) > 2) {
+            echo $OUTPUT->box_start();
+            echo $OUTPUT->heading($this->get_title(), 2);
+
+            $agg = $data->get_aggregation_methods();
+            if (!$data->is_locked() && !$data->is_active()) {
+                $url = new moodle_url('criteria.php', array('id' => $data->id, 'sesskey' => sesskey()));
+                $table = new html_table();
+                $table->attributes = array('class' => 'clearfix');
+                $table->colclasses = array('', 'activatebadge');
+                $table->data[] = array(
+                        $OUTPUT->single_select($url, 'update', $agg, $data->get_aggregation_method($this->criteriatype), null),
+                        get_string('overallcrit', 'badges')
+                        );
+                echo html_writer::table($table);
+            } else {
+                echo $OUTPUT->box(get_string('criteria_descr_' . $this->criteriatype, 'badges',
+                        strtoupper($agg[$data->get_aggregation_method()])), 'clearfix');
+            }
+            echo $OUTPUT->box_end();
+        }
+    }
+
+    /**
+     * Add appropriate parameter elements to the criteria form
+     *
+     */
+    public function config_options(&$mform, $param) {
+    }
+
+    /**
+     * Get criteria details for displaying to users
+     *
+     * @return string
+     */
+    public function get_details($short = '') {
+    }
+
+    /**
+     * Review this criteria and decide if it has been completed
+     * Overall criteria review should be called only from other criteria handlers.
+     *
+     * @param int $userid User whose criteria completion needs to be reviewed.
+     * @return bool Whether criteria is complete
+     */
+    public function review($userid) {
+        global $DB;
+
+        $sql = "SELECT bc.*, bcm.critid, bcm.userid, bcm.datemet
+                FROM {badge_criteria} bc
+                LEFT JOIN {badge_criteria_met} bcm
+                    ON bc.id = bcm.critid AND bcm.userid = :userid
+                WHERE bc.badgeid = :badgeid
+                    AND bc.criteriatype != :criteriatype ";
+
+        $params = array(
+                    'userid' => $userid,
+                    'badgeid' => $this->badgeid,
+                    'criteriatype' => BADGE_CRITERIA_TYPE_OVERALL
+                );
+
+        $criteria = $DB->get_records_sql($sql, $params);
+        $overall = false;
+        foreach ($criteria as $crit) {
+            if ($this->method == BADGE_CRITERIA_AGGREGATION_ALL) {
+                if ($crit->datemet === null) {
+                    return false;
+                } else {
+                    $overall = true;
+                    continue;
+                }
+            } else if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+                if ($crit->datemet === null) {
+                    $overall = false;
+                    continue;
+                } else {
+                    return true;
+                }
+            }
+        }
+
+        return $overall;
+    }
+
+    /**
+     * Add appropriate criteria elements to the form
+     *
+     */
+    public function get_options(&$mform) {
+    }
+
+    /**
+     * Return criteria parameters
+     *
+     * @param int $critid Criterion ID
+     * @return array
+     */
+    public function get_params($cid) {
+    }
+}
\ No newline at end of file
diff --git a/badges/criteria/award_criteria_profile.php b/badges/criteria/award_criteria_profile.php
new file mode 100644 (file)
index 0000000..9940e90
--- /dev/null
@@ -0,0 +1,190 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains the profile completion badge award criteria type class
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+defined('MOODLE_INTERNAL') || die();
+require_once($CFG->dirroot . "/user/lib.php");
+
+/**
+ * Profile completion badge award criteria
+ *
+ */
+class award_criteria_profile extends award_criteria {
+
+    /* @var int Criteria [BADGE_CRITERIA_TYPE_PROFILE] */
+    public $criteriatype = BADGE_CRITERIA_TYPE_PROFILE;
+
+    public $required_param = 'field';
+    public $optional_params = array();
+
+    /**
+     * Add appropriate new criteria options to the form
+     *
+     */
+    public function get_options(&$mform) {
+        global $DB;
+
+        $none = true;
+        $existing = array();
+        $missing = array();
+
+        // Note: cannot use user_get_default_fields() here because it is not possible to decide which fields user can modify.
+        $dfields = array('firstname', 'lastname', 'email', 'address', 'phone1', 'phone2', 'icq', 'skype', 'yahoo',
+                         'aim', 'msn', 'department', 'institution', 'description', 'city', 'url', 'country');
+
+        $sql = "SELECT uf.id as fieldid, uf.name as name, ic.id as categoryid, ic.name as categoryname, uf.datatype
+                FROM {user_info_field} uf
+                JOIN {user_info_category} ic
+                ON uf.categoryid = ic.id AND uf.visible <> 0
+                ORDER BY ic.sortorder ASC, uf.sortorder ASC";
+
+        // Get custom fields.
+        $cfields = $DB->get_records_sql($sql);
+        $cfids = array_map(create_function('$o', 'return $o->fieldid;'), $cfields);
+
+        if ($this->id !== 0) {
+            $existing = array_keys($this->params);
+            $missing = array_diff($existing, array_merge($dfields, $cfids));
+        }
+
+        if (!empty($missing)) {
+            $mform->addElement('header', 'category_errors', get_string('criterror', 'badges'));
+            $mform->addHelpButton('category_errors', 'criterror', 'badges');
+            foreach ($missing as $m) {
+                $this->config_options($mform, array('id' => $m, 'checked' => true, 'name' => get_string('error:nosuchfield', 'badges'), 'error' => true));
+                $none = false;
+            }
+        }
+
+        if (!empty($dfields)) {
+            $mform->addElement('header', 'first_header', $this->get_title());
+            $mform->addHelpButton('first_header', 'criteria_' . $this->criteriatype, 'badges');
+            foreach ($dfields as $field) {
+                $checked = false;
+                if (in_array($field, $existing)) {
+                    $checked = true;
+                }
+                $this->config_options($mform, array('id' => $field, 'checked' => $checked, 'name' => get_user_field_name($field), 'error' => false));
+                $none = false;
+            }
+        }
+
+        if (!empty($cfields)) {
+            foreach ($cfields as $field) {
+                if (!isset($currentcat) || $currentcat != $field->categoryid) {
+                    $currentcat = $field->categoryid;
+                    $mform->addElement('header', 'category_' . $currentcat, format_string($field->categoryname));
+                }
+                $checked = false;
+                if (in_array($field->fieldid, $existing)) {
+                    $checked = true;
+                }
+                $this->config_options($mform, array('id' => $field->fieldid, 'checked' => $checked, 'name' => $field->name, 'error' => false));
+                $none = false;
+            }
+        }
+
+        // Add aggregation.
+        if (!$none) {
+            $mform->addElement('header', 'aggregation', get_string('method', 'badges'));
+            $agg = array();
+            $agg[] =& $mform->createElement('radio', 'agg', '', get_string('allmethodprofile', 'badges'), 1);
+            $agg[] =& $mform->createElement('static', 'none_break', null, '<br/>');
+            $agg[] =& $mform->createElement('radio', 'agg', '', get_string('anymethodprofile', 'badges'), 2);
+            $mform->addGroup($agg, 'methodgr', '', array(' '), false);
+            if ($this->id !== 0) {
+                $mform->setDefault('agg', $this->method);
+            } else {
+                $mform->setDefault('agg', BADGE_CRITERIA_AGGREGATION_ANY);
+            }
+        }
+
+        return array($none, get_string('noparamstoadd', 'badges'));
+    }
+
+    /**
+     * Get criteria details for displaying to users
+     *
+     * @return string
+     */
+    public function get_details($short = '') {
+        global $DB, $OUTPUT;
+        $output = array();
+        foreach ($this->params as $p) {
+            if (is_numeric($p['field'])) {
+                $str = $DB->get_field('user_info_field', 'name', array('id' => $p['field']));
+            } else {
+                $str = get_user_field_name($p['field']);
+            }
+            if (!$str) {
+                $output[] = $OUTPUT->error_text(get_string('error:nosuchfield', 'badges'));
+            } else {
+                $output[] = $str;
+            }
+        }
+
+        if ($short) {
+            return implode(', ', $output);
+        } else {
+            return html_writer::alist($output, array(), 'ul');
+        }
+    }
+
+    /**
+     * Review this criteria and decide if it has been completed
+     *
+     * @param int $userid User whose criteria completion needs to be reviewed.
+     * @return bool Whether criteria is complete
+     */
+    public function review($userid) {
+        global $DB;
+
+        $overall = false;
+        foreach ($this->params as $param) {
+            if (is_numeric($param['field'])) {
+                $crit = $DB->get_field('user_info_data', 'data', array('userid' => $userid, 'fieldid' => $param['field']));
+            } else {
+                $crit = $DB->get_field('user', $param['field'], array('id' => $userid));
+            }
+
+            if ($this->method == BADGE_CRITERIA_AGGREGATION_ALL) {
+                if (!$crit) {
+                    return false;
+                } else {
+                    $overall = true;
+                    continue;
+                }
+            } else if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+                if (!$crit) {
+                    $overall = false;
+                    continue;
+                } else {
+                    return true;
+                }
+            }
+        }
+        return $overall;
+    }
+}
diff --git a/badges/criteria_action.php b/badges/criteria_action.php
new file mode 100644 (file)
index 0000000..74ff62c
--- /dev/null
@@ -0,0 +1,86 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Processing actions with badge criteria.
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+require_once(dirname(dirname(__FILE__)) . '/config.php');
+require_once($CFG->libdir . '/badgeslib.php');
+
+$badgeid = optional_param('badgeid', 0, PARAM_INT); // Badge ID.
+$crit    = optional_param('crit', 0, PARAM_INT);
+$type    = optional_param('type', 0, PARAM_INT); // Criteria type.
+$delete  = optional_param('delete', 0, PARAM_BOOL);
+$confirm = optional_param('confirm', 0, PARAM_BOOL);
+
+require_login();
+
+$return = new moodle_url('/badges/criteria.php', array('id' => $badgeid));
+$badge = new badge($badgeid);
+$context = $badge->get_context();
+$navurl = new moodle_url('/badges/index.php', array('type' => $badge->type));
+
+// Make sure that no actions available for locked or active badges.
+if ($badge->is_active() || $badge->is_locked()) {
+    redirect($return);
+}
+
+if ($badge->type == BADGE_TYPE_COURSE) {
+    require_login($badge->courseid);
+    $navurl = new moodle_url('/badges/index.php', array('type' => $badge->type, 'id' => $badge->courseid));
+}
+
+$PAGE->set_context($context);
+$PAGE->set_url('/badges/criteria_action.php');
+$PAGE->set_pagelayout('standard');
+$PAGE->set_heading($badge->name);
+$PAGE->set_title($badge->name);
+navigation_node::override_active_url($navurl);
+
+if ($delete && has_capability('moodle/badges:configurecriteria', $context)) {
+    if (!$confirm) {
+        $optionsyes = array('confirm' => 1, 'sesskey' => sesskey(), 'badgeid' => $badgeid, 'delete' => true, 'type' => $type);
+
+        $strdeletecheckfull = get_string('delcritconfirm', 'badges');
+
+        echo $OUTPUT->header();
+        $formcontinue = new single_button(new moodle_url('/badges/criteria_action.php', $optionsyes), get_string('yes'));
+        $formcancel = new single_button($return, get_string('no'), 'get');
+        echo $OUTPUT->confirm($strdeletecheckfull, $formcontinue, $formcancel);
+        echo $OUTPUT->footer();
+
+        die();
+    }
+
+    require_sesskey();
+    if (count($badge->criteria) == 2) {
+        // Remove overall criterion as well.
+        $badge->criteria[$type]->delete();
+        $badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->delete();
+    } else {
+        $badge->criteria[$type]->delete();
+    }
+    redirect($return);
+}
+
+redirect($return);
\ No newline at end of file
diff --git a/badges/criteria_form.php b/badges/criteria_form.php
new file mode 100644 (file)
index 0000000..65cc718
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Form classes for editing badges criteria
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir . '/formslib.php');
+require_once($CFG->libdir . '/badgeslib.php');
+
+/**
+ * Form to edit badge criteria.
+ *
+ */
+class edit_criteria_form extends moodleform {
+    public function definition() {
+        global $DB;
+        $mform = $this->_form;
+        $criteria = $this->_customdata['criteria'];
+        $addcourse = $this->_customdata['addcourse'];
+
+        // Get course selector first if it's a new courseset criteria.
+        if (($criteria->id == 0 || $addcourse) && $criteria->criteriatype == BADGE_CRITERIA_TYPE_COURSESET) {
+            $criteria->get_courses($mform);
+        } else {
+            list($none, $message) = $criteria->get_options($mform);
+
+            if ($none) {
+                $mform->addElement('html', html_writer::tag('div', $message));
+                $mform->addElement('submit', 'cancel', get_string('continue'));
+            } else {
+                $mform->closeHeaderBefore('buttonar');
+                $this->add_action_buttons(true, get_string('save', 'badges'));
+            }
+        }
+    }
+
+    /**
+     * Validates form data
+     */
+    public function validation($data, $files) {
+        global $OUTPUT;
+        $errors = parent::validation($data, $files);
+        $addcourse = $this->_customdata['addcourse'];
+
+        if (!$addcourse) {
+            $required = $this->_customdata['criteria']->required_param;
+            $pattern1 = '/^' . $required . '_(\d+)$/';
+            $pattern2 = '/^' . $required . '_(\w+)$/';
+
+            $ok = false;
+            foreach ($data as $key => $value) {
+                if ((preg_match($pattern1, $key) || preg_match($pattern2, $key)) && !($value === 0 || $value == '0')) {
+                    $ok = true;
+                }
+            }
+
+            $warning = $this->_form->createElement('html',
+                    $OUTPUT->notification(get_string('error:parameter', 'badges'), 'notifyproblem'), 'submissionerror');
+
+            if (!$ok) {
+                $errors['formerrors'] = 'Error';
+                $this->_form->insertElementBefore($warning, 'first_header');
+            }
+        }
+        return $errors;
+    }
+}
\ No newline at end of file
diff --git a/badges/criteria_settings.php b/badges/criteria_settings.php
new file mode 100644 (file)
index 0000000..943ecb0
--- /dev/null
@@ -0,0 +1,105 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Page for editing badges criteria settings.
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2013 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+require_once(dirname(dirname(__FILE__)) . '/config.php');
+require_once($CFG->libdir . '/badgeslib.php');
+require_once($CFG->dirroot . '/badges/criteria_form.php');
+
+$badgeid = optional_param('badgeid', 0, PARAM_INT); // Badge ID.
+$type    = optional_param('type', 0, PARAM_INT); // Criteria type.
+$edit    = optional_param('edit', 0, PARAM_INT); // Edit criteria ID.
+$crit    = optional_param('crit', 0, PARAM_INT); // Criteria ID for managing params.
+$param   = optional_param('param', '', PARAM_TEXT); // Param name for managing params.
+$goback    = optional_param('cancel', '', PARAM_TEXT);
+$addcourse = optional_param('addcourse', '', PARAM_TEXT);
+$submitcourse = optional_param('submitcourse', '', PARAM_TEXT);
+
+require_login();
+
+$return = new moodle_url('/badges/criteria.php', array('id' => $badgeid));
+$badge = new badge($badgeid);
+$context = $badge->get_context();
+$navurl = new moodle_url('/badges/index.php', array('type' => $badge->type));
+
+require_capability('moodle/badges:configurecriteria', $context);
+
+if (!empty($goback)) {
+    redirect($return);
+}
+
+// Make sure that no actions available for locked or active badges.
+if ($badge->is_active() || $badge->is_locked()) {
+    redirect($return);
+}
+
+if ($badge->type == BADGE_TYPE_COURSE) {
+    require_login($badge->courseid);
+    $navurl = new moodle_url('/badges/index.php', array('type' => $badge->type, 'id' => $badge->courseid));
+}
+
+$PAGE->set_context($context);
+$PAGE->set_url('/badges/criteria_settings.php');
+$PAGE->set_pagelayout('standard');
+$PAGE->set_heading($badge->name);
+$PAGE->set_title($badge->name);
+navigation_node::override_active_url($navurl);
+$PAGE->navbar->add($badge->name, new moodle_url('overview.php', array('id' => $badge->id)))->add(get_string('criteria_' . $type, 'badges'));
+
+$cparams = array('criteriatype' => $type, 'badgeid' => $badge->id);
+if ($edit) {
+    $criteria = $badge->criteria[$type];
+} else {
+    $criteria = award_criteria::build($cparams);
+}
+
+$mform = new edit_criteria_form($FULLME, array('criteria' => $criteria, 'addcourse' => $addcourse));
+
+if (!empty($addcourse)) {
+    if ($data = $mform->get_data()) {
+        // If no criteria yet, add overall aggregation.
+        if (count($badge->criteria) == 0) {
+            $criteria_overall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_OVERALL, 'badgeid' => $badge->id));
+            $criteria_overall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ALL));
+        }
+
+        $id = $criteria->add_courses($data->courses);
+        redirect(new moodle_url('/badges/criteria_settings.php',
+            array('badgeid' => $badgeid, 'edit' => true, 'type' => BADGE_CRITERIA_TYPE_COURSESET, 'crit' => $id)));
+    }
+} else if ($data = $mform->get_data()) {
+    // If no criteria yet, add overall aggregation.
+    if (count($badge->criteria) == 0) {
+        $criteria_overall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_OVERALL, 'badgeid' => $badge->id));
+        $criteria_overall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ALL));
+    }
+    $criteria->save($data);
+    $return->param('msg', get_string('changessaved'));
+    redirect($return);
+}
+
+echo $OUTPUT->header();
+$mform->display();
+echo $OUTPUT->footer();
\ No newline at end of file
diff --git a/badges/cron.php b/badges/cron.php
new file mode 100644 (file)
index 0000000..833ee35
--- /dev/null
@@ -0,0 +1,162 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Cron job for reviewing and aggregating badge award criteria
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+defined('MOODLE_INTERNAL') || die();
+require_once($CFG->libdir . '/badgeslib.php');
+
+function badge_cron() {
+    global $CFG;
+
+    if ($CFG->enablebadges) {
+        badge_review_cron();
+        badge_message_cron();
+    }
+}
+
+/**
+ * Reviews criteria and awards badges
+ *
+ * First find all badges that can be earned, then reviews each badge.
+ * (Not sure how efficient this is timewise).
+ */
+function badge_review_cron() {
+    global $DB, $CFG;
+    $total = 0;
+
+    $courseparams = array();
+    if (empty($CFG->badges_allowcoursebadges)) {
+        $coursesql = '';
+    } else {
+        $coursesql = ' OR EXISTS (SELECT id FROM {course} WHERE visible = :visible AND startdate < :current) ';
+        $courseparams = array('visible' => true, 'current' => time());
+    }
+
+    $sql = 'SELECT id
+                FROM {badge}
+                WHERE (status = :active OR status = :activelocked)
+                    AND (type = :site ' . $coursesql . ')';
+    $badgeparams = array(
+                    'active' => BADGE_STATUS_ACTIVE,
+                    'activelocked' => BADGE_STATUS_ACTIVE_LOCKED,
+                    'site' => BADGE_TYPE_SITE
+                    );
+    $params = array_merge($badgeparams, $courseparams);
+    $badges = $DB->get_fieldset_sql($sql, $params);
+
+    mtrace('Started reviewing available badges.');
+    foreach ($badges as $bid) {
+        $badge = new badge($bid);
+
+        if ($badge->has_criteria()) {
+            if (debugging()) {
+                mtrace('Processing badge "' . $badge->name . '"...');
+            }
+
+            $issued = $badge->review_all_criteria();
+
+            if (debugging()) {
+                mtrace('...badge was issued to ' . $issued . ' users.');
+            }
+            $total += $issued;
+        }
+    }
+
+    mtrace('Badges were issued ' . $total . ' time(s).');
+}
+
+/**
+ * Sends out scheduled messages to badge creators
+ *
+ */
+function badge_message_cron() {
+    global $DB;
+
+    mtrace('Sending scheduled badge notifications.');
+
+    $scheduled = $DB->get_records_select('badge', 'notification > ? AND (status != ?) AND nextcron < ?',
+                            array(BADGE_MESSAGE_ALWAYS, BADGE_STATUS_ARCHIVED, time()),
+                            'notification ASC', 'id, name, notification, usercreated as creator, timecreated');
+
+    foreach ($scheduled as $sch) {
+        // Send messages.
+        badge_assemble_notification($sch);
+
+        // Update next cron value.
+        $nextcron = badges_calculate_message_schedule($sch->notification);
+        $DB->set_field('badge', 'nextcron', $nextcron, array('id' => $sch->id));
+    }
+}
+
+/**
+ * Creates single message for all notification and sends it out
+ *
+ * @param object $badge A badge which is notified about.
+ */
+function badge_assemble_notification(stdClass $badge) {
+    global $CFG, $DB;
+
+    $admin = get_admin();
+    $userfrom = new stdClass();
+    $userfrom->id = $admin->id;
+    $userfrom->email = !empty($CFG->badges_defaultissuercontact) ? $CFG->badges_defaultissuercontact : $admin->email;
+    $userfrom->firstname = !empty($CFG->badges_defaultissuername) ? $CFG->badges_defaultissuername : $admin->firstname;
+    $userfrom->lastname = !empty($CFG->badges_defaultissuername) ? '' : $admin->lastname;
+    $userfrom->maildisplay = true;
+
+    if ($msgs = $DB->get_records_select('badge_issued', 'issuernotified IS NULL AND badgeid = ?', array($badge->id))) {
+        // Get badge creator.
+        $creator = $DB->get_record('user', array('id' => $badge->creator), '*', MUST_EXIST);
+        $creatorsubject = get_string('creatorsubject', 'badges', $badge->name);
+        $creatormessage = '';
+
+        // Put all messages in one digest.
+        foreach ($msgs as $msg) {
+            $issuedlink = html_writer::link(new moodle_url('/badges/badge.php', array('hash' => $msg->uniquehash)), $badge->name);
+            $recipient = $DB->get_record('user', array('id' => $msg->userid), '*', MUST_EXIST);
+
+            $a = new stdClass();
+            $a->user = fullname($recipient);
+            $a->link = $issuedlink;
+            $creatormessage .= get_string('creatorbody', 'badges', $a);
+            $DB->set_field('badge_issued', 'issuernotified', time(), array('badgeid' => $msg->badgeid, 'userid' => $msg->userid));
+        }
+
+        // Create a message object.
+        $eventdata = new stdClass();
+        $eventdata->component         = 'moodle';
+        $eventdata->name              = 'instantmessage';
+        $eventdata->userfrom          = $userfrom;
+        $eventdata->userto            = $creator;
+        $eventdata->notification      = 1;
+        $eventdata->subject           = $creatorsubject;
+        $eventdata->fullmessage       = $creatormessage;
+        $eventdata->fullmessageformat = FORMAT_PLAIN;
+        $eventdata->fullmessagehtml   = format_text($creatormessage, FORMAT_HTML);
+        $eventdata->smallmessage      = '';
+
+        message_send($eventdata);
+    }
+}
\ No newline at end of file
diff --git a/badges/edit.php b/badges/edit.php
new file mode 100644 (file)
index 0000000..9093040
--- /dev/null
@@ -0,0 +1,146 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Editing badge details, criteria, messages
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+require_once(dirname(dirname(__FILE__)) . '/config.php');
+require_once($CFG->libdir . '/badgeslib.php');
+require_once($CFG->dirroot . '/badges/edit_form.php');
+
+$badgeid = required_param('id', PARAM_INT);
+$action = optional_param('action', 'details', PARAM_TEXT);
+
+require_login();
+
+if (empty($CFG->enablebadges)) {
+    print_error('badgesdisabled', 'badges');
+}
+
+$badge = new badge($badgeid);
+$context = $badge->get_context();
+$navurl = new moodle_url('/badges/index.php', array('type' => $badge->type));
+
+require_capability('moodle/badges:configuredetails', $context);
+
+if ($badge->type == BADGE_TYPE_COURSE) {
+    require_login($badge->courseid);
+    $navurl = new moodle_url('/badges/index.php', array('type' => $badge->type, 'id' => $badge->courseid));
+}
+
+$currenturl = new moodle_url('/badges/edit.php', array('id' => $badge->id, 'action' => $action));
+
+$PAGE->set_context($context);
+$PAGE->set_url($currenturl);
+$PAGE->set_pagelayout('standard');
+$PAGE->set_heading($badge->name);
+$PAGE->set_title($badge->name);
+
+// Set up navigation and breadcrumbs.
+navigation_node::override_active_url($navurl);
+$PAGE->navbar->add($badge->name);
+
+$output = $PAGE->get_renderer('core', 'badges');
+$statusmsg = '';
+$errormsg  = '';
+
+$badge->message = clean_text($badge->message, FORMAT_HTML);
+$editoroptions = array(
+        'subdirs' => 0,
+        'maxbytes' => 0,
+        'maxfiles' => 0,
+        'changeformat' => 0,
+        'context' => $context,
+        'noclean' => false,
+        'trusttext' => false
+        );
+$badge = file_prepare_standard_editor($badge, 'message', $editoroptions, $context);
+
+$form_class = 'edit_' . $action . '_form';
+$form = new $form_class($currenturl, array('badge' => $badge, 'action' => $action, 'editoroptions' => $editoroptions));
+
+if ($form->is_cancelled()) {
+    redirect(new moodle_url('/badges/overview.php', array('id' => $badgeid)));
+} else if ($form->is_submitted() && $form->is_validated() && ($data = $form->get_data())) {
+    if ($action == 'details') {
+        $badge->name = $data->name;
+        $badge->description = $data->description;
+        $badge->usermodified = $USER->id;
+        $badge->issuername = $data->issuername;
+        $badge->issuerurl = $data->issuerurl;
+        $badge->issuercontact = $data->issuercontact;
+        $badge->expiredate = ($data->expiry == 1) ? $data->expiredate : null;
+        $badge->expireperiod = ($data->expiry == 2) ? $data->expireperiod : null;
+
+        // Need to unset message_editor options to avoid errors on form edit.
+        unset($badge->messageformat);
+        unset($badge->message_editor);
+
+        if ($badge->save()) {
+            badges_process_badge_image($badge, $form->save_temp_file('image'));
+            $form->set_data($badge);
+            $statusmsg = get_string('changessaved');
+        } else {
+            $errormsg = get_string('error:save', 'badges');
+        }
+    } else if ($action == 'message') {
+        // Calculate next message cron if form data is different from original badge data.
+        if ($data->notification != $badge->notification) {
+            if ($data->notification > BADGE_MESSAGE_ALWAYS) {
+                $badge->nextcron = badges_calculate_message_schedule($data->notification);
+            } else {
+                $badge->nextcron = null;
+            }
+        }
+
+        $badge->message = clean_text($data->message_editor['text'], FORMAT_HTML);
+        $badge->messagesubject = $data->messagesubject;
+        $badge->notification = $data->notification;
+        $badge->attachment = $data->attachment;
+
+        unset($badge->messageformat);
+        unset($badge->message_editor);
+        if ($badge->save()) {
+            $statusmsg = get_string('changessaved');
+        } else {
+            $errormsg = get_string('error:save', 'badges');
+        }
+    }
+}
+
+echo $OUTPUT->header();
+echo $OUTPUT->heading(print_badge_image($badge, $context, 'small') . ' ' . $badge->name);
+
+if ($errormsg !== '') {
+    echo $OUTPUT->notification($errormsg);
+
+} else if ($statusmsg !== '') {
+    echo $OUTPUT->notification($statusmsg, 'notifysuccess');
+}
+
+echo $output->print_badge_status_box($badge);
+$output->print_badge_tabs($badgeid, $context, $action);
+
+$form->display();
+
+echo $OUTPUT->footer();
\ No newline at end of file
diff --git a/badges/edit_form.php b/badges/edit_form.php
new file mode 100644 (file)
index 0000000..4011e05
--- /dev/null
@@ -0,0 +1,241 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Form classes for editing badges
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir . '/formslib.php');
+require_once($CFG->libdir . '/badgeslib.php');
+require_once($CFG->libdir . '/filelib.php');
+
+/**
+ * Form to edit badge details.
+ *
+ */
+class edit_details_form extends moodleform {
+
+    /**
+     * Defines the form
+     */
+    public function definition() {
+        global $CFG;
+
+        $mform = $this->_form;
+        $badge = (isset($this->_customdata['badge'])) ? $this->_customdata['badge'] : false;
+        $action = $this->_customdata['action'];
+
+        $mform->addElement('header', 'badgedetails', get_string('badgedetails', 'badges'));
+        $mform->addElement('text', 'name', get_string('name'), array('size' => '70'));
+        // Using PARAM_FILE to avoid problems later when downloading badge files.
+        $mform->setType('name', PARAM_FILE);
+        $mform->addRule('name', null, 'required');
+        $mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
+
+        $mform->addElement('textarea', 'description', get_string('description', 'badges'), 'wrap="virtual" rows="8" cols="70"');
+        $mform->setType('description', PARAM_CLEANHTML);
+        $mform->addRule('description', null, 'required');
+
+        $str = $action == 'new' ? get_string('badgeimage', 'badges') : get_string('newimage', 'badges');
+        $imageoptions = array('maxbytes' => 262144, 'accepted_types' => array('web_image'));
+        $mform->addElement('filepicker', 'image', $str, null, $imageoptions);
+
+        if ($action == 'new') {
+            $mform->addRule('image', null, 'required');
+        } else {
+            $currentimage = $mform->createElement('static', 'currentimage', get_string('currentimage', 'badges'));
+            $mform->insertElementBefore($currentimage, 'image');
+        }
+        $mform->addHelpButton('image', 'badgeimage', 'badges');
+
+        $mform->addElement('header', 'issuerdetails', get_string('issuerdetails', 'badges'));
+
+        $mform->addElement('text', 'issuername', get_string('name'), array('size' => '70'));
+        $mform->setType('issuername', PARAM_NOTAGS);
+        $mform->addRule('issuername', null, 'required');
+        $mform->setDefault('issuername', $CFG->badges_defaultissuername);
+        $mform->addHelpButton('issuername', 'issuername', 'badges');
+
+        $mform->addElement('text', 'issuercontact', get_string('contact', 'badges'), array('size' => '70'));
+        $mform->setDefault('issuercontact', $CFG->badges_defaultissuercontact);
+        $mform->setType('issuercontact', PARAM_EMAIL);
+        $mform->addRule('issuercontact', get_string('invalidemail', 'moodle'), 'email', null, 'client', true);
+        $mform->addHelpButton('issuercontact', 'contact', 'badges');
+
+        $mform->addElement('header', 'issuancedetails', get_string('issuancedetails', 'badges'));
+
+        $issuancedetails = array();
+        $issuancedetails[] =& $mform->createElement('radio', 'expiry', '', get_string('never', 'badges'), 0);
+        $issuancedetails[] =& $mform->createElement('static', 'none_break', null, '<br/>');
+        $issuancedetails[] =& $mform->createElement('radio', 'expiry', '', get_string('fixed', 'badges'), 1);
+        $issuancedetails[] =& $mform->createElement('date_selector', 'expiredate', '');
+        $issuancedetails[] =& $mform->createElement('static', 'expirydate_break', null, '<br/>');
+        $issuancedetails[] =& $mform->createElement('radio', 'expiry', '', get_string('relative', 'badges'), 2);
+        $issuancedetails[] =& $mform->createElement('duration', 'expireperiod', '', array('defaultunit' => 86400, 'optional' => false));
+        $issuancedetails[] =& $mform->createElement('static', 'expiryperiods_break', null, get_string('after', 'badges'));
+
+        $mform->addGroup($issuancedetails, 'expirydategr', get_string('expirydate', 'badges'), array(' '), false);
+        $mform->addHelpButton('expirydategr', 'expirydate', 'badges');
+        $mform->setDefault('expiry', 0);
+        $mform->setDefault('expiredate', strtotime('+1 year'));
+        $mform->disabledIf('expiredate[day]', 'expiry', 'neq', 1);
+        $mform->disabledIf('expiredate[month]', 'expiry', 'neq', 1);
+        $mform->disabledIf('expiredate[year]', 'expiry', 'neq', 1);
+        $mform->disabledIf('expireperiod[number]', 'expiry', 'neq', 2);
+        $mform->disabledIf('expireperiod[timeunit]', 'expiry', 'neq', 2);
+
+        // Set issuer URL.
+        // Have to parse URL because badge issuer origin cannot be a subfolder in wwwroot.
+        $url = parse_url($CFG->wwwroot);
+        $mform->addElement('hidden', 'issuerurl', $url['scheme'] . '://' . $url['host']);
+        $mform->setType('issuerurl', PARAM_URL);
+
+        $mform->addElement('hidden', 'action', $action);
+        $mform->setType('action', PARAM_TEXT);
+
+        if ($action == 'new') {
+            $this->add_action_buttons(true, get_string('createbutton', 'badges'));
+        } else {
+            // Add hidden fields.
+            $mform->addElement('hidden', 'id', $badge->id);
+            $mform->setType('id', PARAM_INT);
+
+            $this->add_action_buttons();
+            $this->set_data($badge);
+
+            // Freeze all elements if badge is active or locked.
+            if ($badge->is_active() || $badge->is_locked()) {
+                $mform->hardFreezeAllVisibleExcept(array());
+            }
+        }
+    }
+
+    /**
+     * Load in existing data as form defaults
+     *
+     * @param stdClass|array $default_values object or array of default values
+     */
+    public function set_data($badge) {
+        $default_values = array();
+        parent::set_data($badge);
+
+        if (!empty($badge->expiredate)) {
+            $default_values['expiry'] = 1;
+            $default_values['expiredate'] = $badge->expiredate;
+        } else if (!empty($badge->expireperiod)) {
+            $default_values['expiry'] = 2;
+            $default_values['expireperiod'] = $badge->expireperiod;
+        }
+        $default_values['currentimage'] = print_badge_image($badge, $badge->get_context(), 'large');
+
+        parent::set_data($default_values);
+    }
+
+    /**
+     * Validates form data
+     */
+    public function validation($data, $files) {
+        global $DB;
+        $errors = parent::validation($data, $files);
+
+        if ($data['expiry'] == 2 && $data['expireperiod'] <= 0) {
+            $errors['expirydategr'] = get_string('error:invalidexpireperiod', 'badges');
+        }
+
+        if ($data['expiry'] == 1 && $data['expiredate'] <= time()) {
+            $errors['expirydategr'] = get_string('error:invalidexpiredate', 'badges');
+        }
+
+        // Check for duplicate badge names.
+        if ($data['action'] == 'new') {
+            $duplicate = $DB->record_exists_select('badge', 'name = :name',
+                        array('name' => $data['name']));
+        } else {
+            $duplicate = $DB->record_exists_select('badge', 'name = :name AND id != :badgeid',
+                    array('name' => $data['name'], 'badgeid' => $data['id']));
+        }
+
+        if ($duplicate) {
+            $errors['name'] = get_string('error:duplicatename', 'badges');
+        }
+
+        return $errors;
+    }
+}
+
+/**
+ * Form to edit badge message.
+ *
+ */
+class edit_message_form extends moodleform {
+    public function definition() {
+        global $CFG, $OUTPUT;
+
+        $mform = $this->_form;
+        $badge = $this->_customdata['badge'];
+        $action = $this->_customdata['action'];
+        $editoroptions = $this->_customdata['editoroptions'];
+
+        // Add hidden fields.
+        $mform->addElement('hidden', 'id', $badge->id);
+        $mform->setType('id', PARAM_INT);
+
+        $mform->addElement('hidden', 'action', $action);
+        $mform->setType('action', PARAM_TEXT);
+
+        $mform->addElement('header', 'badgemessage', get_string('configuremessage', 'badges'));
+        $mform->addHelpButton('badgemessage', 'variablesubstitution', 'badges');
+
+        $mform->addElement('text', 'messagesubject', get_string('subject', 'badges'), array('size' => '70'));
+        $mform->setType('messagesubject', PARAM_TEXT);
+        $mform->addRule('messagesubject', null, 'required');
+        $mform->addRule('messagesubject', get_string('maximumchars', '', 255), 'maxlength', 255);
+
+        $mform->addElement('editor', 'message_editor', get_string('message', 'badges'), null, $editoroptions);
+        $mform->setType('message_editor', PARAM_RAW);
+        $mform->addRule('message_editor', null, 'required');
+
+        $mform->addElement('advcheckbox', 'attachment', get_string('attachment', 'badges'), '', null, array(0, 1));
+        $mform->addHelpButton('attachment', 'attachment', 'badges');
+
+        $options = array(
+                BADGE_MESSAGE_NEVER   => get_string('never'),
+                BADGE_MESSAGE_ALWAYS  => get_string('notifyevery', 'badges'),
+                BADGE_MESSAGE_DAILY   => get_string('notifydaily', 'badges'),
+                BADGE_MESSAGE_WEEKLY  => get_string('notifyweekly', 'badges'),
+                BADGE_MESSAGE_MONTHLY => get_string('notifymonthly', 'badges'),
+                );
+        $mform->addElement('select', 'notification', get_string('notification', 'badges'), $options);
+        $mform->addHelpButton('notification', 'notification', 'badges');
+
+        $this->add_action_buttons();
+        $this->set_data($badge);
+    }
+
+    public function validation($data, $files) {
+        $errors = parent::validation($data, $files);
+
+        return $errors;
+    }
+}
\ No newline at end of file
diff --git a/badges/external.php b/badges/external.php
new file mode 100644 (file)
index 0000000..5436f01
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Display details of an issued badge with criteria and evidence
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+require_once(dirname(dirname(__FILE__)) . '/config.php');
+require_once($CFG->libdir . '/badgeslib.php');
+
+$json = required_param('badge', PARAM_RAW);
+
+$PAGE->set_context(context_system::instance());
+$output = $PAGE->get_renderer('core', 'badges');
+
+$badge = new external_badge(unserialize($json));
+
+$PAGE->set_url('/badges/external.php');
+$PAGE->set_pagelayout('base');
+$PAGE->set_title(get_string('issuedbadge', 'badges'));
+
+echo $OUTPUT->header();
+
+echo $output->render($badge);
+
+echo $OUTPUT->footer();
diff --git a/badges/index.php b/badges/index.php
new file mode 100644 (file)
index 0000000..d7f40b4
--- /dev/null
@@ -0,0 +1,186 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Page for badges management
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+require_once(dirname(dirname(__FILE__)) . '/config.php');
+require_once($CFG->libdir . '/badgeslib.php');
+
+$type       = required_param('type', PARAM_INT);
+$courseid   = optional_param('id', 0, PARAM_INT);
+$page       = optional_param('page', 0, PARAM_INT);
+$activate   = optional_param('activate', 0, PARAM_INT);
+$deactivate = optional_param('lock', 0, PARAM_INT);
+$sortby     = optional_param('sort', 'name', PARAM_ALPHA);
+$sorthow    = optional_param('dir', 'ASC', PARAM_ALPHA);
+$confirm    = optional_param('confirm', false, PARAM_BOOL);
+$delete     = optional_param('delete', 0, PARAM_INT);
+$msg        = optional_param('msg', '', PARAM_TEXT);
+
+if (!in_array($sortby, array('name', 'status'))) {
+    $sortby = 'name';
+}
+
+if ($sorthow != 'ASC' and $sorthow != 'DESC') {
+    $sorthow = 'ASC';
+}
+
+if ($page < 0) {
+    $page = 0;
+}
+
+require_login();
+
+if (empty($CFG->enablebadges)) {
+    print_error('badgesdisabled', 'badges');
+}
+
+$err = '';
+$urlparams = array('sort' => $sortby, 'dir' => $sorthow, 'page' => $page);
+
+if ($course = $DB->get_record('course', array('id' => $courseid))) {
+    $urlparams['type'] = $type;
+    $urlparams['id'] = $course->id;
+} else {
+    $urlparams['type'] = $type;
+}
+
+$hdr = get_string('managebadges', 'badges');
+$returnurl = new moodle_url('/badges/index.php', $urlparams);
+$PAGE->set_url($returnurl);
+
+if ($type == BADGE_TYPE_SITE) {
+    $title = get_string('sitebadges', 'badges');
+    $PAGE->set_context(context_system::instance());
+    $PAGE->set_pagelayout('admin');
+    $PAGE->set_heading($title . ': ' . $hdr);
+    navigation_node::override_active_url(new moodle_url('/badges/index.php', array('type' => BADGE_TYPE_SITE)));
+} else {
+    require_login($course);
+    $title = get_string('coursebadges', 'badges');
+    $PAGE->set_context(context_course::instance($course->id));
+    $PAGE->set_pagelayout('course');
+    $PAGE->set_heading($course->fullname . ': ' . $hdr);
+    navigation_node::override_active_url(
+        new moodle_url('/badges/index.php', array('type' => BADGE_TYPE_COURSE, 'id' => $course->id))
+    );
+}
+
+if (!has_capability('moodle/badges:awardbadge', $PAGE->context)) {
+    redirect($CFG->wwwroot);
+}
+
+$PAGE->set_title($hdr);
+$PAGE->requires->js('/badges/backpack.js');
+$PAGE->requires->js_init_call('check_site_access', null, false);
+$output = $PAGE->get_renderer('core', 'badges');
+
+if ($delete && has_capability('moodle/badges:deletebadge', $PAGE->context)) {
+    $badge = new badge($delete);
+    if (!$confirm) {
+        echo $output->header();
+        echo $output->confirm(
+                    get_string('delconfirm', 'badges', $badge->name),
+                    new moodle_url($PAGE->url, array('delete' => $badge->id, 'confirm' => 1)),
+                    $returnurl
+                );
+        echo $output->footer();
+        die();
+    } else {
+        require_sesskey();
+        $badge->delete();
+        redirect($returnurl);
+    }
+}
+
+if ($activate && has_capability('moodle/badges:configuredetails', $PAGE->context)) {
+    $badge = new badge($activate);
+
+    if (!$badge->has_criteria()) {
+        $err = get_string('error:cannotact', 'badges') . get_string('nocriteria', 'badges');
+    } else {
+        if ($badge->is_locked()) {
+            $badge->set_status(BADGE_STATUS_ACTIVE_LOCKED);
+            $msg = get_string('activatesuccess', 'badges');
+        } else {
+            $badge->set_status(BADGE_STATUS_ACTIVE);
+            $msg = get_string('activatesuccess', 'badges');
+        }
+        $returnurl->param('msg', $msg);
+        redirect($returnurl);
+    }
+} else if ($deactivate && has_capability('moodle/badges:configuredetails', $PAGE->context)) {
+    $badge = new badge($deactivate);
+    if ($badge->is_locked()) {
+        $badge->set_status(BADGE_STATUS_INACTIVE_LOCKED);
+        $msg = get_string('deactivatesuccess', 'badges');
+    } else {
+        $badge->set_status(BADGE_STATUS_INACTIVE);
+        $msg = get_string('deactivatesuccess', 'badges');
+    }
+    $returnurl->param('msg', $msg);
+    redirect($returnurl);
+}
+
+echo $OUTPUT->header();
+if ($type == BADGE_TYPE_SITE) {
+    echo $OUTPUT->heading_with_help($PAGE->heading, 'sitebadges', 'badges');
+} else {
+    echo $OUTPUT->heading($PAGE->heading);
+}
+echo $OUTPUT->box('', 'notifyproblem hide', 'check_connection');
+
+$totalcount = count(badges_get_badges($type, $courseid, '', '' , '', ''));
+$records = badges_get_badges($type, $courseid, $sortby, $sorthow, $page, BADGE_PERPAGE);
+
+if ($totalcount) {
+    echo $output->heading(get_string('badgestoearn', 'badges', $totalcount), 4);
+
+    if ($course && $course->startdate > time()) {
+        echo $OUTPUT->box(get_string('error:notifycoursedate', 'badges'), 'generalbox notifyproblem');
+    }
+
+    if ($err !== '') {
+        echo $OUTPUT->notification($err, 'notifyproblem');
+    }
+
+    if ($msg !== '') {
+        echo $OUTPUT->notification($msg, 'notifysuccess');
+    }
+
+    $badges             = new badge_management($records);
+    $badges->sort       = $sortby;
+    $badges->dir        = $sorthow;
+    $badges->page       = $page;
+    $badges->perpage    = BADGE_PERPAGE;
+    $badges->totalcount = $totalcount;
+
+    echo $output->render($badges);
+} else {
+    echo $output->notification(get_string('nobadges', 'badges'));
+    echo $OUTPUT->single_button(new moodle_url('newbadge.php', array('type' => $type, 'id' => $courseid)),
+            get_string('newbadge', 'badges'));
+}
+
+echo $OUTPUT->footer();
diff --git a/badges/lib/awardlib.php b/badges/lib/awardlib.php
new file mode 100644 (file)
index 0000000..30987e0
--- /dev/null
@@ -0,0 +1,242 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Classes to manage manual badge award.
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once(dirname(dirname(dirname(__FILE__))) . '/config.php');
+require_once($CFG->libdir . '/badgeslib.php');
+require_once($CFG->dirroot . '/user/selector/lib.php');
+
+abstract class badge_award_selector_base extends user_selector_base {
+
+    /**
+     * The id of the badge this selector is being used for
+     * @var int
+     */
+    protected $badgeid = null;
+    /**
+     * The context of the badge this selector is being used for
+     * @var object
+     */
+    protected $context = null;
+    /**
+     * The id of the role of badge issuer in current context
+     * @var int
+     */
+    protected $issuerrole = null;
+    /**
+     * The id of badge issuer
+     * @var int
+     */
+    protected $issuerid = null;
+
+    /**
+     * Constructor method
+     * @param string $name
+     * @param array $options
+     */
+    public function __construct($name, array $options) {
+        $options['accesscontext'] = $options['context'];
+        parent::__construct($name, $options);
+        if (isset($options['context'])) {
+            if ($options['context'] instanceof context_system) {
+                // If it is a site badge, we need to get context of frontpage.
+                $this->context = context_course::instance(SITEID);
+            } else {
+                $this->context = $options['context'];
+            }
+        }
+        if (isset($options['badgeid'])) {
+            $this->badgeid = $options['badgeid'];
+        }
+        if (isset($options['issuerid'])) {
+            $this->issuerid = $options['issuerid'];
+        }
+        if (isset($options['issuerrole'])) {
+            $this->issuerrole = $options['issuerrole'];
+        }
+    }
+
+    /**
+     * Returns an array of options to seralise and store for searches
+     *
+     * @return array
+     */
+    protected function get_options() {
+        global $CFG;
+        $options = parent::get_options();
+        $options['file'] =  'badges/lib/awardlib.php';
+        $options['context'] = $this->context;
+        $options['badgeid'] = $this->badgeid;
+        $options['issuerid'] = $this->issuerid;
+        $options['issuerrole'] = $this->issuerrole;
+        return $options;
+    }
+}
+
+/**
+ * A user selector control for potential users to award badge
+ */
+class badge_potential_users_selector extends badge_award_selector_base {
+    const MAX_USERS_PER_PAGE = 100;
+
+    /**
+     * Existing recipients
+     */
+    protected $existingrecipients = array();
+
+    /**
+     * Finds all potential badge recipients
+     *
+     * Potential badge recipients are all enroled users
+     * who haven't got a badge from current issuer role.
+     *
+     * @param string $search
+     * @return array
+     */
+    public function find_users($search) {
+        global $DB;
+
+        $whereconditions = array();
+        list($wherecondition, $params) = $this->search_sql($search, 'u');
+        if ($wherecondition) {
+            $whereconditions[] = $wherecondition;
+        }
+
+        $existingids = array();
+        foreach ($this->existingrecipients as $group) {
+            foreach ($group as $user) {
+                $existingids[] = $user->id;
+            }
+        }
+        if ($existingids) {
+            list($usertest, $userparams) = $DB->get_in_or_equal($existingids, SQL_PARAMS_NAMED, 'ex', false);
+            $whereconditions[] = 'u.id ' . $usertest;
+            $params = array_merge($params, $userparams);
+        }
+
+        if ($whereconditions) {
+            $wherecondition = ' WHERE ' . implode(' AND ', $whereconditions);
+        }
+
+        list($esql, $eparams) = get_enrolled_sql($this->context, 'moodle/badges:earnbadge', 0, true);
+        $params = array_merge($params, $eparams);
+
+        $fields      = 'SELECT ' . $this->required_fields_sql('u');
+        $countfields = 'SELECT COUNT(u.id)';
+
+        $params['badgeid'] = $this->badgeid;
+        $params['issuerrole'] = $this->issuerrole;
+
+        $sql = " FROM {user} u JOIN ($esql) je ON je.id = u.id
+                 LEFT JOIN {badge_manual_award} bm
+                     ON (bm.recipientid = u.id AND bm.badgeid = :badgeid AND bm.issuerrole = :issuerrole)
+                 $wherecondition AND bm.id IS NULL";
+
+        list($sort, $sortparams) = users_order_by_sql('u', $search, $this->accesscontext);
+        $order = ' ORDER BY ' . $sort;
+
+        if (!$this->is_validating()) {
+            $potentialmemberscount = $DB->count_records_sql($countfields . $sql, $params);
+            if ($potentialmemberscount > self::MAX_USERS_PER_PAGE) {
+                return $this->too_many_results($search, $potentialmemberscount);
+            }
+        }
+
+        $availableusers = $DB->get_records_sql($fields . $sql . $order, array_merge($params, $sortparams));
+
+        if (empty($availableusers)) {
+            return array();
+        }
+
+        return array(get_string('potentialrecipients', 'badges') => $availableusers);
+    }
+
+    /**
+     * Sets the existing recipients
+     * @param array $users
+     */
+    public function set_existing_recipients(array $users) {
+        $this->existingrecipients = $users;
+    }
+}
+
+/**
+ * A user selector control for existing users to award badge
+ */
+class badge_existing_users_selector extends badge_award_selector_base {
+
+    /**
+     * Finds all users who already have been awarded a badge by current role
+     *
+     * @param string $search
+     * @return array
+     */
+    public function find_users($search) {
+        global $DB;
+        list($wherecondition, $params) = $this->search_sql($search, 'u');
+        $params['badgeid'] = $this->badgeid;
+        $params['issuerrole'] = $this->issuerrole;
+
+        list($esql, $eparams) = get_enrolled_sql($this->context, 'moodle/badges:earnbadge', 0, true);
+        $fields = $this->required_fields_sql('u');
+        list($sort, $sortparams) = users_order_by_sql('u', $search, $this->accesscontext);
+
+        $params = array_merge($params, $eparams, $sortparams);
+        $recipients = $DB->get_records_sql("SELECT $fields
+                FROM {user} u
+                JOIN ($esql) je ON je.id = u.id
+                JOIN {badge_manual_award} s ON s.recipientid = u.id
+                WHERE $wherecondition AND s.badgeid = :badgeid AND s.issuerrole = :issuerrole
+                ORDER BY $sort", $params);
+
+        return array(get_string('existingrecipients', 'badges') => $recipients);
+    }
+}
+
+function process_manual_award($recipientid, $issuerid, $issuerrole, $badgeid) {
+    global $DB;
+    $params = array(
+                'badgeid' => $badgeid,
+                'issuerid' => $issuerid,
+                'issuerrole' => $issuerrole,
+                'recipientid' => $recipientid
+            );
+
+    if (!$DB->record_exists('badge_manual_award', $params)) {
+        $award = new stdClass();
+        $award->badgeid = $badgeid;
+        $award->issuerid = $issuerid;
+        $award->issuerrole = $issuerrole;
+        $award->recipientid = $recipientid;
+        $award->datemet = time();
+        if ($DB->insert_record('badge_manual_award', $award)) {
+            return true;
+        }
+    }
+
+    return false;
+}
diff --git a/badges/lib/backpacklib.php b/badges/lib/backpacklib.php
new file mode 100644 (file)
index 0000000..f38a066
--- /dev/null
@@ -0,0 +1,119 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * External backpack library.
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->libdir . '/filelib.php');
+
+// Adopted from https://github.com/jbkc85/openbadges-class-php.
+// Author Jason Cameron <jbkc85@gmail.com>.
+
+class OpenBadgesBackpackHandler {
+    private $backpack;
+    private $email;
+    private $backpackuid = 0;
+    private $backpackgid = 0;
+
+    public function __construct($record) {
+        $this->backpack = $record->backpackurl;
+        $this->email = $record->email;
+        $this->backpackuid = isset($record->backpackuid) ? $record->backpackuid : 0;
+        $this->backpackgid = isset($record->backpackgid) ? $record->backpackgid : 0;
+    }
+
+    public function curl_request($action) {
+        $curl = new curl();
+
+        switch($action) {
+            case 'user':
+                $url = $this->backpack."/displayer/convert/email";
+                $param = array('email' => $this->email);
+                break;
+            case 'groups':
+                $url = $this->backpack . '/displayer/' . $this->backpackuid . '/groups.json';
+                break;
+            case 'badges':
+                $url = $this->backpack . '/displayer/' . $this->backpackuid . '/group/'. $this->backpackgid . '.json';
+                break;
+        }
+
+        $options = array(
+            'FRESH_CONNECT' => true,
+            'RETURNTRANSFER' => true,
+            'FORBID_REUSE' => true,
+            'HEADER' => 0,
+            'CONNECTTIMEOUT_MS' => 3000,
+        );
+
+        if ($action == 'user') {
+            $out = $curl->post($url, $param, $options);
+        } else {
+            $out = $curl->get($url, array(), $options);
+        }
+
+        return json_decode($out);
+    }
+
+    private function check_status($status) {
+        switch($status) {
+            case "missing":
+                $response = array(
+                    'status'  => $status,
+                    'message' => get_string('error:nosuchuser', 'badges')
+                );
+                return $response;
+                break;
+        }
+    }
+
+    public function get_groups() {
+        $json = $this->curl_request('user', $this->email);
+        if (isset($json->status)) {
+            if ($json->status != 'okay') {
+                return $this->check_status($json->status);
+            } else {
+                $this->backpackuid = $json->userId;
+                return $this->curl_request('groups');
+            }
+        }
+    }
+
+    public function get_badges() {
+        $json = $this->curl_request('user', $this->email);
+        if (isset($json->status)) {
+            if ($json->status != 'okay') {
+                return $this->check_status($json->status);
+            } else {
+                return $this->curl_request('badges');
+            }
+        }
+    }
+
+    public function get_url() {
+        return $this->backpack;
+    }
+}
diff --git a/badges/lib/bakerlib.php b/badges/lib/bakerlib.php
new file mode 100644 (file)
index 0000000..8a0c45e
--- /dev/null
@@ -0,0 +1,155 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Baking badges library.
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Information on PNG file chunks can be found at http://www.w3.org/TR/PNG/#11Chunks
+ * Some other info on PNG that I used http://garethrees.org/2007/11/14/pngcrush/
+ *
+ * Example of use:
+ * $png = new PNG_MetaDataHandler('file.png');
+ *
+ * if ($png->check_chunks("tEXt", "openbadge")) {
+ *     $newcontents = $png->add_chunks("tEXt", "openbadge", 'http://some.public.url/to.your.assertion.file');
+ * }
+ *
+ * file_put_contents('file.png', $newcontents);
+ */
+
+class PNG_MetaDataHandler
+{
+    /** @var string File content as a string */
+    private $_contents;
+    /** @var int Length of the image file */
+    private $_size;
+    /** @var array Variable for storing parsed chunks */
+    private $_chunks;
+
+    /**
+     * Prepares file for handling metadata.
+     * Verifies that this file is a valid PNG file.
+     * Unpacks file chunks and reads them into an array.
+     *
+     * @param string $contents File content as a string
+     */
+    public function __construct($contents) {
+        $this->_contents = $contents;
+        $png_signature = pack("C8", 137, 80, 78, 71, 13, 10, 26, 10);
+
+        // Read 8 bytes of PNG header and verify.
+        $header = substr($this->_contents, 0, 8);
+
+        if ($header != $png_signature) {
+            debugging('This is not a valid PNG image');
+        }
+
+        $this->_size = strlen($this->_contents);
+
+        $this->_chunks = array();
+
+        // Skip 8 bytes of IHDR image header.
+        $position = 8;
+        do {
+            $chunk = @unpack('Nsize/a4type', substr($this->_contents, $position, 8));
+            $this->_chunks[$chunk['type']][] = substr($this->_contents, $position + 8, $chunk['size']);
+
+            // Skip 12 bytes chunk overhead.
+            $position += $chunk['size'] + 12;
+        } while ($position < $this->_size);
+    }
+
+    /**
+     * Checks if a key already exists in the chunk of said type.
+     * We need to avoid writing same keyword into file chunks.
+     *
+     * @param string $type Chunk type, like iTXt, tEXt, etc.
+     * @param string $check Keyword that needs to be checked.
+     *
+     * @return boolean (true|false) True if file is safe to write this keyword, false otherwise.
+     */
+    public function check_chunks($type, $check) {
+        if (array_key_exists($type, $this->_chunks)) {
+            foreach (array_keys($this->_chunks[$type]) as $typekey) {
+                list($key, $data) = explode("\0", $this->_chunks[$type][$typekey]);
+
+                if (strcmp($key, $check) == 0) {
+                    debugging('Key "' . $check . '" already exists in "' . $type . '" chunk.');
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Adds a chunk with keyword and data to the file content.
+     * Chunk is added to the end of the file, before IEND image trailer.
+     *
+     * @param string $type Chunk type, like iTXt, tEXt, etc.
+     * @param string $key Keyword that needs to be added.
+     * @param string $value Currently an assertion URL that is added to an image metadata.
+     *
+     * @return string $result File content with a new chunk as a string. Can be used in file_put_contents() to write to a file.
+     */
+    public function add_chunks($type, $key, $value) {
+        if (strlen($key) > 79) {
+            debugging('Key is too big');
+        }
+
+        if ($type == 'iTXt') {
+            // iTXt International textual data.
+            // Keyword:             1-79 bytes (character string)
+            // Null separator:      1 byte
+            // Compression flag:    1 byte
+            // Compression method:  1 byte
+            // Language tag:        0 or more bytes (character string)
+            // Null separator:      1 byte
+            // Translated keyword:  0 or more bytes
+            // Null separator:      1 byte
+            // Text:                0 or more bytes
+            $data = $key . "\000'json'\0''\0\"{'method': 'hosted', 'assertionUrl': '" . $value . "'}\"";
+        } else {
+            // tEXt Textual data.
+            // Keyword:        1-79 bytes (character string)
+            // Null separator: 1 byte
+            // Text:           n bytes (character string)
+            $data = $key . "\0" . $value;
+        }
+        $crc = pack("N", crc32($type . $data));
+        $len = pack("N", strlen($data));
+
+        // Chunk format: length + type + data + CRC.
+        // CRC is a CRC-32 computed over the chunk type and chunk data.
+        $newchunk = $len . $type . $data . $crc;
+
+        $result = substr($this->_contents, 0, $this->_size - 12)
+                . $newchunk
+                . substr($this->_contents, $this->_size - 12, 12);
+
+        return $result;
+    }
+}
diff --git a/badges/mybackpack.php b/badges/mybackpack.php
new file mode 100644 (file)
index 0000000..6797c7d
--- /dev/null
@@ -0,0 +1,164 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * User backpack settings page.
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+require_once(dirname(dirname(__FILE__)) . '/config.php');
+require_once($CFG->libdir . '/badgeslib.php');
+require_once($CFG->dirroot . '/badges/backpack_form.php');
+require_once($CFG->dirroot . '/badges/lib/backpacklib.php');
+
+require_login();
+
+if (empty($CFG->enablebadges)) {
+    print_error('badgesdisabled', 'badges');
+}
+
+$context = context_user::instance($USER->id);
+require_capability('moodle/badges:manageownbadges', $context);
+
+$clear = optional_param('clear', false, PARAM_BOOL);
+
+if (!$CFG->badges_allowexternalbackpack) {
+    redirect($CFG->wwwroot);
+}
+
+$PAGE->set_url(new moodle_url('/badges/mybackpack.php'));
+$PAGE->set_context($context);
+
+$title = get_string('mybackpack', 'badges');
+$PAGE->set_title($title);
+$PAGE->set_heading($title);
+$PAGE->set_pagelayout('mydashboard');
+
+navigation_node::override_active_url(new moodle_url('/badges/mybadges.php'));
+$PAGE->navbar->add(get_string('mybackpack', 'badges'));
+
+if ($clear) {
+     $DB->delete_records('badge_backpack', array('userid' => $USER->id));
+     redirect(new moodle_url('/badges/mybadges.php'));
+}
+
+$backpack = $DB->get_record('badge_backpack', array('userid' => $USER->id));
+
+if ($backpack) {
+    $bp = new OpenBadgesBackpackHandler($backpack);
+    $request = $bp->get_groups();
+
+    if (empty($request->groups)) {
+        unset($SESSION->badgesparams);
+        redirect(new moodle_url('/badges/mybadges.php'), get_string('error:nogroups', 'badges'), 20);
+    }
+
+    $params = array(
+            'data' => $backpack,
+            'backpackuid' => $request->userId,
+            'groups' => $request->groups
+    );
+    $groupform = new edit_backpack_group_form(new moodle_url('/badges/mybackpack.php'), $params);
+
+    if ($groupform->is_cancelled()) {
+        redirect(new moodle_url('/badges/mybadges.php'));
+    } else if ($groupdata = $groupform->get_data()) {
+        $obj = new stdClass();
+        $obj->userid = $groupdata->userid;
+        $obj->email = $groupdata->email;
+        $obj->backpackurl = $groupdata->backpackurl;
+        $obj->backpackuid = $groupdata->backpackuid;
+        $obj->backpackgid = $groupdata->backpackgid;
+        $obj->autosync = 0;
+        $obj->password = '';
+        if ($rec = $DB->get_record('badge_backpack', array('userid' => $groupdata->userid))) {
+            $obj->id = $rec->id;
+            $DB->update_record('badge_backpack', $obj);
+        } else {
+            $DB->insert_record('badge_backpack', $obj);
+        }
+        redirect(new moodle_url('/badges/mybadges.php'));
+    }
+
+    echo $OUTPUT->header();
+    $groupform->display();
+    echo $OUTPUT->footer();
+    die();
+} else {
+    $form = new edit_backpack_form();
+
+    if ($form->is_cancelled()) {
+        redirect(new moodle_url('/badges/mybadges.php'));
+    } else if (($data = $form->get_data()) || !empty($SESSION->badgesparams)) {
+        if (empty($SESSION->badgesparams)) {
+            $bp = new OpenBadgesBackpackHandler($data);
+            $request = $bp->get_groups();
+
+            // If there is an error, start over.
+            if (is_array($request) && $request['status'] == 'missing') {
+                unset($SESSION->badgesparams);
+                redirect(new moodle_url('/badges/mybackpack.php'), $request['message'], 10);
+            } else if (empty($request->groups)) {
+                unset($SESSION->badgesparams);
+                redirect(new moodle_url('/badges/mybadges.php'), get_string('error:nogroups', 'badges'), 20);
+            }
+
+            $params = array(
+                    'data' => $data,
+                    'backpackuid' => $request->userId,
+                    'groups' => $request->groups
+            );
+            $SESSION->badgesparams = $params;
+        }
+        $groupform = new edit_backpack_group_form(new moodle_url('/badges/mybackpack.php', array('addgroups' => true)), $SESSION->badgesparams);
+
+        if ($groupform->is_cancelled()) {
+            unset($SESSION->badgesparams);
+            redirect(new moodle_url('/badges/mybadges.php'));
+        } else if ($groupdata = $groupform->get_data()) {
+            $obj = new stdClass();
+            $obj->userid = $groupdata->userid;
+            $obj->email = $groupdata->email;
+            $obj->backpackurl = $groupdata->backpackurl;
+            $obj->backpackuid = $groupdata->backpackuid;
+            $obj->backpackgid = $groupdata->backpackgid;
+            $obj->autosync = 0;
+            $obj->password = '';
+            if ($rec = $DB->get_record('badge_backpack', array('userid' => $groupdata->userid))) {
+                $obj->id = $rec->id;
+                $DB->update_record('badge_backpack', $obj);
+            } else {
+                $DB->insert_record('badge_backpack', $obj);
+            }
+            unset($SESSION->badgesparams);
+            redirect(new moodle_url('/badges/mybadges.php'));
+        }
+
+        echo $OUTPUT->header();
+        $groupform->display();
+        echo $OUTPUT->footer();
+        die();
+    }
+
+    echo $OUTPUT->header();
+    $form->display();
+    echo $OUTPUT->footer();
+}
\ No newline at end of file
diff --git a/badges/mybadges.php b/badges/mybadges.php
new file mode 100644 (file)
index 0000000..bf18a7f
--- /dev/null
@@ -0,0 +1,116 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Displays user badges for badges management in own profile
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+require_once(dirname(dirname(__FILE__)) . '/config.php');
+require_once($CFG->libdir . '/badgeslib.php');
+
+$page        = optional_param('page', 0, PARAM_INT);
+$search      = optional_param('search', '', PARAM_CLEAN);
+$clearsearch = optional_param('clearsearch', '', PARAM_TEXT);
+$download    = optional_param('download', 0, PARAM_INT);
+$hash        = optional_param('hash', '', PARAM_ALPHANUM);
+$downloadall = optional_param('downloadall', false, PARAM_BOOL);
+$hide        = optional_param('hide', 0, PARAM_INT);
+$show        = optional_param('show', 0, PARAM_INT);
+
+require_login();
+
+if (empty($CFG->enablebadges)) {
+    print_error('badgesdisabled', 'badges');
+}
+
+if (isguestuser()) {
+    die();
+}
+
+if ($page < 0) {
+    $page = 0;
+}
+
+if ($clearsearch) {
+    $search = '';
+}
+
+if ($hide) {
+    require_sesskey();
+    $DB->set_field('badge_issued', 'visible', 0, array('id' => $hide));
+} else if ($show) {
+    require_sesskey();
+    $DB->set_field('badge_issued', 'visible', 1, array('id' => $show));
+} else if ($download && $hash) {
+    require_sesskey();
+    $badge = new badge($download);
+    $name = str_replace(' ', '_', $badge->name) . '.png';
+    ob_start();
+    $file = badges_bake($hash, $download);
+    header('Content-Type: image/png');
+    header('Content-Disposition: attachment; filename="'. $name .'"');
+    readfile($file);
+    ob_flush();
+} else if ($downloadall) {
+    require_sesskey();
+    ob_start();
+    badges_download($USER->id);
+    ob_flush();
+}
+
+$context = context_user::instance($USER->id);
+require_capability('moodle/badges:manageownbadges', $context);
+
+$url = new moodle_url('/badges/mybadges.php');
+
+$PAGE->set_url($url);
+$PAGE->set_context($context);
+
+$title = get_string('mybadges', 'badges');
+$PAGE->set_title($title);
+$PAGE->set_heading($title);
+$PAGE->set_pagelayout('mydashboard');
+
+// TODO: Better way of pushing badges to Mozilla backpack?
+if ($CFG->badges_allowexternalbackpack) {
+    $PAGE->requires->js(new moodle_url('http://backpack.openbadges.org/issuer.js'), true);
+    $PAGE->requires->js('/badges/backpack.js', true);
+}
+
+$output = $PAGE->get_renderer('core', 'badges');
+$badges = badges_get_user_badges($USER->id);
+
+echo $OUTPUT->header();
+$totalcount = count($badges);
+$records = badges_get_user_badges($USER->id, null, $page, BADGE_PERPAGE, $search);
+
+$userbadges             = new badge_user_collection($records, $USER->id);
+$userbadges->sort       = 'dateissued';
+$userbadges->dir        = 'DESC';
+$userbadges->page       = $page;
+$userbadges->perpage    = BADGE_PERPAGE;
+$userbadges->totalcount = $totalcount;
+$userbadges->search     = $search;
+
+echo $output->render($userbadges);
+
+echo $OUTPUT->footer();
\ No newline at end of file
diff --git a/badges/newbadge.php b/badges/newbadge.php
new file mode 100644 (file)
index 0000000..fa3c2c6
--- /dev/null
@@ -0,0 +1,106 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * First step page for creating a new badge
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+require_once(dirname(dirname(__FILE__)) . '/config.php');
+require_once($CFG->libdir . '/badgeslib.php');
+require_once($CFG->dirroot . '/badges/edit_form.php');
+
+$type = required_param('type', PARAM_INT);
+$courseid = optional_param('id', 0, PARAM_INT);
+
+require_login();
+
+if (empty($CFG->enablebadges)) {
+    print_error('badgesdisabled', 'badges');
+}
+
+$title = get_string('create', 'badges');
+
+if (($type == BADGE_TYPE_COURSE) && ($course = $DB->get_record('course', array('id' => $courseid)))) {
+    require_login($course);
+    $PAGE->set_context(context_course::instance($course->id));
+    $PAGE->set_pagelayout('course');
+    $PAGE->set_url('/badges/newbadge.php', array('type' => $type, 'id' => $course->id));
+    $PAGE->set_heading($course->fullname . ": " . $title);
+    $PAGE->set_title($course->fullname . ": " . $title);
+} else {
+    $PAGE->set_context(context_system::instance());
+    $PAGE->set_pagelayout('admin');
+    $PAGE->set_url('/badges/newbadge.php', array('type' => $type));
+    $PAGE->set_heading($title);
+    $PAGE->set_title($title);
+}
+
+require_capability('moodle/badges:createbadge', $PAGE->context);
+
+$PAGE->requires->js('/badges/backpack.js');
+$PAGE->requires->js_init_call('check_site_access', null, false);
+
+$fordb = new stdClass();
+$fordb->id = null;
+
+$form = new edit_details_form($PAGE->url, array('action' => 'new'));
+
+if ($form->is_cancelled()) {
+    redirect(new moodle_url('/badges/index.php', array('type' => $type, 'id' => $courseid)));
+} else if ($data = $form->get_data()) {
+    // Creating new badge here.
+    $now = time();
+
+    $fordb->name = $data->name;
+    $fordb->description = $data->description;
+    $fordb->timecreated = $now;
+    $fordb->timemodified = $now;
+    $fordb->usercreated = $USER->id;
+    $fordb->usermodified = $USER->id;
+    $fordb->image = 0;
+    $fordb->issuername = $data->issuername;
+    $fordb->issuerurl = $data->issuerurl;
+    $fordb->issuercontact = $data->issuercontact;
+    $fordb->expiredate = ($data->expiry == 1) ? $data->expiredate : null;
+    $fordb->expireperiod = ($data->expiry == 2) ? $data->expireperiod : null;
+    $fordb->type = $type;
+    $fordb->courseid = ($type == BADGE_TYPE_COURSE) ? $courseid : null;
+    $fordb->messagesubject = get_string('messagesubject', 'badges');
+    $fordb->message = get_string('messagebody', 'badges',
+            html_writer::link($CFG->wwwroot . '/badges/mybadges.php', get_string('mybadges', 'badges')));
+    $fordb->attachment = 1;
+    $fordb->notification = BADGE_MESSAGE_NEVER;
+    $fordb->status = BADGE_STATUS_INACTIVE;
+
+    $newid = $DB->insert_record('badge', $fordb, true);
+
+    $newbadge = new badge($newid);
+    badges_process_badge_image($newbadge, $form->save_temp_file('image'));
+    redirect(new moodle_url('/badges/criteria.php', array('id' => $newid)));
+}
+
+echo $OUTPUT->header();
+echo $OUTPUT->box('', 'notifyproblem hide', 'check_connection');
+
+$form->display();
+
+echo $OUTPUT->footer();
\ No newline at end of file
diff --git a/badges/overview.php b/badges/overview.php
new file mode 100644 (file)
index 0000000..b658558
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Badge overview page
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+require_once(dirname(dirname(__FILE__)) . '/config.php');
+require_once($CFG->libdir . '/badgeslib.php');
+
+$badgeid = required_param('id', PARAM_INT);
+$awards = optional_param('awards', '', PARAM_ALPHANUM);
+
+require_login();
+
+if (empty($CFG->enablebadges)) {
+    print_error('badgesdisabled', 'badges');
+}
+
+$badge = new badge($badgeid);
+$context = $badge->get_context();
+$navurl = new moodle_url('/badges/index.php', array('type' => $badge->type));
+
+if ($badge->type == BADGE_TYPE_COURSE) {
+    require_login($badge->courseid);
+    $navurl = new moodle_url('/badges/index.php', array('type' => $badge->type, 'id' => $badge->courseid));
+}
+
+$currenturl = new moodle_url('/badges/overview.php', array('id' => $badge->id));
+
+$PAGE->set_context($context);
+$PAGE->set_url($currenturl);
+$PAGE->set_pagelayout('standard');
+$PAGE->set_heading($badge->name);
+$PAGE->set_title($badge->name);
+
+// Set up navigation and breadcrumbs.
+navigation_node::override_active_url($navurl);
+$PAGE->navbar->add($badge->name);
+
+echo $OUTPUT->header();
+echo $OUTPUT->heading(print_badge_image($badge, $context, 'small') . ' ' . $badge->name);
+
+if ($awards == 'cron') {
+    echo $OUTPUT->notification(get_string('awardoncron', 'badges'), 'notifysuccess');
+} else if ($awards != 0) {
+    echo $OUTPUT->notification(get_string('numawardstat', 'badges', $awards), 'notifysuccess');
+}
+
+$output = $PAGE->get_renderer('core', 'badges');
+echo $output->print_badge_status_box($badge);
+$output->print_badge_tabs($badgeid, $context, 'overview');
+echo $output->print_badge_overview($badge, $context);
+
+echo $OUTPUT->footer();
\ No newline at end of file
diff --git a/badges/recipients.php b/badges/recipients.php
new file mode 100644 (file)
index 0000000..8105863
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Badge awards information
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+require_once(dirname(dirname(__FILE__)) . '/config.php');
+require_once($CFG->libdir . '/badgeslib.php');
+
+$badgeid    = required_param('id', PARAM_INT);
+$sortby     = optional_param('sort', 'dateissued', PARAM_ALPHA);
+$sorthow    = optional_param('dir', 'DESC', PARAM_ALPHA);
+$page       = optional_param('page', 0, PARAM_INT);
+$updatepref = optional_param('updatepref', false, PARAM_BOOL);
+
+require_login();
+
+if (empty($CFG->enablebadges)) {
+    print_error('badgesdisabled', 'badges');
+}
+
+if (!in_array($sortby, array('firstname', 'lastname', 'dateissued'))) {
+    $sortby = 'dateissued';
+}
+
+if ($sorthow != 'ASC' and $sorthow != 'DESC') {
+    $sorthow = 'DESC';
+}
+
+if ($page < 0) {
+    $page = 0;
+}
+
+$badge = new badge($badgeid);
+$context = $badge->get_context();
+$navurl = new moodle_url('/badges/index.php', array('type' => $badge->type));
+
+if ($badge->type == BADGE_TYPE_COURSE) {
+    require_login($badge->courseid);
+    $navurl = new moodle_url('/badges/index.php', array('type' => $badge->type, 'id' => $badge->courseid));
+}
+
+$PAGE->set_context($context);
+$PAGE->set_url('/badges/recipients.php', array('id' => $badgeid, 'sort' => $sortby, 'dir' => $sorthow));
+$PAGE->set_pagelayout('standard');
+$PAGE->set_heading($badge->name);
+$PAGE->set_title($badge->name);
+$PAGE->navbar->add($badge->name);
+navigation_node::override_active_url($navurl);
+
+$output = $PAGE->get_renderer('core', 'badges');
+
+echo $output->header();
+echo $OUTPUT->heading(print_badge_image($badge, $context, 'small') . ' ' . $badge->name);
+
+echo $output->print_badge_status_box($badge);
+$output->print_badge_tabs($badgeid, $context, 'awards');
+
+// Add button for badge manual award.
+if ($badge->has_manual_award_criteria() && has_capability('moodle/badges:awardbadge', $context) && $badge->is_active()) {
+    $url = new moodle_url('/badges/award.php', array('id' => $badge->id));
+    echo $OUTPUT->box($OUTPUT->single_button($url, get_string('award', 'badges')), 'clearfix mdl-align');
+}
+
+$sql = "SELECT b.userid, b.dateissued, b.uniquehash, u.firstname, u.lastname
+    FROM {badge_issued} b INNER JOIN {user} u
+        ON b.userid = u.id
+    WHERE b.badgeid = :badgeid
+    ORDER BY $sortby $sorthow";
+
+$totalcount = $DB->count_records('badge_issued', array('badgeid' => $badge->id));
+
+if ($badge->has_awards()) {
+    $users = $DB->get_records_sql($sql, array('badgeid' => $badge->id), $page * BADGE_PERPAGE, BADGE_PERPAGE);
+    $recipients             = new badge_recipients($users);
+    $recipients->sort       = $sortby;
+    $recipients->dir        = $sorthow;
+    $recipients->page       = $page;
+    $recipients->perpage    = BADGE_PERPAGE;
+    $recipients->totalcount = $totalcount;
+
+    echo $output->render($recipients);
+} else {
+    echo $output->notification(get_string('noawards', 'badges'));
+}
+
+echo $output->footer();
\ No newline at end of file
diff --git a/badges/renderer.php b/badges/renderer.php
new file mode 100644 (file)
index 0000000..d78c54b
--- /dev/null
@@ -0,0 +1,1013 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Renderer for use with the badges output
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+require_once($CFG->libdir . '/badgeslib.php');
+require_once($CFG->libdir . '/tablelib.php');
+require_once($CFG->dirroot . '/user/filters/lib.php');
+
+/**
+ * Standard HTML output renderer for badges
+ */
+class core_badges_renderer extends plugin_renderer_base {
+
+    // Outputs badges list.
+    public function print_badges_list($badges, $userid, $profile = false, $external = false) {
+        global $USER, $CFG;
+        foreach ($badges as $badge) {
+            if (!$external) {
+                $context = ($badge->type == BADGE_TYPE_SITE) ? context_system::instance() : context_course::instance($badge->courseid);
+                $bname = $badge->name;
+                $imageurl = moodle_url::make_pluginfile_url($context->id, 'badges', 'badgeimage', $badge->id, '/', 'f1', false);
+            } else {
+                $bname = $badge->assertion->badge->name;
+                $imageurl = $badge->imageUrl;
+            }
+
+            $name = html_writer::tag('span', $bname, array('class' => 'badge-name'));
+
+            $image = html_writer::empty_tag('img', array('src' => $imageurl, 'class' => 'badge-image'));
+            if (!empty($badge->dateexpire) && $badge->dateexpire < time()) {
+                $image .= $this->output->pix_icon('i/expired',
+                        get_string('expireddate', 'badges', userdate($badge->dateexpire)),
+                        'moodle',
+                        array('class' => 'expireimage'));
+                $name .= '(' . get_string('expired', 'badges') . ')';
+            }
+
+            $download = $status = $push = '';
+            if (($userid == $USER->id) && !$profile) {
+                $url = new moodle_url('mybadges.php', array('download' => $badge->id, 'hash' => $badge->uniquehash, 'sesskey' => sesskey()));
+                if ($CFG->badges_allowexternalbackpack && (empty($badge->dateexpire) || $badge->dateexpire > time())) {
+                    $assertion = new moodle_url('/badges/assertion.php', array('b' => $badge->uniquehash));
+                    $action = new component_action('click', 'addtobackpack', array('assertion' => $assertion->out(false)));
+                    $push = $this->output->action_icon(new moodle_url('#'), new pix_icon('t/backpack', get_string('addtobackpack', 'badges')), $action);
+                }
+
+                $download = $this->output->action_icon($url, new pix_icon('t/download', get_string('download')));
+                if ($badge->visible) {
+                    $url = new moodle_url('mybadges.php', array('hide' => $badge->issuedid, 'sesskey' => sesskey()));
+                    $status = $this->output->action_icon($url, new pix_icon('t/hide', get_string('makeprivate', 'badges')));
+                } else {
+                    $url = new moodle_url('mybadges.php', array('show' => $badge->issuedid, 'sesskey' => sesskey()));
+                    $status = $this->output->action_icon($url, new pix_icon('t/show', get_string('makepublic', 'badges')));
+                }
+            }
+
+            if (!$profile) {
+                $url = new moodle_url('badge.php', array('hash' => $badge->uniquehash));
+            } else {
+                if (!$external) {
+                    $url = new moodle_url($CFG->wwwroot . '/badges/badge.php', array('hash' => $badge->uniquehash));
+                } else {
+                    $url = new moodle_url($CFG->wwwroot . '/badges/external.php', array('badge' => serialize($badge)));
+                }
+            }
+            $actions = html_writer::tag('div', $push . $download . $status, array('class' => 'badge-actions'));
+            $items[] = html_writer::link($url, $image . $actions . $name, array('title' => $bname));
+        }
+
+        return html_writer::alist($items, array('class' => 'badges'));
+    }
+
+    // Recipients selection form.
+    public function recipients_selection_form(user_selector_base $existinguc, user_selector_base $potentialuc) {
+        $output = '';
+        $formattributes = array();
+        $formattributes['id'] = 'recipientform';
+        $formattributes['action'] = '';
+        $formattributes['method'] = 'post';
+        $output .= html_writer::start_tag('form', $formattributes);
+        $output .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()));
+
+        $existingcell = new html_table_cell();
+        $existingcell->text = $existinguc->display(true);
+        $existingcell->attributes['class'] = 'existing';
+        $actioncell = new html_table_cell();
+        $actioncell->text  = html_writer::start_tag('div', array());
+        $actioncell->text .= html_writer::empty_tag('input', array(
+                    'type' => 'submit',
+                    'name' => 'award',
+                    'value' => $this->output->larrow() . ' ' . get_string('award', 'badges'),
+                    'class' => 'actionbutton')
+                );
+        $actioncell->text .= html_writer::end_tag('div', array());
+        $actioncell->attributes['class'] = 'actions';
+        $potentialcell = new html_table_cell();
+        $potentialcell->text = $potentialuc->display(true);
+        $potentialcell->attributes['class'] = 'potential';
+
+        $table = new html_table();
+        $table->attributes['class'] = 'recipienttable boxaligncenter';
+        $table->data = array(new html_table_row(array($existingcell, $actioncell, $potentialcell)));
+        $output .= html_writer::table($table);
+
+        $output .= html_writer::end_tag('form');
+        return $output;
+    }
+
+    // Prints a badge overview infomation.
+    public function print_badge_overview($badge, $context) {
+        $display = "";
+
+        // Badge details.
+        $display .= html_writer::start_tag('fieldset', array('class' => 'generalbox'));
+        $display .= html_writer::tag('legend', get_string('badgedetails', 'badges'), array('class' => 'bold'));
+
+        $detailstable = new html_table();
+        $detailstable->attributes = array('class' => 'clearfix', 'id' => 'badgedetails');
+        $detailstable->data[] = array(get_string('name') . ":", $badge->name);
+        $detailstable->data[] = array(get_string('description', 'badges') . ":", $badge->description);
+        $detailstable->data[] = array(get_string('createdon', 'search') . ":", userdate($badge->timecreated));
+        $detailstable->data[] = array(get_string('badgeimage', 'badges') . ":",
+                print_badge_image($badge, $context, 'large'));
+        $display .= html_writer::table($detailstable);
+        $display .= html_writer::end_tag('fieldset');
+
+        // Issuer details.
+        $display .= html_writer::start_tag('fieldset', array('class' => 'generalbox'));
+        $display .= html_writer::tag('legend', get_string('issuerdetails', 'badges'), array('class' => 'bold'));
+
+        $issuertable = new html_table();
+        $issuertable->attributes = array('class' => 'clearfix', 'id' => 'badgeissuer');
+        $issuertable->data[] = array(get_string('issuername', 'badges') . ":", $badge->issuername);
+        $issuertable->data[] = array(get_string('contact', 'badges') . ":",
+                html_writer::tag('a', $badge->issuercontact, array('href' => 'mailto:' . $badge->issuercontact)));
+        $display .= html_writer::table($issuertable);
+        $display .= html_writer::end_tag('fieldset');
+
+        // Issuance details if any.
+        $display .= html_writer::start_tag('fieldset', array('class' => 'generalbox'));
+        $display .= html_writer::tag('legend', get_string('issuancedetails', 'badges'), array('class' => 'bold'));
+        if ($badge->can_expire()) {
+            if ($badge->expiredate) {
+                $display .= get_string('expiredate', 'badges', userdate($badge->expiredate));
+            } else if ($badge->expireperiod) {
+                if ($badge->expireperiod < 60) {
+                    $display .= get_string('expireperiods', 'badges', round($badge->expireperiod, 2));
+                } else if ($badge->expireperiod < 60 * 60) {
+                    $display .= get_string('expireperiodm', 'badges', round($badge->expireperiod / 60, 2));
+                } else if ($badge->expireperiod < 60 * 60 * 24) {
+                    $display .= get_string('expireperiodh', 'badges', round($badge->expireperiod / 60 / 60, 2));
+                } else {
+                    $display .= get_string('expireperiod', 'badges', round($badge->expireperiod / 60 / 60 / 24, 2));
+                }
+            }
+        } else {
+            $display .= get_string('noexpiry', 'badges');
+        }
+        $display .= html_writer::end_tag('fieldset');
+
+        // Criteria details if any.
+        $display .= html_writer::start_tag('fieldset', array('class' => 'generalbox'));
+        $display .= html_writer::tag('legend', get_string('bcriteria', 'badges'), array('class' => 'bold'));
+        if ($badge->has_criteria()) {
+            $display .= self::print_badge_criteria($badge);
+        } else {
+            $display .= get_string('nocriteria', 'badges');
+            if (has_capability('moodle/badges:configurecriteria', $context)) {
+                $display .= $this->output->single_button(
+                    new moodle_url('/badges/criteria.php', array('id' => $badge->id)),
+                    get_string('addcriteria', 'badges'), 'POST', array('class' => 'activatebadge'));
+            }
+        }
+        $display .= html_writer::end_tag('fieldset');
+
+        // Awards details if any.
+        if (has_capability('moodle/badges:viewawarded', $context)) {
+            $display .= html_writer::start_tag('fieldset', array('class' => 'generalbox'));
+            $display .= html_writer::tag('legend', get_string('awards', 'badges'), array('class' => 'bold'));
+            if ($badge->has_awards()) {
+                $url = new moodle_url('/badges/recipients.php', array('id' => $badge->id));
+                $a = new stdClass();
+                $a->link = $url->out();
+                $a->count = count($badge->get_awards());
+                $display .= get_string('numawards', 'badges', $a);
+            } else {
+                $display .= get_string('noawards', 'badges');
+            }
+
+            if (has_capability('moodle/badges:awardbadge', $context) &&
+                $badge->has_manual_award_criteria() &&
+                $badge->is_active()) {
+                $display .= $this->output->single_button(
+                        new moodle_url('/badges/award.php', array('id' => $badge->id)),
+                        get_string('award', 'badges'), 'POST', array('class' => 'activatebadge'));
+            }
+            $display .= html_writer::end_tag('fieldset');
+        }
+
+        return $display;
+    }
+
+    // Prints action icons for the badge.
+    public function print_badge_table_actions($badge, $context) {
+        $actions = "";
+
+        if (has_capability('moodle/badges:configuredetails', $context)) {
+            // Activate/deactivate badge.
+            if ($badge->status == BADGE_STATUS_INACTIVE || $badge->status == BADGE_STATUS_INACTIVE_LOCKED) {
+                $url = new moodle_url(qualified_me());
+                $url->param('activate', $badge->id);
+                $actions .= $this->output->action_icon($url, new pix_icon('t/show', get_string('activate', 'badges'))) . " ";
+            } else {
+                $url = new moodle_url(qualified_me());
+                $url->param('lock', $badge->id);
+                $url->param('sesskey', sesskey());
+                $actions .= $this->output->action_icon($url, new pix_icon('t/hide', get_string('deactivate', 'badges'))) . " ";
+            }
+        }
+
+        // Award badge manually.
+        if ($badge->has_manual_award_criteria() &&
+                has_capability('moodle/badges:awardbadge', $context) &&
+                $badge->is_active()) {
+            $url = new moodle_url('/badges/award.php', array('id' => $badge->id));
+            $actions .= $this->output->action_icon($url, new pix_icon('t/award', get_string('award', 'badges'))) . " ";
+        }
+
+        // Edit badge.
+        if (has_capability('moodle/badges:configuredetails', $context)) {
+            $url = new moodle_url('/badges/edit.php', array('id' => $badge->id, 'action' => 'details'));
+            $actions .= $this->output->action_icon($url, new pix_icon('t/edit', get_string('edit'))) . " ";
+        }
+
+        // Duplicate badge.
+        if (has_capability('moodle/badges:createbadge', $context)) {
+            $url = new moodle_url('/badges/action.php', array('copy' => '1', 'id' => $badge->id, 'sesskey' => sesskey()));
+            $actions .= $this->output->action_icon($url, new pix_icon('t/copy', get_string('copy'))) . " ";
+        }
+
+        // Delete badge.
+        if (has_capability('moodle/badges:deletebadge', $context)) {
+            $url = new moodle_url(qualified_me());
+            $url->param('delete', $badge->id);
+            $actions .= $this->output->action_icon($url, new pix_icon('t/delete', get_string('delete'))) . " ";
+        }
+
+        return $actions;
+    }
+
+    // Outputs issued badge with actions available.
+    protected function render_issued_badge(issued_badge $ibadge) {
+        global $USER, $CFG, $DB;
+        $issued = $ibadge->issued;
+        $badge = new badge($ibadge->badgeid);
+        $today_date = date('Y-m-d');
+        $today = strtotime($today_date);
+
+        if ($ibadge->visible
+            || ($USER->id == $ibadge->recipient)
+            || has_capability('moodle/badges:viewawarded', context_system::instance())) {
+            $table = new html_table();
+
+            $imagetable = new html_table();
+            $imagetable->attributes = array('class' => 'clearfix badgeissuedimage');
+            $imagetable->data[] = array(html_writer::empty_tag('img', array('src' => $issued['badge']['image'])));
+            if ($USER->id == $ibadge->recipient && !empty($CFG->enablebadges)) {
+                $imagetable->data[] = array($this->output->single_button(
+                            new moodle_url('/badges/badge.php', array('hash' => $ibadge->hash, 'bake' => true)),
+                            get_string('download'),
+                            'POST'));
+                $expiration = isset($issued['expires']) ? strtotime($issued['expires']) : $today + 1;
+                if ($CFG->badges_allowexternalbackpack && ($expiration > $today)) {
+                    $assertion = new moodle_url('/badges/assertion.php', array('b' => $ibadge->hash));
+                    $attributes = array(
+                            'type' => 'button',
+                            'value' => get_string('addtobackpack', 'badges'),
+                            'onclick' => 'OpenBadges.issue(["' . $assertion->out(false) . '"], function(errors, successes) { })');
+                    $tobackpack = html_writer::tag('input', '', $attributes);
+                    $imagetable->data[] = array($tobackpack);
+                }
+            }
+            $datatable = new html_table();
+            $datatable->attributes = array('class' => 'badgeissuedinfo');
+            $datatable->colclasses = array('bfield', 'bvalue');
+            $datatable->data[] = array($this->output->heading(get_string('issuerdetails', 'badges'), 3), '');
+            $datatable->data[] = array(get_string('issuername', 'badges'), $badge->issuername);
+            if (isset($badge->issuercontact) && !empty($badge->issuercontact)) {
+                $datatable->data[] = array(get_string('contact', 'badges'),
+                    html_writer::tag('a', $badge->issuercontact, array('href' => 'mailto:' . $badge->issuercontact)));
+            }
+            $datatable->data[] = array($this->output->heading(get_string('badgedetails', 'badges'), 3), '');
+            $datatable->data[] = array(get_string('name'), $badge->name);
+            $datatable->data[] = array(get_string('description', 'badges'), $badge->description);
+
+            if ($badge->type == BADGE_TYPE_COURSE && isset($badge->courseid)) {
+                $coursename = $DB->get_field('course', 'fullname', array('id' => $badge->courseid));
+                $datatable->data[] = array(get_string('course'), $coursename);
+            }
+
+            $datatable->data[] = array(get_string('bcriteria', 'badges'), self::print_badge_criteria($badge));
+            $datatable->data[] = array($this->output->heading(get_string('issuancedetails', 'badges'), 3), '');
+            $datatable->data[] = array(get_string('dateawarded', 'badges'), $issued['issued_on']);
+            if (isset($issued['expires'])) {
+                $expiration = strtotime($issued['expires']);
+                if ($expiration < $today) {
+                    $cell = new html_table_cell($issued['expires'] . get_string('warnexpired', 'badges'));
+                    $cell->attributes = array('class' => 'notifyproblem warning');
+                    $datatable->data[] = array(get_string('expirydate', 'badges'), $cell);
+
+                    $image = html_writer::start_tag('div', array('class' => 'badge'));
+                    $image .= html_writer::empty_tag('img', array('src' => $issued['badge']['image']));
+                    $image .= $this->output->pix_icon('i/expired',
+                                    get_string('expireddate', 'badges', $issued['expires']),
+                                    'moodle',
+                                    array('class' => 'expireimage'));
+                    $image .= html_writer::end_tag('div');
+                    $imagetable->data[0] = array($image);
+                } else {
+                    $datatable->data[] = array(get_string('expirydate', 'badges'), $issued['expires']);
+                }
+            }
+
+            // Print evidence.
+            $agg = $badge->get_aggregation_methods();
+            $evidence = $badge->get_criteria_completions($ibadge->recipient);
+            $eids = array_map(create_function('$o', 'return $o->critid;'), $evidence);
+            unset($badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]);
+
+            $items = array();
+            foreach ($badge->criteria as $type => $c) {
+                if (in_array($c->id, $eids)) {
+                    if (count($c->params) == 1) {
+                        $items[] = get_string('criteria_descr_single_' . $type , 'badges') . $c->get_details();
+                    } else {
+                        $items[] = get_string('criteria_descr_' . $type , 'badges',
+                                strtoupper($agg[$badge->get_aggregation_method($type)])) . $c->get_details();
+                    }
+                }
+            }
+
+            $datatable->data[] = array(get_string('evidence', 'badges'),
+                    get_string('completioninfo', 'badges') .
+                    html_writer::alist($items, array(), 'ul'));
+            $table->attributes = array('class' => 'generalbox boxaligncenter issuedbadgebox');
+            $table->data[] = array(html_writer::table($imagetable), html_writer::table($datatable));
+            $htmlbadge = html_writer::table($table);
+
+            return $htmlbadge;
+        } else {
+            return get_string('hiddenbadge', 'badges');
+        }
+    }
+
+    // Outputs external badge.
+    protected function render_external_badge(external_badge $ibadge) {
+        $issued = $ibadge->issued;
+        $assertion = $issued->assertion;
+        $issuer = $assertion->badge->issuer;
+        $table = new html_table();
+
+        $imagetable = new html_table();
+        $imagetable->attributes = array('class' => 'clearfix badgeissuedimage');
+        $imagetable->data[] = array(html_writer::empty_tag('img', array('src' => $issued->imageUrl, 'width' => '100px')));
+
+        $datatable = new html_table();
+        $datatable->attributes = array('class' => 'badgeissuedinfo');
+        $datatable->colclasses = array('bfield', 'bvalue');
+        $datatable->data[] = array($this->output->heading(get_string('issuerdetails', 'badges'), 3), '');
+        $datatable->data[] = array(get_string('issuername', 'badges'), $issuer->name);
+        $datatable->data[] = array(get_string('issuerurl', 'badges'),
+                html_writer::tag('a', $issuer->origin, array('href' => $issuer->origin)));
+        if (isset($issuer->contact)) {
+            $datatable->data[] = array(get_string('contact', 'badges'),
+                html_writer::tag('a', $issuer->contact, array('href' => 'mailto:' . $issuer->contact)));
+        }
+        $datatable->data[] = array($this->output->heading(get_string('badgedetails', 'badges'), 3), '');
+        $datatable->data[] = array(get_string('name'), $assertion->badge->name);
+        $datatable->data[] = array(get_string('description', 'badges'), $assertion->badge->description);
+        $datatable->data[] = array(get_string('bcriteria', 'badges'),
+                html_writer::tag('a', $assertion->badge->criteria, array('href' => $assertion->badge->criteria)));
+        $datatable->data[] = array($this->output->heading(get_string('issuancedetails', 'badges'), 3), '');
+        if (isset($assertion->issued_on)) {
+            $datatable->data[] = array(get_string('dateawarded', 'badges'), $assertion->issued_on);
+        }
+        if (isset($assertion->badge->expire)) {
+            $today_date = date('Y-m-d');
+            $today = strtotime($today_date);
+            $expiration = strtotime($assertion->badge->expire);
+            if ($expiration < $today) {
+                $cell = new html_table_cell($assertion->badge->expire . get_string('warnexpired', 'badges'));
+                $cell->attributes = array('class' => 'notifyproblem warning');
+                $datatable->data[] = array(get_string('expirydate', 'badges'), $cell);
+
+                $image = html_writer::start_tag('div', array('class' => 'badge'));
+                $image .= html_writer::empty_tag('img', array('src' => $issued['badge']['image']));
+                $image .= html_writer::start_tag('span', array('class' => 'expired'))
+                            . $this->output->pix_icon('i/expired',
+                                get_string('expireddate', 'badges', $assertion->badge->expire),
+                                'moodle',
+                                array('class' => 'expireimage'))
+                            . html_writer::end_tag('span');
+                $image .= html_writer::end_tag('div');
+                $imagetable->data[0] = array($image);
+            } else {
+                $datatable->data[] = array(get_string('expirydate', 'badges'), $assertion->badge->expire);
+            }
+        }
+        if (isset($assertion->evidence)) {
+            $datatable->data[] = array(get_string('evidence', 'badges'),
+                html_writer::tag('a', $assertion->evidence, array('href' => $assertion->evidence)));
+        }
+        $table->attributes = array('class' => 'generalbox boxaligncenter issuedbadgebox');
+        $table->data[] = array(html_writer::table($imagetable), html_writer::table($datatable));
+        $htmlbadge = html_writer::table($table);
+
+        return $htmlbadge;
+    }
+
+    // Outputs table of user badges.
+    protected function render_badge_user_collection(badge_user_collection $badges) {
+        global $CFG, $USER, $SITE;
+        $paging = new paging_bar($badges->totalcount, $badges->page, $badges->perpage, $this->page->url, 'page');
+        $htmlpagingbar = $this->render($paging);
+
+        // Search box.
+        $searchform = $this->output->box($this->helper_search_form($badges->search), 'boxwidthwide boxaligncenter');
+
+        // Download all button.
+        $downloadall = $this->output->single_button(
+                    new moodle_url('/badges/mybadges.php', array('downloadall' => true, 'sesskey' => sesskey())),
+                    get_string('downloadall'), 'POST', array('class' => 'activatebadge'));
+
+        // Local badges.
+        $localhtml = html_writer::start_tag('fieldset', array('class' => 'generalbox'));
+        $localhtml .= html_writer::tag('legend',
+                    $this->output->heading_with_help(get_string('localbadges', 'badges', $SITE->fullname), 'localbadgesh', 'badges'));
+        if ($badges->badges) {
+            $table = new html_table();
+            $table->attributes['class'] = 'statustable';
+            $table->data[] = array($this->output->heading(get_string('badgesearned', 'badges', $badges->totalcount), 4, 'activatebadge'), $downloadall);
+            $downloadbutton = html_writer::table($table);
+
+            $htmllist = $this->print_badges_list($badges->badges, $USER->id);
+            $localhtml .= $downloadbutton . $searchform . $htmlpagingbar . $htmllist . $htmlpagingbar;
+        } else {
+            $localhtml .= $searchform . $this->output->notification(get_string('nobadges', 'badges'));
+        }
+        $localhtml .= html_writer::end_tag('fieldset');
+
+        // External badges.
+        $backpack = $badges->backpack;
+        $externalhtml = "";
+        if ($CFG->badges_allowexternalbackpack) {
+            $externalhtml .= html_writer::start_tag('fieldset', array('class' => 'generalbox'));
+            $externalhtml .= html_writer::tag('legend', $this->output->heading_with_help(get_string('externalbadges', 'badges'), 'externalbadges', 'badges'));
+            if (!is_null($backpack)) {
+                if ($backpack->totalbadges > 0) {
+                    $externalhtml .= get_string('backpackbadges', 'badges', $backpack);
+                } else {
+                    $externalhtml .= get_string('nobackpackbadges', 'badges', $backpack);
+                }
+                $label = get_string('editsettings', 'badges');
+                $externalhtml .= $this->output->single_button(
+                        new moodle_url('mybackpack.php', array('clear' => true)),
+                        get_string('clearsettings', 'badges'),
+                        'POST',
+                        array('class' => 'backpackform'));
+            } else {
+                $externalhtml .= get_string('nobackpack', 'badges');
+                $label = get_string('setup', 'badges');
+            }
+            $externalhtml .= $this->output->single_button('mybackpack.php', $label, 'POST', array('class' => 'backpackform'));
+
+            if (isset($backpack->totalbadges) && $backpack->totalbadges !== 0) {
+                $externalhtml .= '<br/><br/>' . $this->print_badges_list($backpack->badges, $USER->id, true, true);
+            }
+            $externalhtml .= html_writer::end_tag('fieldset');
+        }
+
+        return $localhtml . $externalhtml;
+    }
+
+    // Outputs table of available badges.
+    protected function render_badge_collection(badge_collection $badges) {
+        $paging = new paging_bar($badges->totalcount, $badges->page, $badges->perpage, $this->page->url, 'page');
+        $htmlpagingbar = $this->render($paging);
+        $table = new html_table();
+        $table->attributes['class'] = 'collection boxaligncenter boxwidthwide';
+
+        $sortbyname = $this->helper_sortable_heading(get_string('name'),
+                'name', $badges->sort, $badges->dir);
+        $sortbyawarded = $this->helper_sortable_heading(get_string('awardedtoyou', 'badges'),
+                'dateissued', $badges->sort, $badges->dir);
+        $table->head = array(
+                    get_string('badgeimage', 'badges'),
+                    $sortbyname,
+                    get_string('description', 'badges'),
+                    get_string('bcriteria', 'badges'),
+                    $sortbyawarded
+                );
+        $table->colclasses = array('badgeimage', 'name', 'description', 'criteria', 'awards');
+
+        foreach ($badges->badges as $badge) {
+            $badgeimage = print_badge_image($badge, $this->page->context, 'large');
+            $name = $badge->name;
+            $description = $badge->description;
+            $criteria = self::print_badge_criteria($badge);
+            if ($badge->dateissued) {
+                $icon = new pix_icon('i/tick_green_big',
+                            get_string('dateearned', 'badges',
+                                userdate($badge->dateissued, get_string('strftimedatefullshort', 'core_langconfig'))));
+                $badgeurl = new moodle_url('/badges/badge.php', array('hash' => $badge->uniquehash));
+                $awarded = $this->output->action_icon($badgeurl, $icon, null, null, true);
+            } else {
+                $awarded = "";
+            }
+            $row = array($badgeimage, $name, $description, $criteria, $awarded);
+            $table->data[] = $row;
+        }
+
+        $htmltable = html_writer::table($table);
+
+        return $htmlpagingbar . $htmltable . $htmlpagingbar;
+    }
+
+    // Outputs table of badges with actions available.
+    protected function render_badge_management(badge_management $badges) {
+        $paging = new paging_bar($badges->totalcount, $badges->page, $badges->perpage, $this->page->url, 'page');
+
+        // New badge button.
+        $n['type'] = $this->page->url->get_param('type');
+        $n['id'] = $this->page->url->get_param('id');
+        $htmlnew = $this->output->single_button(new moodle_url('newbadge.php', $n), get_string('newbadge', 'badges'));
+
+        $htmlpagingbar = $this->render($paging);
+        $table = new html_table();
+        $table->attributes['class'] = 'collection';
+
+        $sortbyname = $this->helper_sortable_heading(get_string('name'),
+                'name', $badges->sort, $badges->dir);
+        $sortbystatus = $this->helper_sortable_heading(get_string('status', 'badges'),
+                'status', $badges->sort, $badges->dir);
+        $table->head = array(
+                $sortbyname,
+                $sortbystatus,
+                get_string('bcriteria', 'badges'),
+                get_string('awards', 'badges'),
+                get_string('actions')
+            );
+        $table->colclasses = array('name', 'status', 'criteria', 'awards', 'actions');
+
+        foreach ($badges->badges as $b) {
+            $style = !$b->is_active() ? array('class' => 'dimmed') : array();
+            $forlink =  print_badge_image($b, $this->page->context) . ' ' .
+                        html_writer::start_tag('span') . $b->name . html_writer::end_tag('span');
+            $name = html_writer::link(new moodle_url('/badges/overview.php', array('id' => $b->id)), $forlink, $style);
+            $status = $b->statstring;
+            $criteria = self::print_badge_criteria($b, 'short');
+
+            if (has_capability('moodle/badges:viewawarded', $this->page->context)) {
+                $awards = html_writer::link(new moodle_url('/badges/recipients.php', array('id' => $b->id)), $b->awards);
+            } else {
+                $awards = $b->awards;
+            }
+
+            $actions = self::print_badge_table_actions($b, $this->page->context);
+
+            $row = array($name, $status, $criteria, $awards, $actions);
+            $table->data[] = $row;
+        }
+        $htmltable = html_writer::table($table);
+
+        return $htmlnew . $htmlpagingbar . $htmltable . $htmlpagingbar;
+    }
+
+    // Prints tabs for badge editing.
+    public function print_badge_tabs($badgeid, $context, $current = 'overview') {
+        global $DB;
+
+        $tabs = $row = array();
+
+        $row[] = new tabobject('overview',
+                    new moodle_url('/badges/overview.php', array('id' => $badgeid)),
+                    get_string('boverview', 'badges')
+                );
+
+        if (has_capability('moodle/badges:configuredetails', $context)) {
+            $row[] = new tabobject('details',
+                        new moodle_url('/badges/edit.php', array('id' => $badgeid, 'action' => 'details')),
+                        get_string('bdetails', 'badges')
+                    );
+        }
+
+        if (has_capability('moodle/badges:configurecriteria', $context)) {
+            $row[] = new tabobject('criteria',
+                        new moodle_url('/badges/criteria.php', array('id' => $badgeid)),
+                        get_string('bcriteria', 'badges')
+                    );
+        }
+
+        if (has_capability('moodle/badges:configuremessages', $context)) {
+            $row[] = new tabobject('message',
+                        new moodle_url('/badges/edit.php', array('id' => $badgeid, 'action' => 'message')),
+                        get_string('bmessage', 'badges')
+                    );
+        }
+
+        if (has_capability('moodle/badges:viewawarded', $context)) {
+            $awarded = $DB->count_records('badge_issued', array('badgeid' => $badgeid));
+            $row[] = new tabobject('awards',
+                        new moodle_url('/badges/recipients.php', array('id' => $badgeid)),
+                        get_string('bawards', 'badges', $awarded)
+                    );
+        }
+
+        $tabs[] = $row;
+
+        print_tabs($tabs, $current);
+    }
+
+    // Prints badge status box.
+    public function print_badge_status_box(badge $badge) {
+        $table = new html_table();
+        $table->attributes['class'] = 'boxaligncenter statustable';
+
+        if (has_capability('moodle/badges:configurecriteria', $badge->get_context())) {
+            if (!$badge->has_criteria()) {
+                $criteriaurl = new moodle_url('/badges/criteria.php', array('id' => $badge->id));
+                $status = get_string('nocriteria', 'badges');
+                if ($this->page->url != $criteriaurl) {
+                    $action = $this->output->single_button(
+                        $criteriaurl,
+                        get_string('addcriteria', 'badges'), 'POST', array('class' => 'activatebadge'));
+                } else {
+                    $action = '';
+                }
+                $row = array($status, $action);
+            } else {
+                $status = get_string('statusmessage_' . $badge->status, 'badges');
+                if ($badge->is_active()) {
+                    $action = $this->output->single_button(new moodle_url('/badges/action.php',
+                                array('id' => $badge->id, 'lock' => 1, 'sesskey' => sesskey(),
+                                      'return' => $this->page->url->out_as_local_url(false))),
+                            get_string('deactivate', 'badges'), 'POST', array('class' => 'activatebadge'));
+                } else {
+                    $action = $this->output->single_button(new moodle_url('/badges/action.php',
+                                array('id' => $badge->id, 'activate' => 1, 'sesskey' => sesskey(),
+                                      'return' => $this->page->url->out_as_local_url(false))),
+                            get_string('activate', 'badges'), 'POST', array('class' => 'activatebadge'));
+                }
+                $row = array($status . $this->output->help_icon('status', 'badges'), $action);
+            }
+        }
+
+        $table->data[] = $row;
+
+        $style = $badge->is_active() ? 'generalbox statusbox active' : 'generalbox statusbox inactive';
+        return $this->output->box(html_writer::table($table), $style);
+    }
+
+    // Prints badge criteria.
+    public function print_badge_criteria(badge $badge, $short = '') {
+        $output = "";
+        $agg = $badge->get_aggregation_methods();
+        if (empty($badge->criteria)) {
+            return get_string('nocriteria', 'badges');
+        } else if (count($badge->criteria) == 2) {
+            if (!$short) {
+                $output .= get_string('criteria_descr', 'badges');
+            }
+        } else {
+            $output .= get_string('criteria_descr_' . $short . BADGE_CRITERIA_TYPE_OVERALL, 'badges',
+                                    strtoupper($agg[$badge->get_aggregation_method()]));
+        }
+        $items = array();
+        unset($badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]);
+        foreach ($badge->criteria as $type => $c) {
+            if (count($c->params) == 1) {
+                $items[] = get_string('criteria_descr_single_' . $short . $type , 'badges') . $c->get_details($short);
+            } else {
+                $items[] = get_string('criteria_descr_' . $short . $type , 'badges',
+                        strtoupper($agg[$badge->get_aggregation_method($type)])) . $c->get_details($short);
+            }
+        }
+        $output .= html_writer::alist($items, array(), 'ul');
+        return $output;
+    }
+
+    // Prints criteria actions for badge editing.
+    public function print_criteria_actions(badge $badge) {
+        $table = new html_table();
+        $table->attributes = array('class' => 'boxaligncenter', 'id' => 'badgeactions');
+        $table->colclasses = array('activatebadge');
+
+        $actions = array();
+        if (!$badge->is_active() && !$badge->is_locked()) {
+            $accepted = $badge->get_accepted_criteria();
+            $potential = array_diff($accepted, array_keys($badge->criteria));
+
+            if (!empty($potential)) {
+                foreach ($potential as $p) {
+                    if ($p != 0) {
+                        $select[$p] = get_string('criteria_' . $p, 'badges');
+                    }
+                }
+                $actions[] = get_string('addbadgecriteria', 'badges');
+                $actions[] = $this->output->single_select(new moodle_url('/badges/criteria_settings.php',
+                        array('badgeid' => $badge->id, 'add' => true)), 'type', $select);
+            } else {
+                $actions[] = $this->output->box(get_string('nothingtoadd', 'badges'), 'clearfix');
+            }
+        }
+
+        $table->data[] = $actions;
+        return html_writer::table($table);
+    }
+
+    // Renders a table with users who have earned the badge.
+    // Based on stamps collection plugin.
+    protected function render_badge_recipients(badge_recipients $recipients) {
+        $paging = new paging_bar($recipients->totalcount, $recipients->page, $recipients->perpage, $this->page->url, 'page');
+        $htmlpagingbar = $this->render($paging);
+        $table = new html_table();
+        $table->attributes['class'] = 'generaltable generalbox boxaligncenter boxwidthwide';
+
+        $sortbyfirstname = $this->helper_sortable_heading(get_string('firstname'),
+                'firstname', $recipients->sort, $recipients->dir);
+        $sortbylastname = $this->helper_sortable_heading(get_string('lastname'),
+                'lastname', $recipients->sort, $recipients->dir);
+        if ($this->helper_fullname_format() == 'lf') {
+            $sortbyname = $sortbylastname . ' / ' . $sortbyfirstname;
+        } else {
+            $sortbyname = $sortbyfirstname . ' / ' . $sortbylastname;
+        }
+
+        $sortbydate = $this->helper_sortable_heading(get_string('dateawarded', 'badges'),
+                'dateissued', $recipients->sort, $recipients->dir);
+
+        $table->head = array($sortbyname, $sortbydate, '');
+
+        foreach ($recipients->userids as $holder) {
+            $fullname = fullname($holder);
+            $fullname = html_writer::link(
+                            new moodle_url('/user/profile.php', array('id' => $holder->userid)),
+                            $fullname
+                        );
+            $awarded  = userdate($holder->dateissued);
+            $badgeurl = html_writer::link(
+                            new moodle_url('/badges/badge.php', array('hash' => $holder->uniquehash)),
+                            get_string('viewbadge', 'badges')
+                        );
+
+            $row = array($fullname, $awarded, $badgeurl);
+            $table->data[] = $row;
+        }
+
+        $htmltable = html_writer::table($table);
+
+        return $htmlpagingbar . $htmltable . $htmlpagingbar;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Helper methods
+    // Reused from stamps collection plugin
+    ////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Renders a text with icons to sort by the given column
+     *
+     * This is intended for table headings.
+     *
+     * @param string $text    The heading text
+     * @param string $sortid  The column id used for sorting
+     * @param string $sortby  Currently sorted by (column id)
+     * @param string $sorthow Currently sorted how (ASC|DESC)
+     *
+     * @return string
+     */
+    protected function helper_sortable_heading($text, $sortid = null, $sortby = null, $sorthow = null) {
+        $out = html_writer::tag('span', $text, array('class' => 'text'));
+
+        if (!is_null($sortid)) {
+            if ($sortby !== $sortid || $sorthow !== 'ASC') {
+                $url = new moodle_url($this->page->url);
+                $url->params(array('sort' => $sortid, 'dir' => 'ASC'));
+                $out .= $this->output->action_icon($url,
+                        new pix_icon('t/sort_asc', get_string('sortbyx', 'core', s($text)), null, array('class' => 'iconsort')));
+            }
+            if ($sortby !== $sortid || $sorthow !== 'DESC') {
+                $url = new moodle_url($this->page->url);
+                $url->params(array('sort' => $sortid, 'dir' => 'DESC'));
+                $out .= $this->output->action_icon($url,
+                        new pix_icon('t/sort_desc', get_string('sortbyxreverse', 'core', s($text)), null, array('class' => 'iconsort')));
+            }
+        }
+        return $out;
+    }
+    /**
+     * Tries to guess the fullname format set at the site
+     *
+     * @return string fl|lf
+     */
+    protected function helper_fullname_format() {
+        $fake = new stdClass();
+        $fake->lastname = 'LLLL';
+        $fake->firstname = 'FFFF';
+        $fullname = get_string('fullnamedisplay', '', $fake);
+        if (strpos($fullname, 'LLLL') < strpos($fullname, 'FFFF')) {
+            return 'lf';
+        } else {
+            return 'fl';
+        }
+    }
+    /**
+     * Renders a search form
+     *
+     * @param string $search Search string
+     * @return string HTML
+     */
+    protected function helper_search_form($search) {
+        global $CFG;
+        require_once($CFG->libdir . '/formslib.php');
+
+        $mform = new MoodleQuickForm('searchform', 'POST', $this->page->url);
+
+        $mform->addElement('hidden', 'sesskey', sesskey());
+
+        $el[] = $mform->createElement('text', 'search', get_string('search'), array('size' => 20));
+        $mform->setDefault('search', $search);
+        $el[] = $mform->createElement('submit', 'submitsearch', get_string('search'));
+        $el[] = $mform->createElement('submit', 'clearsearch', get_string('clear'));
+        $mform->addGroup($el, 'searchgroup', get_string('searchname', 'badges'), ' ', false);
+
+        ob_start();
+        $mform->display();
+        $out = ob_get_clean();
+
+        return $out;
+    }
+}
+
+/**
+ * An issued badges for badge.php page
+ */
+class issued_badge implements renderable {
+    /** @var issued badge */
+    public $issued;
+
+    /** @var badge recipient */
+    public $recipient = 0;
+
+    /** @var badge visibility to others */
+    public $visible = 0;
+
+    /** @var badge class */
+    public $badgeid = 0;
+
+    /** @var issued badge unique hash */
+    public $hash = "";
+
+    /**
+     * Initializes the badge to display
+     *
+     * @param string $hash Issued badge hash
+     */
+    public function __construct($hash) {
+        global $DB;
+        $this->issued = badges_get_issued_badge_info($hash);
+        $this->hash = $hash;
+
+        $rec = $DB->get_record_sql('SELECT userid, visible, badgeid
+                FROM {badge_issued}
+                WHERE ' . $DB->sql_compare_text('uniquehash', 40) . ' = ' . $DB->sql_compare_text(':hash', 40),
+                array('hash' => $hash), IGNORE_MISSING);
+        if ($rec) {
+            $this->recipient = $rec->userid;
+            $this->visible = $rec->visible;
+            $this->badgeid = $rec->badgeid;
+        }
+    }
+}
+
+/**
+ * An external badges for external.php page
+ */
+class external_badge implements renderable {
+    /** @var issued badge */
+    public $issued;
+
+    /**
+     * Initializes the badge to display
+     *
+     * @param string $json External badge information.
+     */
+    public function __construct($json) {
+        $this->issued = $json;
+    }
+}
+
+/**
+ * Badge recipients rendering class
+ */
+class badge_recipients implements renderable {
+    /** @var string how are the data sorted */
+    public $sort = 'lastname';
+
+    /** @var string how are the data sorted */
+    public $dir = 'ASC';
+
+    /** @var int page number to display */
+    public $page = 0;
+
+    /** @var int number of badge recipients to display per page */
+    public $perpage = 30;
+
+    /** @var int the total number or badge recipients to display */
+    public $totalcount = null;
+
+    /** @var array internal list of  badge recipients ids */
+    public $userids = array();
+    /**
+     * Initializes the list of users to display
+     *
+     * @param array $holders List of badge holders
+     */
+    public function __construct($holders) {
+        $this->userids = $holders;
+    }
+}
+
+/**
+ * Collection of all badges for view.php page
+ */
+class badge_collection implements renderable {
+
+    /** @var string how are the data sorted */
+    public $sort = 'name';
+
+    /** @var string how are the data sorted */
+    public $dir = 'ASC';
+
+    /** @var int page number to display */
+    public $page = 0;
+
+    /** @var int number of badges to display per page */
+    public $perpage = BADGE_PERPAGE;
+
+    /** @var int the total number of badges to display */
+    public $totalcount = null;
+
+    /** @var array list of badges */
+    public $badges = array();
+
+    /**
+     * Initializes the list of badges to display
+     *
+     * @param array $badges Badges to render
+     */
+    public function __construct($badges) {
+        $this->badges = $badges;
+    }
+}
+
+/**
+ * Collection of badges used at the index.php page
+ */
+class badge_management extends badge_collection implements renderable {
+}
+
+/**
+ * Collection of user badges used at the mybadges.php page
+ */
+class badge_user_collection extends badge_collection implements renderable {
+    /** @var array backpack settings */
+    public $backpack;
+
+    /** @var string search */
+    public $search = '';
+
+    /**
+     * Initializes user badge collection.
+     *
+     * @param array $badges Badges to render
+     * @param int $userid Badges owner
+     */
+    public function __construct($badges, $userid) {
+        parent::__construct($badges);
+        $this->backpack = get_backpack_settings($userid);
+    }
+}
diff --git a/badges/tests/badgeslib_test.php b/badges/tests/badgeslib_test.php
new file mode 100644 (file)
index 0000000..37c6707
--- /dev/null
@@ -0,0 +1,188 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Unit tests for badges
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2013 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->libdir . '/badgeslib.php');
+
+class badges_testcase extends advanced_testcase {
+    protected $badgeid;
+
+    protected function setUp() {
+        global $DB;
+        $this->resetAfterTest(true);
+
+        $user = $this->getDataGenerator()->create_user();
+
+        $fordb = new stdClass();
+        $fordb->id = null;
+        $fordb->name = "Test badge";
+        $fordb->description = "Testing badges";
+        $fordb->timecreated = time();
+        $fordb->timemodified = time();
+        $fordb->usercreated = $user->id;
+        $fordb->usermodified = $user->id;
+        $fordb->image = 0;
+        $fordb->issuername = "Test issuer";
+        $fordb->issuerurl = "http://issuer-url.domain.co.nz";
+        $fordb->expiredate = null;
+        $fordb->expireperiod = null;
+        $fordb->type = BADGE_TYPE_SITE;
+        $fordb->courseid = null;
+        $fordb->messagesubject = "Test message subject";
+        $fordb->message = "Test message body";
+        $fordb->attachment = 1;
+        $fordb->notification = 0;
+        $fordb->status = BADGE_STATUS_INACTIVE;
+
+        $this->badgeid = $DB->insert_record('badge', $fordb, true);
+    }
+
+    public function test_create_badge() {
+        $badge = new badge($this->badgeid);
+
+        $this->assertInstanceOf('badge', $badge);
+        $this->assertEquals($this->badgeid, $badge->id);
+    }
+
+    public function test_clone_badge() {
+        $badge = new badge($this->badgeid);
+        $newid = $badge->make_clone();
+        $cloned_badge = new badge($newid);
+
+        $this->assertEquals($badge->image, $cloned_badge->image);
+        $this->assertEquals($badge->description, $cloned_badge->description);
+        $this->assertEquals($badge->issuercontact, $cloned_badge->issuercontact);
+        $this->assertEquals($badge->issuername, $cloned_badge->issuername);
+        $this->assertEquals($badge->issuerurl, $cloned_badge->issuerurl);
+        $this->assertEquals($badge->expiredate, $cloned_badge->expiredate);
+        $this->assertEquals($badge->expireperiod, $cloned_badge->expireperiod);
+        $this->assertEquals($badge->type, $cloned_badge->type);
+        $this->assertEquals($badge->courseid, $cloned_badge->courseid);
+        $this->assertEquals($badge->message, $cloned_badge->message);
+        $this->assertEquals($badge->messagesubject, $cloned_badge->messagesubject);
+        $this->assertEquals($badge->attachment, $cloned_badge->attachment);
+        $this->assertEquals($badge->notification, $cloned_badge->notification);
+    }
+
+    public function test_badge_status() {
+        $badge = new badge($this->badgeid);
+        $old_status = $badge->status;
+        $badge->set_status(BADGE_STATUS_ACTIVE);
+        $this->assertAttributeNotEquals($old_status, 'status', $badge);
+        $this->assertAttributeEquals(BADGE_STATUS_ACTIVE, 'status', $badge);
+    }
+
+    public function test_delete_badge() {
+        $badge = new badge($this->badgeid);
+        $badge->delete();
+        // We don't actually delete badges. We archive them.
+        $this->assertAttributeEquals(BADGE_STATUS_ARCHIVED, 'status', $badge);
+    }
+
+    public function test_create_badge_criteria() {
+        $badge = new badge($this->badgeid);
+        $criteria_overall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_OVERALL, 'badgeid' => $badge->id));
+        $criteria_overall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ALL));
+
+        $this->assertCount(1, $badge->get_criteria());
+
+        $criteria_profile = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_PROFILE, 'badgeid' => $badge->id));
+        $params = array('agg' => BADGE_CRITERIA_AGGREGATION_ALL, 'field_address' => 'address');
+        $criteria_profile->save($params);
+
+        $this->assertCount(2, $badge->get_criteria());
+    }
+
+    public function test_delete_badge_criteria() {
+        $criteria_overall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_OVERALL, 'badgeid' => $this->badgeid));
+        $criteria_overall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ALL));
+        $badge = new badge($this->badgeid);
+
+        $this->assertInstanceOf('award_criteria_overall', $badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]);
+
+        $badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->delete();
+        $this->assertEmpty($badge->get_criteria());
+    }
+
+    public function test_badge_awards() {
+        $badge = new badge($this->badgeid);
+        $user1 = $this->getDataGenerator()->create_user();
+
+        $badge->issue($user1->id, true);
+        $this->assertTrue($badge->is_issued($user1->id));
+
+        $user2 = $this->getDataGenerator()->create_user();
+        $badge->issue($user2->id, true);
+        $this->assertTrue($badge->is_issued($user2->id));
+
+        $this->assertCount(2, $badge->get_awards());
+    }
+
+    public function data_for_message_from_template() {
+        return array(
+            array(
+                'This is a message with no variables',
+                array(), // no params
+                'This is a message with no variables'
+            ),
+            array(
+                'This is a message with %amissing% variables',
+                array(), // no params
+                'This is a message with %amissing% variables'
+            ),
+            array(
+                'This is a message with %one% variable',
+                array('one' => 'a single'),
+                'This is a message with a single variable'
+            ),
+            array(
+                'This is a message with %one% %two% %three% variables',
+                array('one' => 'more', 'two' => 'than', 'three' => 'one'),
+                'This is a message with more than one variables'
+            ),
+            array(
+                'This is a message with %three% %two% %one%',
+                array('one' => 'variables', 'two' => 'ordered', 'three' => 'randomly'),
+                'This is a message with randomly ordered variables'
+            ),
+            array(
+                'This is a message with %repeated% %one% %repeated% of variables',
+                array('one' => 'and', 'repeated' => 'lots'),
+                'This is a message with lots and lots of variables'
+            ),
+        );
+    }
+
+    /**
+     * @dataProvider data_for_message_from_template
+     */
+    public function test_badge_message_from_template($message, $params, $result) {
+        $this->assertEquals(badge_message_from_template($message, $params), $result);
+    }
+
+}
diff --git a/badges/tests/behat/add_badge.feature b/badges/tests/behat/add_badge.feature
new file mode 100644 (file)
index 0000000..3b92100
--- /dev/null
@@ -0,0 +1,46 @@
+@badges
+Feature: Add badges to the system
+  In order to give badges to users for their achievements
+  As an admin
+  I need to manage badges in the system
+
+  Background:
+    Given I am on homepage
+    And I log in as "admin"
+
+  @javascript
+  Scenario: Setting badges settings
+    Given I expand "Site administration" node
+    And I expand "Badges" node
+    And I follow "Badges settings"
+    And I fill in "Default badge issuer name" with "Test Badge Site"
+    And I fill in "Default badge issuer contact details" with "testuser@test-badge-site.com"
+    And I press "Save changes"
+    When I follow "Add a new badge"
+    Then the "issuercontact" field should match "testuser@test-badge-site.com" value
+    And the "issuername" field should match "Test Badge Site" value
+
+  @javascript
+  Scenario: Accessing the badges
+    Given I expand "Site pages" node
+    And I follow "Site badges"
+    Then I should see "There are no badges available."
+
+  @javascript
+  Scenario: Add a badge
+    Given I expand "Site administration" node
+    And I expand "Badges" node
+    And I follow "Add a new badge"
+    And I fill the moodle form with:
+      | Name | Test Badge |
+      | Description | Test badge description |
+      | issuername | Test Badge Site |
+      | issuercontact | testuser@test-badge-site.com |
+    And I upload "badges/tests/behat/badge.png" file to "Image" filepicker
+    When I press "Create badge"
+    Then I should see "Edit details"
+    And I should see "Test Badge"
+    And I should not see "Create badge"
+    And I follow "Manage badges"
+    And I should see "Number of badges available: 1"
+    And I should not see "There are no badges available."
diff --git a/badges/tests/behat/award_badge.feature b/badges/tests/behat/award_badge.feature
new file mode 100644 (file)
index 0000000..affd123
--- /dev/null
@@ -0,0 +1,58 @@
+@award_badges
+Feature: Award badges
+  In order to award badges to users for their achievements
+  As an admin
+  I need to add criteria to badges in the system
+
+  Background:
+    Given I am on homepage
+    And I log in as "admin"
+
+  @javascript
+  Scenario: Add criteria
+    Given I expand "Site administration" node
+    And I expand "Badges" node
+    And I follow "Add a new badge"
+    And I fill the moodle form with:
+      | Name | Test Badge |
+      | Description | Test badge description |
+      | issuername | Test Badge Site |
+      | issuercontact | testuser@test-badge-site.com |
+    And I upload "badges/tests/behat/badge.png" file to "Image" filepicker
+    And I press "Create badge"
+    And I select "Profile completion" from "type"
+    And I wait "5" seconds
+    And I check "First name"
+    And I check "Email address"
+    When I press "Save"
+    Then I should see "Profile completion"
+    And I should see "First name"
+    And I should see "Email address"
+    And I should not see "Criteria for this badge have not been set up yet."
+
+  @javascript
+  Scenario: Earn badge
+    Given I expand "Site administration" node
+    And I expand "Badges" node
+    And I follow "Add a new badge"
+    And I fill the moodle form with:
+      | Name | Profile Badge |
+      | Description | Test badge description |
+      | issuername | Test Badge Site |
+      | issuercontact | testuser@test-badge-site.com |
+    And I upload "badges/tests/behat/badge.png" file to "Image" filepicker
+    And I press "Create badge"
+    And I select "Profile completion" from "type"
+    And I wait "5" seconds
+    And I check "Phone"
+    And I press "Save"
+    And I press "Enable access"
+    And I press "Continue"
+    And I expand "My profile settings" node
+    And I follow "Edit profile"
+    And I expand all fieldsets
+    And I fill in "Phone" with "123456789"
+    And I press "Update profile"
+    When I follow "My badges"
+    Then I should see "Profile Badge"
+    And I should not see "There are no badges available."
diff --git a/badges/tests/behat/badge.png b/badges/tests/behat/badge.png
new file mode 100644 (file)
index 0000000..d9324f5
Binary files /dev/null and b/badges/tests/behat/badge.png differ
diff --git a/badges/view.php b/badges/view.php
new file mode 100644 (file)
index 0000000..b4a5c2a
--- /dev/null
@@ -0,0 +1,106 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Displays available badges to a user
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+require_once(dirname(dirname(__FILE__)) . '/config.php');
+require_once($CFG->libdir . '/badgeslib.php');
+
+$type       = required_param('type', PARAM_INT);
+$courseid   = optional_param('id', 0, PARAM_INT);
+$sortby     = optional_param('sort', 'name', PARAM_ALPHA);
+$sorthow    = optional_param('dir', 'DESC', PARAM_ALPHA);
+$page       = optional_param('page', 0, PARAM_INT);
+
+require_login();
+
+if (empty($CFG->enablebadges)) {
+    print_error('badgesdisabled', 'badges');
+}
+
+if (!in_array($sortby, array('name', 'dateissued'))) {
+    $sortby = 'name';
+}
+
+if ($sorthow != 'ASC' && $sorthow != 'DESC') {
+    $sorthow = 'ACS';
+}
+
+if ($page < 0) {
+    $page = 0;
+}
+
+if ($course = $DB->get_record('course', array('id' => $courseid))) {
+    $PAGE->set_url('/badges/view.php', array('type' => $type, 'id' => $course->id, 'sort' => $sortby, 'dir' => $sorthow));
+} else {
+    $PAGE->set_url('/badges/view.php', array('type' => $type, 'sort' => $sortby, 'dir' => $sorthow));
+}
+
+if ($type == BADGE_TYPE_SITE) {
+    $title = get_string('sitebadges', 'badges');
+    $PAGE->set_context(context_system::instance());
+    $PAGE->set_pagelayout('admin');
+    $PAGE->set_heading($title);
+} else {
+    require_login($course);
+    $title = $course->fullname . ': ' . get_string('coursebadges', 'badges');
+    $PAGE->set_context(context_course::instance($course->id));
+    $PAGE->set_pagelayout('course');
+    $PAGE->set_heading($title);
+
+    // Fix course navigation.
+    $PAGE->navbar->ignore_active();
+    $PAGE->navbar->add($course->shortname, new moodle_url('/course/view.php', array('id' => $course->id)));
+    $PAGE->navbar->add(get_string('coursebadges', 'badges'));
+}
+
+$PAGE->set_title($title);
+$output = $PAGE->get_renderer('core', 'badges');
+
+echo $output->header();
+echo $OUTPUT->heading($title);
+
+$totalcount = count(badges_get_badges($type, $courseid, '', '', '', '', $USER->id));
+$records = badges_get_badges($type, $courseid, $sortby, $sorthow, $page, BADGE_PERPAGE, $USER->id);
+
+if ($totalcount) {
+    echo $output->heading(get_string('badgestoearn', 'badges', $totalcount), 4);
+
+    if ($course && $course->startdate > time()) {
+        echo $OUTPUT->box(get_string('error:notifycoursedate', 'badges'), 'generalbox notifyproblem');
+    }
+
+    $badges             = new badge_collection($records);
+    $badges->sort       = $sortby;
+    $badges->dir        = $sorthow;
+    $badges->page       = $page;
+    $badges->perpage    = BADGE_PERPAGE;
+    $badges->totalcount = $totalcount;
+
+    echo $output->render($badges);
+} else {
+    echo $output->notification(get_string('nobadges', 'badges'));
+}
+
+echo $output->footer();
\ No newline at end of file
diff --git a/blocks/badges/block_badges.php b/blocks/badges/block_badges.php
new file mode 100644 (file)
index 0000000..2968a23
--- /dev/null
@@ -0,0 +1,100 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Block for displaying earned local badges to users
+ *
+ * @package    block_badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir . "/badgeslib.php");
+
+/**
+ * Displays recent badges
+ */
+class block_badges extends block_base {
+
+    public function init() {
+        $this->title = get_string('pluginname', 'block_badges');
+    }
+
+    public function instance_allow_multiple() {
+        return true;
+    }
+
+    public function has_config() {
+        return false;
+    }
+
+    public function instance_allow_config() {
+        return true;
+    }
+
+    public function applicable_formats() {
+        return array(
+                'admin' => false,
+                'site-index' => true,
+                'course-view' => false,
+                'mod' => false,
+                'my' => true
+        );
+    }
+
+    public function specialization() {
+        if (empty($this->config->title)) {
+            $this->title = get_string('pluginname', 'block_badges');
+        } else {
+            $this->title = $this->config->title;
+        }
+    }
+
+    public function get_content() {
+        global $USER, $PAGE, $CFG;
+
+        if ($this->content !== null) {
+            return $this->content;
+        }
+
+        if (empty($this->config)) {
+            $this->config = new stdClass();
+        }
+
+        // Number of badges to display.
+        if (empty($this->config->numberofbadges)) {
+            $this->config->numberofbadges = 10;
+        }
+
+        // Create empty content.
+        $this->content = new stdClass();
+        $this->content->text = '';
+
+        if (empty($CFG->enablebadges)) {
+            $this->content->text .= get_string('badgesdisabled', 'badges');
+        } else if ($badges = badges_get_user_badges($USER->id, null, 0, $this->config->numberofbadges)) {
+            $output = $PAGE->get_renderer('core', 'badges');
+            $this->content->text = $output->print_badges_list($badges, $USER->id, true);
+        } else {
+            $this->content->text .= get_string('nothingtodisplay', 'block_badges');
+        }
+
+        return $this->content;
+    }
+}
\ No newline at end of file
diff --git a/blocks/badges/db/access.php b/blocks/badges/db/access.php
new file mode 100644 (file)
index 0000000..3470f53
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * My latest badges block capabilities.
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+$capabilities = array(
+        'block/badges:addinstance' => array(
+                'riskbitmask'  => RISK_PERSONAL,
+                'captype'      => 'read',
+                'contextlevel' => CONTEXT_SYSTEM,
+                'archetypes'   => array(
+                    'user' => CAP_ALLOW,
+                ),
+                'clonepermissionsfrom' => 'moodle/site:manageblocks'
+        ),
+        'block/badges:myaddinstance' => array(
+                'riskbitmask'  => RISK_PERSONAL,
+                'captype'      => 'read',
+                'contextlevel' => CONTEXT_SYSTEM,
+                'archetypes'   => array(
+                        'user' => CAP_ALLOW,
+                ),
+                'clonepermissionsfrom' => 'moodle/my:manageblocks'
+        ),
+);
\ No newline at end of file
diff --git a/blocks/badges/edit_form.php b/blocks/badges/edit_form.php
new file mode 100644 (file)
index 0000000..eb0767d
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Form for editing badges block instances.
+ *
+ * @package    block_badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+class block_badges_edit_form extends block_edit_form {
+    protected function specific_definition($mform) {
+        $mform->addElement('header', 'configheader', get_string('blocksettings', 'block'));
+
+        $numberofbadges = array('0' => get_string('all'));
+        for ($i = 1; $i <= 20; $i++) {
+            $numberofbadges[$i] = $i;
+        }
+
+        $mform->addElement('select', 'config_numberofbadges', get_string('numbadgestodisplay', 'block_badges'), $numberofbadges);
+        $mform->setDefault('config_numberofbadges', 10);
+    }
+}
\ No newline at end of file
diff --git a/blocks/badges/lang/en/block_badges.php b/blocks/badges/lang/en/block_badges.php
new file mode 100644 (file)
index 0000000..93c71ed
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Language file for block "badges"
+ *
+ * @package    block_badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+$string['pluginname'] = 'My latest badges';
+$string['numbadgestodisplay'] = 'Number of latest badges to display';
+$string['nothingtodisplay'] = 'You have no badges to display';
+$string['badges:addinstance'] = 'Add a new My latest badges block';
+$string['badges:myaddinstance'] = 'Add a new My latest badges block to the My Moodle page';
\ No newline at end of file
diff --git a/blocks/badges/version.php b/blocks/badges/version.php
new file mode 100644 (file)
index 0000000..6b62bb7
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Version details
+ *
+ * @package    block_badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$plugin->version   = 2012110600;        // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires  = 2012061700;        // Requires this Moodle version.
+$plugin->component = 'block_badges';
\ No newline at end of file
diff --git a/lang/en/badges.php b/lang/en/badges.php
new file mode 100644 (file)
index 0000000..82be867
--- /dev/null
@@ -0,0 +1,337 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Language file for 'badges' component
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+$string['actions'] = 'Actions';
+$string['activate'] = 'Enable access';
+$string['activatesuccess'] = 'Access to the badges was successfully enabled.';
+$string['addbadgecriteria'] = 'Add badge criteria';
+$string['addcriteria'] = 'Add criteria';
+$string['addcriteriatext'] = 'To start adding criteria, please select one of the options from the drop down menu.';
+$string['addcourse'] = 'Add courses';
+$string['addcourse_help'] = 'Select all courses that should be added to this badge requirement. Hold CTRL key to select multiple items.';
+$string['addtobackpack'] = 'Add to backpack';
+$string['adminaward'] = 'As a site administrator, you can select a role to award this badge.';
+$string['adminonly'] = 'This page is restricted to Site Administrators only';
+$string['after'] = 'after the date of issue.';
+$string['aggregationmethod'] = 'Aggregation method';
+$string['all'] = 'All';
+$string['allmethod'] = 'All of the selected conditions are met';
+$string['allmethodactivity'] = 'All of the selected activities are complete';
+$string['allmethodcourseset'] = 'All of the selected courses are complete';
+$string['allmethodmanual'] = 'All of the selected roles award the badge';
+$string['allmethodprofile'] = 'All of the selected profile fields have been completed';
+$string['allowcoursebadges'] = 'Enable course badges';
+$string['allowcoursebadges_desc'] = 'Allow badges to be created and awarded in course context.';
+$string['allowexternalbackpack'] = 'Enable connection to external backpacks';
+$string['allowexternalbackpack_desc'] = 'Allow users to set up connections and display badges from their external backpack providers.
+
+Note: It is recommended to leave this option disabled if the web site cannot be accesed from the internet (e.g. because of the firewall).';
+$string['any'] = 'Any';
+$string['anymethod'] = 'Any of the selected conditions is met';
+$string['anymethodactivity'] = 'Any of the selected activities is complete';
+$string['anymethodcourseset'] = 'Any of the selected courses is complete';
+$string['anymethodmanual'] = 'Any of the selected roles awards the badge';
+$string['anymethodprofile'] = 'Any of the selected profile fields has been completed';
+$string['attachment'] = 'Attach badge to message';
+$string['attachment_help'] = 'If checked, an issued badge will be attached to the recepient\'s email for download';
+$string['award'] = 'Award badge';
+$string['awardedtoyou'] = 'Issued to me';
+$string['awardoncron'] = 'Access to the badges was successfully enabled. Too many users can instantly earn this badge. To ensure site performance, this action will take some time to process.';
+$string['awards'] = 'Recipients';
+$string['backpackavailability'] = 'External badge verification';
+$string['backpackavailability_help'] = 'For badge recipients to be able to prove they earned their badges from you, an external backpack service should be able to access your site and verify badges issued from it.
+Your site does not currently appear to be accessible, which means that badges you have already issued or will issue in the future cannot be verified.
+
+##Why am I seeing this message?
+
+It may be that your firewall prevents access from users outside your network, your site is password protected, or you are running the site on a computer that is not available from the internet (such as a local development machine).
+
+##Is this a problem?
+
+You should fix this issue on any production site where you are planning to issue badges, otherwise the recipients will not be able to prove they earned their badges from you.
+
+If your site is not yet live you can create and issue test badges, as long as the site is accessible before you go live.
+
+##What if I can\'t make my whole site publically accessible?
+
+The only URL required for verification is [your-site-url]/badges/assertion.php so if you are able to modify your firewall to allow external access to that file, badge verification will still work.';
+$string['backpackbadges'] = 'You have {$a->totalbadges} badge(s) displayed from your backpack at <a href="{$a->backpackurl}">{$a->backpackurl}</a>.<br/>';
+$string['backpackdetails'] = 'Backpack settings';
+$string['badgedetails'] = 'Badge details';
+$string['badgeimage'] = 'Image';
+$string['badgeimage_help'] = 'This is an image that will be used when this badge is issued.
+
+To add a new image, browse and select an image (in JPG or PNG format) then click "Save changes". The image will be cropped to a square and resized to match badge image requirements. ';
+
+$string['badgesalt'] = 'Salt for hashing the recepient\'s email address';
+$string['badgesalt_desc'] = 'Using a hash allows backpack services to confirm the badge earner without having to expose their email address. This setting should only use numbers and letters.';
+$string['badgesdisabled'] = 'Badges are not enabled on this site.';
+$string['badgesearned'] = 'Number of badges earned: {$a}';
+$string['badgesettings'] = 'Badges settings';
+$string['badgestatus_0'] = 'Not available to users';
+$string['badgestatus_1'] = 'Available to users';
+$string['badgestatus_2'] = 'Not available to users';
+$string['badgestatus_3'] = 'Available to users';
+$string['badgestatus_4'] = 'Archived';
+$string['badgestoearn'] = 'Number of badges available: {$a}';
+$string['badgesview'] = 'Course badges';
+$string['badgeurl'] = 'Issued badge link';
+$string['bawards'] = 'Recipients ({$a})';
+$string['bcriteria'] = 'Criteria';
+$string['bdetails'] = 'Edit details';
+$string['bmessage'] = 'Message';
+$string['boverview'] = 'Overview';
+$string['bydate'] = ' complete by';
+$string['clearsettings'] = 'Clear settings';
+$string['completionnotenabled'] = 'Course completion is not enabled for this course, so it cannot be included in badge criteria.<br/>You can enable course completion in the course settings.';
+$string['completioninfo'] = 'This badge was issued for completing: ';
+$string['configenablebadges'] = 'When enabled, this feature lets you create badges and award them to site users.';
+$string['configuremessage'] = 'Badge message';
+$string['contact'] = 'Contact';
+$string['contact_help'] = 'An email address associated with the badge issuer.';
+$string['copyof'] = 'Copy of {$a}';
+$string['coursecompletion'] = 'Learners must complete this course. ';
+$string['coursebadges'] = 'Badges';
+$string['create'] = 'New badge';
+$string['createbutton'] = 'Create badge';
+$string['creatorbody'] = '<p>{$a->user} has completed all badge requirements and has been awarded the badge. View issued badge at {$a->link} </p>';
+$string['creatorsubject'] = '\'{$a}\' has been awarded!';
+$string['criteriasummary'] = 'Criteria summary';
+$string['criteria_descr'] = 'Learners are awarded this badge when they complete the following requirement: ';
+$string['criteria_descr_bydate'] = ' by <em>{$a}</em> ';
+$string['criteria_descr_grade'] = ' with minimum grade of <em>{$a}</em> ';
+$string['criteria_descr_short0'] = 'Complete <strong>{$a}</strong> of: ';
+$string['criteria_descr_short1'] = 'Complete <strong>{$a}</strong> of: ';
+$string['criteria_descr_short2'] = 'Awarded by <strong>{$a}</strong> of: ';
+$string['criteria_descr_short4'] = 'Complete the course ';
+$string['criteria_descr_short5'] = 'Complete <strong>{$a}</strong> of: ';
+$string['criteria_descr_short6'] = 'Complete <strong>{$a}</strong> of: ';
+$string['criteria_descr_single_short1'] = 'Complete: ';
+$string['criteria_descr_single_short2'] = 'Awarded by: ';
+$string['criteria_descr_single_short4'] = 'Complete the course ';
+$string['criteria_descr_single_short5'] = 'Complete: ';
+$string['criteria_descr_single_short6'] = 'Complete: ';
+$string['criteria_descr_single_1'] = 'The following activity has to be completed:';
+$string['criteria_descr_single_2'] = 'This badge has to be awarded by a user with the following role:';
+$string['criteria_descr_single_4'] = 'Learners must complete the course ';
+$string['criteria_descr_single_5'] = 'The following course has to be completed:';
+$string['criteria_descr_single_6'] = 'The following user profile field has to be completed:';
+$string['criteria_descr_0'] = 'Learners are awarded this badge when they complete <strong>{$a}</strong> of the listed requirements.';
+$string['criteria_descr_1'] = '<strong>{$a}</strong> of the following activities are completed:';
+$string['criteria_descr_2'] = 'This badge has to be awarded by the users with <strong>{$a}</strong> of the following roles:';
+$string['criteria_descr_4'] = 'Learners must complete the course ';
+$string['criteria_descr_5'] = '<strong>{$a}</strong> of the following courses have to be completed:';
+$string['criteria_descr_6'] = '<strong>{$a}</strong> of the following user profile fields have to be completed:';
+$string['criteria_0'] = 'This badge is awarded when...';
+$string['criteria_1'] = 'Activity completion';
+$string['criteria_1_help'] = 'Allows a badge to be awarded to users based on the completion of a set of activities within a course.';
+$string['criteria_2'] = 'Manual issue by role';
+$string['criteria_2_help'] = 'Allows a badge to be awarded manually by users who have a particular role within the site or course.';
+$string['criteria_3'] = 'Social participation';
+$string['criteria_3_help'] = 'Social';
+$string['criteria_4'] = 'Course completion';
+$string['criteria_4_help'] = 'Allows a badge to be awarded to users who have completed the course. This criterion can have additional parameters such as minimum grade and date of course completion.';
+$string['criteria_5'] = 'Completing a set of courses';
+$string['criteria_5_help'] = 'Allows a badge to be awarded to users who have completed a set of courses. Each course can have additional parameters such as minimum grade and date of course completion. ';
+$string['criteria_6'] = 'Profile completion';
+$string['criteria_6_help'] = 'Allows a badge to be awarded to users for completing certain fields in their profile. You can select from default and custom profile fields that are available to users. ';
+$string['criterror'] = 'Current parameters issues';
+$string['criterror_help'] = 'This fieldset shows all parameters that were initially added to this badge requirement but are no longer available. It is recommended that you uncheck such parameters to make sure that learners can earn this badge in the future.';
+$string['currentimage'] = 'Current image';
+$string['currentstatus'] = 'Current status: ';
+$string['dateawarded'] = 'Date issued';
+$string['dateearned'] = 'Date: {$a}';
+$string['day'] = 'Day(s)';
+$string['deactivate'] = 'Disable access';
+$string['deactivatesuccess'] = 'Access to the badges was successfully disabled.';
+$string['defaultissuercontact'] = 'Default badge issuer contact details';
+$string['defaultissuercontact_desc'] = 'An email address associated with the badge issuer.';
+$string['defaultissuername'] = 'Default badge issuer name';
+$string['defaultissuername_desc'] = 'Name of the issuing agent or authority.';
+$string['delbadge'] = 'Delete badge';
+$string['delconfirm'] = 'Are you sure that you want to delete badge \'{$a}\'?';
+$string['delcritconfirm'] = 'Are you sure that you want to delete this criterion?';
+$string['delparamconfirm'] = 'Are you sure that you want to delete this parameter?';
+$string['description'] = 'Description';
+$string['donotaward'] = 'Currently, this badge is not active, so it cannot be awarded to users. If you would like to award this badge, please set its status to active.';
+$string['editsettings'] = 'Edit settings';
+$string['enablebadges'] = 'Enable badges';
+$string['error:backpacknotavailable'] = 'Your site is not accessible from the Internet, so any badges issued from this site cannot be verified by external backpack services';
+$string['error:cannotact'] = 'Cannot activate the badge. ';
+$string['error:cannotawardbadge'] = 'Cannot award badge to a user.';
+$string['error:clone'] = 'Cannot clone the badge.';
+$string['error:duplicatename'] = 'Badge with such name already exists in the system.';
+$string['error:invalidbadgeurl'] = 'Invalid badge issuer URL format.';
+$string['error:invalidcriteriatype'] = 'Invalid criteria type.';
+$string['error:invalidexpiredate'] = 'Expiry date has to be in the future.';
+$string['error:invalidexpireperiod'] = 'Expiry period cannot be negative or equal 0.';
+$string['error:noactivities'] = 'There are no activities with completion criteria enabled in this course.';
+$string['error:nocourses'] = 'Course completion is not enabled for any of the courses in this site, so none can be displayed. You can enable course completion in the course settings.';
+$string['error:nogroups'] = '<p>There are no public collections of badges available in your backpack. </p>
+<p>To display external badges, you will need to visit your backpack and make at least one collection of badges public. </p>
+<p>If you already have backpack service connection established and your previously selected badge collection has been removed, clear these settings and try again.</p>';
+$string['error:nopermissiontoview'] = 'You have no permissions to view badge recipients';
+$string['error:nosuchbadge'] = 'Badge with id {$a} does not exist.';
+$string['error:nosuchcourse'] = 'Warning: This course is no longer available.';
+$string['error:nosuchfield'] = 'Warning: This user profile field is no longer available.';
+$string['error:nosuchmod'] = 'Warning: This activity is no longer available.';
+$string['error:nosuchrole'] = 'Warning: This role is no longer available.';
+$string['error:nosuchuser'] = 'User with this email address does not have an account with the current backpack provider';
+$string['error:notifycoursedate'] = 'Warning: Badges associated with course and activity completions will not be issued until the course start date.';
+$string['error:parameter'] = 'Warning: At least one parameter should be selected to ensure correct badge issuing workflow.';
+$string['error:save'] = 'Cannot save the badge.';
+$string['evidence'] = 'Evidence';
+$string['existingrecipients'] = 'Existing badge recipients';
+$string['expired'] = 'Expired';
+$string['expiredate'] = 'This badge expires on {$a}.';
+$string['expireddate'] = 'This badge expired on {$a}.';
+$string['expireperiod'] = 'This badge expires {$a} day(s) after being issued.';
+$string['expireperiodh'] = 'This badge expires {$a} hour(s) after being issued.';
+$string['expireperiodm'] = 'This badge expires {$a} minute(s) after being issued.';
+$string['expireperiods'] = 'This badge expires {$a} second(s) after being issued.';
+$string['expirydate'] = 'Expiry date';
+$string['expirydate_help'] = 'Optionally, badges can expire on a specific date, or the date can be calculated based on the date when the badge was issued to a user. ';
+$string['externalbadges'] = 'My badges from other web sites';
+$string['externalbadgesp'] = 'Badges from other web sites:';
+$string['externalbadges_help'] = 'This area allows to set up connection to an external backpack provider.
+
+Currently, only <a href="http://backpack.openbadges.org">Mozilla OpenBadges Backpack</a> is supported. You need to sign up for a backpack service before trying to set up backpack connection on this page.
+
+After such connection is successfully established, a number of badges from your backpack will be displayed on this page. A list of badges from a backpack along with their details and description can be found on a user profile page.';
+$string['fixed'] = 'Fixed date';
+$string['hidden'] = 'Hidden';
+$string['hiddenbadge'] = 'Unfortunately, badge owner has not made this information available.';
+$string['issuedbadge'] = 'Issued badge information';
+$string['issuancedetails'] = 'Badge expiry';
+$string['issuerdetails'] = 'Issuer details';
+$string['issuername'] = 'Issuer name';
+$string['issuername_help'] = 'Name of the issuing agent or authority.';
+$string['issuerurl'] = 'Issuer URL';
+$string['localbadges'] = 'My badges from {$a} web site';
+$string['localbadgesh'] = 'My badges from this web site';
+$string['localbadgesh_help'] = 'All badges earned within this web site by completing courses, course activities, and other requirements.
+
+You can manage your badges here by making them public or private for your profile page.
+
+You can download all of your badges or each badge separately and save them on your computer. Downloaded badges can be added to your external backpack service.';
+$string['localbadgesp'] = 'Badges from {$a}:';
+$string['makeprivate'] = 'Make private';
+$string['makepublic'] = 'Make public';
+$string['managebadges'] = 'Manage badges';
+$string['message'] = 'Message body';
+$string['messagebody'] = '<p>You have been awarded a badge "%badgename%"!</p>
+<p>More information about this badge can be found at %badgelink%.</p>
+<p>If there is no badge attached to this email, you can manage and download it from {$a} page.</p>';
+$string['messagesubject'] = 'Congratulations! You just earned a badge!';
+$string['method'] = 'This criterion is complete when...';
+$string['mingrade'] = 'Minimum grade required';
+$string['month'] = 'Month(s)';
+$string['mybadges'] = 'My badges';
+$string['mybackpack'] = 'My backpack settings';
+$string['never'] = 'Never';
+$string['newbadge'] = 'Add a new badge';
+$string['newimage'] = 'New image';
+$string['noawards'] = 'This badge has not been earned yet.';
+$string['nobackpack'] = 'There is no backpack service connected to this account.<br/>';
+$string['nobackpackbadges'] = 'There are no badges displayed from your backpack at <a href="{$a->backpackurl}">{$a->backpackurl}</a>.<br/>';
+$string['nobadges'] = 'There are no badges available.';
+$string['nocriteria'] = 'Criteria for this badge have not been set up yet.';
+$string['noexpiry'] = 'This badge does not have an expiry date.';
+$string['noparamstoadd'] = 'There are no additional parameters available to add to this badge requirement.';
+$string['notacceptedrole'] = 'Your current role assignment is not among the roles that can manually issue this badge.<br/>
+If you would like to see users who have already earned this badge, you can visit {$a} page. ';
+$string['nothingtoadd'] = 'There are no available criteria to add.';
+$string['notification'] = 'Notify badge creator';
+$string['notification_help'] = 'This setting manages notifications sent to a badge creator to let them know that the badge has been issued.
+
+The following options are available:
+
+* **NEVER** – Do not send notifications.
+
+* **EVERY TIME** – Send a notification every time this badge is awarded.
+
+* **DAILY** – Send notifications once a day.
+
+* **WEEKLY** – Send notifications once a week.
+
+* **MONTHLY** – Send notifications once a month.';
+$string['notifydaily'] = 'Daily';
+$string['notifyevery'] = 'Every time';
+$string['notifymonthly'] = 'Monthly';
+$string['notifyweekly'] = 'Weekly';
+$string['numawards'] = 'This badge has been issued to <a href="{$a->link}">{$a->count}</a> user(s).';
+$string['numawardstat'] = 'This badge has been issued {$a} user(s).';
+$string['overallcrit'] = 'of the selected criteria are complete.';
+$string['potentialrecipients'] = 'Potential badge recipients';
+$string['recipients'] = 'Badge recipients';
+$string['relative'] = 'Relative date';
+$string['requiredcourse'] = 'At least one course should be added to the courseset criterion.';
+$string['reviewbadge'] = 'Review badge criteria';
+$string['reviewconfirm'] = '<p>This action will perform a check if any of the users have already completed all the requirements for \'{$a}\' badge?</p>
+<p>Would you like to proceed?</p>';
+$string['save'] = 'Save';
+$string['searchname'] = 'Search by name';
+$string['selectgroup'] = 'Select badge collection';
+$string['selecting'] = 'With selected badges...';
+$string['setup'] = 'Set up connection';
+$string['sitebadges'] = 'Site badges';
+$string['sitebadges_help'] = 'Site badges can only be awarded to users for site related activities. These include completing a set of courses or parts of user profiles. Site badges can also be issued manually by one user to another.
+
+Badges for course related activities must be created at a course level. Course badges can be found under Course Administration > Badges';
+$string['statusmessage_0'] = 'This badge is currently not available to users. Enable access if you want users to earn this badge. ';
+$string['statusmessage_1'] = 'This badge is currently available to users. Disable access to make any changes. ';
+$string['statusmessage_2'] = 'This badge is currently not available to users, and its criteria are locked. Enable access if you want users to earn this badge. ';
+$string['statusmessage_3'] = 'This badge is currently available to users, and its criteria are locked. ';
+$string['statusmessage_4'] = 'This badge is currently archived';
+$string['status'] = 'Badge status';
+$string['status_help'] = 'Status of a badge determines its behaviour in the system:
+
+* **AVAILABLE** – Means that this badge can be earned by users. While a badge is available to users, its criteria cannot be modified.
+
+* **NOT AVAILABLE** – Means that this badge is not available to users and cannot be earned or manually issued. If such badge has never been issued before, its criteria can be changed.
+
+Once a badge has been issued to at least one user, it automatically becomes **LOCKED**. Locked badges can still be earned by users, but their criteria can no longer be changed. If you need to modify details or criteria of a locked badge, you can duplicate this badge and make all the required changes.
+
+*Why do we lock badges?*
+
+We want to make sure that all users complete the same requirements to earn a badge. Currently, it is not possible to revoke badges. If we allowed badges requirements to be modified all the time, we would most likely end up with users having the same badge for meeting completely different requirements.';
+$string['subject'] = 'Message subject';
+$string['variablesubstitution'] = 'Variable substitution in messages.';
+$string['variablesubstitution_help'] = 'In a badge message, certain variables can be inserted into the subject and/or body of a message so that they will be replaced with real values when the message is sent. The variables should be inserted into the the text exactly as they are shown below. The following variables can be used:
+
+%badgename%
+:   This will be replaced by the badge\'s full name.
+
+%username%
+:   This will be replaced by the recipient\'s full name.
+
+%badgelink%
+:   This will be replaced by the public URL with information about the issued badge.';
+$string['viewbadge'] = 'View issued badge';
+$string['visible'] = 'Visible';
+$string['warnexpired'] = ' (This badge has expired!)';
+$string['year'] = 'Year(s)';
index 4b8cd53..5b80b5e 100644 (file)
@@ -41,6 +41,7 @@ $string['audienceeducators'] = 'Educators';
 $string['audiencestudents'] = 'Students';
 $string['audienceadmins'] = 'Moodle administrators';
 $string['badurlformat'] = 'Bad URL format';
+$string['badgesnumber'] = 'Number of badges ({$a})';
 $string['community'] = 'Community';
 $string['communityremoved'] = 'That course link has been removed from your list';
 $string['confirmregistration'] = 'Confirm registration';
@@ -109,6 +110,7 @@ $string['hub'] = 'Hub';
 $string['imageurl'] = 'Image URL';
 $string['imageurl_help'] = 'This image will be displayed on the hub. This image must be available from the hub at any moment. The image should have a maximum size of {$a->width} X {$a->height}';
 $string['information'] = 'Information';
+$string['issuedbadgesnumber'] = 'Number of issued badges ({$a})';
 $string['language'] = 'Language';
 $string['language_help'] = 'The main language of this course.';
 $string['lasttimechecked'] = 'Last time checked';
index a2248ed..0e515ad 100644 (file)
@@ -193,6 +193,7 @@ $string['backuptakealook'] = 'Please take a look at your backup logs in:
   {$a}';
 $string['backupuserfileshelp'] = 'Choose whether user files (eg profile images) should be included in automated backups';
 $string['backupversion'] = 'Backup version';
+$string['badges'] = 'Badges';
 $string['block'] = 'Block';
 $string['blockconfiga'] = 'Configuring a {$a} block';
 $string['blockconfigbad'] = 'This block has not been implemented correctly and thus cannot provide a configuration interface.';
index 08e1310..ffa7d82 100644 (file)
@@ -69,6 +69,18 @@ $string['backup:downloadfile'] = 'Download files from backup areas';
 $string['backup:backuptargethub'] = 'Backup for hub';
 $string['backup:backuptargetimport'] = 'Backup for import';
 $string['backup:userinfo'] = 'Backup user data';
+$string['badges:awardbadge'] = 'Award badge to a user';
+$string['badges:createbadge'] = 'Create/duplicate badges';
+$string['badges:configuredetails'] = 'Set up/edit badge details';
+$string['badges:configurecriteria'] = 'Set up/edit criteria of earning a badge';
+$string['badges:configuremessages'] = 'Configure badge messages';
+$string['badges:deletebadge'] = 'Delete badges';
+$string['badges:earnbadge'] = 'Earn badge';
+$string['badges:manageglobalsettings'] = 'Manage badges global settings';
+$string['badges:manageownbadges'] = 'View and manage own earned badges';
+$string['badges:viewawarded'] = 'View users who earned a specific badge without being able to award a badge';
+$string['badges:viewbadges'] = 'View available badges without earning them';
+$string['badges:viewotherbadges'] = 'View public badges in other users\' profiles';
 $string['block:edit'] = 'Edit a block\'s settings';
 $string['block:view'] = 'View block';
 $string['blog:associatecourse'] = 'Associate blog entries with courses';
diff --git a/lib/badgeslib.php b/lib/badgeslib.php
new file mode 100644 (file)
index 0000000..35b5a4b
--- /dev/null
@@ -0,0 +1,1279 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Contains classes, functions and constants used in badges.
+ *
+ * @package    core
+ * @subpackage badges
+ * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/* Include required award criteria library. */
+require_once($CFG->dirroot . '/badges/criteria/award_criteria.php');
+
+/*
+ * Number of records per page.
+*/
+define('BADGE_PERPAGE', 50);
+
+/*
+ * Badge award criteria aggregation method.
+ */
+define('BADGE_CRITERIA_AGGREGATION_ALL', 1);
+
+/*
+ * Badge award criteria aggregation method.
+ */
+define('BADGE_CRITERIA_AGGREGATION_ANY', 2);
+
+/*
+ * Inactive badge means that this badge cannot be earned and has not been awarded
+ * yet. Its award criteria can be changed.
+ */
+define('BADGE_STATUS_INACTIVE', 0);
+
+/*
+ * Active badge means that this badge can we earned, but it has not been awarded
+ * yet. Can be deactivated for the purpose of changing its criteria.
+ */
+define('BADGE_STATUS_ACTIVE', 1);
+
+/*
+ * Inactive badge can no longer be earned, but it has been awarded in the past and
+ * therefore its criteria cannot be changed.
+ */
+define('BADGE_STATUS_INACTIVE_LOCKED', 2);
+
+/*
+ * Active badge means that it can be earned and has already been awarded to users.
+ * Its criteria cannot be changed any more.
+ */
+define('BADGE_STATUS_ACTIVE_LOCKED', 3);
+
+/*
+ * Archived badge is considered deleted and can no longer be earned and is not
+ * displayed in the list of all badges.
+ */
+define('BADGE_STATUS_ARCHIVED', 4);
+
+/*
+ * Badge type for site badges.
+ */
+define('BADGE_TYPE_SITE', 1);
+
+/*
+ * Badge type for course badges.
+ */
+define('BADGE_TYPE_COURSE', 2);
+
+/*
+ * Badge messaging schedule options.
+ */
+define('BADGE_MESSAGE_NEVER', 0);
+define('BADGE_MESSAGE_ALWAYS', 1);
+define('BADGE_MESSAGE_DAILY', 2);
+define('BADGE_MESSAGE_WEEKLY', 3);
+define('BADGE_MESSAGE_MONTHLY', 4);
+
+/**
+ * Class that represents badge.
+ *
+ */
+class badge {
+    /** @var int Badge id */
+    public $id;
+
+    /** Values from the table 'badge' */
+    public $name;
+    public $description;
+    public $timecreated;
+    public $timemodified;
+    public $usercreated;
+    public $usermodified;
+    public $image;
+    public $issuername;
+    public $issuerurl;
+    public $issuercontact;
+    public $expiredate;
+    public $expireperiod;
+    public $type;
+    public $courseid;
+    public $message;
+    public $messagesubject;
+    public $attachment;
+    public $notification;
+    public $status = 0;
+    public $nextcron;
+
+    /** @var array Badge criteria */
+    public $criteria = array();
+
+    /**
+     * Constructs with badge details.
+     *
+     * @param int $badgeid badge ID.
+     */
+    public function __construct($badgeid) {
+        global $DB;
+        $this->id = $badgeid;
+
+        $data = $DB->get_record('badge', array('id' => $badgeid));
+
+        if (empty($data)) {
+            print_error('error:nosuchbadge', 'badges', $badgeid);
+        }
+
+        foreach ((array)$data as $field => $value) {
+            if (property_exists($this, $field)) {
+                $this->{$field} = $value;
+            }
+        }
+
+        $this->criteria = self::get_criteria();
+    }
+
+    /**
+     * Use to get context instance of a badge.
+     * @return context instance.
+     */
+    public function get_context() {
+        if ($this->type == BADGE_TYPE_SITE) {
+            return context_system::instance();
+        } else if ($this->type == BADGE_TYPE_COURSE) {
+            return context_course::instance($this->courseid);
+        } else {
+            debugging('Something is wrong...');
+        }
+    }
+
+    /**
+     * Return array of aggregation methods
+     * @return array
+     */
+    public static function get_aggregation_methods() {
+        return array(
+                BADGE_CRITERIA_AGGREGATION_ALL => get_string('all', 'badges'),
+                BADGE_CRITERIA_AGGREGATION_ANY => get_string('any', 'badges'),
+        );
+    }
+
+    /**
+     * Return array of accepted criteria types for this badge
+     * @return array
+     */
+    public function get_accepted_criteria() {
+        $criteriatypes = array();
+
+        if ($this->type == BADGE_TYPE_COURSE) {
+            $criteriatypes = array(
+                    BADGE_CRITERIA_TYPE_OVERALL,
+                    BADGE_CRITERIA_TYPE_MANUAL,
+                    BADGE_CRITERIA_TYPE_COURSE,
+                    BADGE_CRITERIA_TYPE_ACTIVITY
+            );
+        } else if ($this->type == BADGE_TYPE_SITE) {
+            $criteriatypes = array(
+                    BADGE_CRITERIA_TYPE_OVERALL,
+                    BADGE_CRITERIA_TYPE_MANUAL,
+                    BADGE_CRITERIA_TYPE_COURSESET,
+                    BADGE_CRITERIA_TYPE_PROFILE,
+            );
+        }
+
+        return $criteriatypes;
+    }
+
+    /**
+     * Save/update badge information in 'badge' table only.
+     * Cannot be used for updating awards and criteria settings.
+     *
+     * @return bool Returns true on success.
+     */
+    public function save() {
+        global $DB;
+
+        $fordb = new stdClass();
+        foreach (get_object_vars($this) as $k => $v) {
+            $fordb->{$k} = $v;
+        }
+        unset($fordb->criteria);
+
+        $fordb->timemodified = time();
+        if ($DB->update_record_raw('badge', $fordb)) {
+            return true;
+        } else {
+            throw new moodle_exception('error:save', 'badges');
+            return false;
+        }
+    }
+
+    /**
+     * Creates and saves a clone of badge with all its properties.
+     * Clone is not active by default and has 'Copy of' attached to its name.
+     *
+     * @return int ID of new badge.
+     */
+    public function make_clone() {
+        global $DB, $USER;
+
+        $fordb = new stdClass();
+        foreach (get_object_vars($this) as $k => $v) {
+            $fordb->{$k} = $v;
+        }
+
+        $fordb->name = get_string('copyof', 'badges', $this->name);
+        $fordb->status = BADGE_STATUS_INACTIVE;
+        $fordb->image = 0;
+        $fordb->usercreated = $USER->id;
+        $fordb->usermodified = $USER->id;
+        $fordb->timecreated = time();
+        $fordb->timemodified = time();
+        unset($fordb->id);
+
+        if ($fordb->notification > 1) {
+            $fordb->nextcron = badges_calculate_message_schedule($fordb->notification);
+        }
+
+        $criteria = $fordb->criteria;
+        unset($fordb->criteria);
+
+        if ($new = $DB->insert_record('badge', $fordb, true)) {
+            $newbadge = new badge($new);
+
+            // Copy badge image.
+            $fs = get_file_storage();
+            if ($file = $fs->get_file($this->get_context()->id, 'badges', 'badgeimage', $this->id, '/', 'f1.png')) {
+                if ($imagefile = $file->copy_content_to_temp()) {
+                    badges_process_badge_image($newbadge, $imagefile);
+                }
+            }
+
+            // Copy badge criteria.
+            foreach ($this->criteria as $crit) {
+                $crit->make_clone($new);
+            }
+
+            return $new;
+        } else {
+            throw new moodle_exception('error:clone', 'badges');
+            return false;
+        }
+    }
+
+    /**
+     * Checks if badges is active.
+     * Used in badge award.
+     *
+     * @return bool A status indicating badge is active
+     */
+    public function is_active() {
+        if (($this->status == BADGE_STATUS_ACTIVE) ||
+            ($this->status == BADGE_STATUS_ACTIVE_LOCKED)) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Use to get the name of badge status.
+     *
+     */
+    public function get_status_name() {
+        return get_string('badgestatus_' . $this->status, 'badges');
+    }
+
+    /**
+     * Use to set badge status.
+     * Only active badges can be earned/awarded/issued.
+     *
+     * @param int $status Status from BADGE_STATUS constants
+     */
+    public function set_status($status = 0) {
+        $this->status = $status;
+        $this->save();
+    }
+
+    /**
+     * Checks if badges is locked.
+     * Used in badge award and editing.
+     *
+     * @return bool A status indicating badge is locked
+     */
+    public function is_locked() {
+        if (($this->status == BADGE_STATUS_ACTIVE_LOCKED) ||
+                ($this->status == BADGE_STATUS_INACTIVE_LOCKED)) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Checks if badge has been awarded to users.
+     * Used in badge editing.
+     *
+     * @return bool A status indicating badge has been awarded at least once
+     */
+    public function has_awards() {
+        global $DB;
+        if ($DB->record_exists('badge_issued', array('badgeid' => $this->id))) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Gets list of users who have earned an instance of this badge.
+     *
+     * @return array An array of objects with information about badge awards.
+     */
+    public function get_awards() {
+        global $DB;
+
+        $awards = $DB->get_records_sql(
+                'SELECT b.userid, b.dateissued, b.uniquehash, u.firstname, u.lastname
+                    FROM {badge_issued} b INNER JOIN {user} u
+                        ON b.userid = u.id
+                    WHERE b.badgeid = :badgeid', array('badgeid' => $this->id));
+
+        return $awards;
+    }
+
+    /**
+     * Indicates whether badge has already been issued to a user.
+     *
+     */
+    public function is_issued($userid) {
+        global $DB;
+        return $DB->record_exists('badge_issued', array('badgeid' => $this->id, 'userid' => $userid));
+    }
+
+    /**
+     * Issue a badge to user.
+     *
+     * @param int $userid User who earned the badge
+     * @param bool $nobake Not baking actual badges (for testing purposes)
+     */
+    public function issue($userid, $nobake = false) {
+        global $DB, $CFG;
+
+        $now = time();
+        $issued = new stdClass();
+        $issued->badgeid = $this->id;
+        $issued->userid = $userid;
+        $issued->uniquehash = sha1(rand() . $userid . $this->id . $now);
+        $issued->dateissued = $now;
+
+        if ($this->can_expire()) {
+            $issued->dateexpire = $this->calculate_expiry($now);
+        } else {
+            $issued->dateexpire = null;
+        }
+
+        // Issued badges always being issued as private.
+        $issued->visible = 0;
+
+        $result = $DB->insert_record('badge_issued', $issued, true);
+
+        if ($result) {
+            // Lock the badge, so that its criteria could not be changed any more.
+            if ($this->status == BADGE_STATUS_ACTIVE) {
+                $this->set_status(BADGE_STATUS_ACTIVE_LOCKED);
+            }
+
+            // Update details in criteria_met table.
+            $compl = $this->get_criteria_completions($userid);
+            foreach ($compl as $c) {
+                $obj = new stdClass();
+                $obj->id = $c->id;
+                $obj->issuedid = $result;
+                $DB->update_record('badge_criteria_met', $obj, true);
+            }
+
+            if (!$nobake) {
+                // Bake a badge image.
+                $pathhash = badges_bake($issued->uniquehash, $this->id, $userid, true);
+
+                // Notify recipients and badge creators.
+                if (empty($CFG->noemailever)) {
+                    badges_notify_badge_award($this, $userid, $issued->uniquehash, $pathhash);
+                }
+            }
+        }
+    }
+
+    /**
+     * Reviews all badge criteria and checks if badge can be instantly awarded.
+     *
+     * @return int Number of awards
+     */
+    public function review_all_criteria() {
+        global $DB, $CFG;
+        $awards = 0;
+
+        // Raise timelimit as this could take a while for big web sites.
+        set_time_limit(0);
+        raise_memory_limit(MEMORY_HUGE);
+
+        // For site level badges, get all active site users who can earn this badge and haven't got it yet.
+        if ($this->type == BADGE_TYPE_SITE) {
+            $sql = 'SELECT DISTINCT u.id, bi.badgeid
+                        FROM {user} u
+                        LEFT JOIN {badge_issued} bi
+                            ON u.id = bi.userid AND bi.badgeid = :badgeid
+                        WHERE bi.badgeid IS NULL AND u.id != :guestid AND u.deleted = 0';
+            $toearn = $DB->get_fieldset_sql($sql, array('badgeid' => $this->id, 'guestid' => $CFG->siteguest));
+        } else {
+            // For course level badges, get users who can earn this badge in the course.
+            // These are all enrolled users with capability moodle/badges:earnbadge.
+            $earned = $DB->get_fieldset_select('badge_issued', 'userid AS id', 'badgeid = :badgeid', array('badgeid' => $this->id));
+            $users = get_enrolled_users($this->get_context(), 'moodle/badges:earnbadge', 0, 'u.id');
+            $toearn = array_diff(array_keys($users), $earned);
+        }
+
+        foreach ($toearn as $uid) {
+            $toreview = false;
+            foreach ($this->criteria as $crit) {
+                if ($crit->criteriatype != BADGE_CRITERIA_TYPE_OVERALL) {
+                    if ($crit->review($uid)) {
+                        $crit->mark_complete($uid);
+                        if ($this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+                            $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($uid);
+                            $this->issue($uid);
+                            $awards++;
+                            break;
+                        } else {
+                            $toreview = true;
+                            continue;
+                        }
+                    } else {
+                        if ($this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+                            continue;
+                        } else {
+                            break;
+                        }
+                    }
+                }
+            }
+            // Review overall if it is required.
+            if ($toreview && $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($uid)) {
+                $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($uid);
+                $this->issue($uid);
+                $awards++;
+            }
+        }
+
+        return $awards;
+    }
+
+    /**
+     * Gets an array of completed criteria from 'badge_criteria_met' table.
+     *
+     * @param int $userid Completions for a user
+     * @return array Records of criteria completions
+     */
+    public function get_criteria_completions($userid) {
+        global $DB;
+        $completions = array();
+        $sql = "SELECT bcm.id, bcm.critid
+                FROM {badge_criteria_met} bcm
+                    INNER JOIN {badge_criteria} bc ON bcm.critid = bc.id
+                WHERE bc.badgeid = :badgeid AND bcm.userid = :userid ";
+        $completions = $DB->get_records_sql($sql, array('badgeid' => $this->id, 'userid' => $userid));
+
+        return $completions;
+    }
+
+    /**
+     * Checks if badges has award criteria set up.
+     *
+     * @return bool A status indicating badge has at least one criterion
+     */
+    public function has_criteria() {
+        if (count($this->criteria) > 0) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns badge award criteria
+     *
+     * @return array An array of badge criteria
+     */
+    public function get_criteria() {
+        global $DB;
+        $criteria = array();
+
+        if ($records = (array)$DB->get_records('badge_criteria', array('badgeid' => $this->id))) {
+            foreach ($records as $record) {
+                $criteria[$record->criteriatype] = award_criteria::build((array)$record);
+            }
+        }
+
+        return $criteria;
+    }
+
+    /**
+     * Get aggregation method for badge criteria
+     *
+     * @param int $criteriatype If none supplied, get overall aggregation method (optional)
+     * @return int One of BADGE_CRITERIA_AGGREGATION_ALL or BADGE_CRITERIA_AGGREGATION_ANY
+     */
+    public function get_aggregation_method($criteriatype = 0) {
+        global $DB;
+        $params = array('badgeid' => $this->id, 'criteriatype' => $criteriatype);
+        $aggregation = $DB->get_field('badge_criteria', 'method', $params, IGNORE_MULTIPLE);
+
+        if (!$aggregation) {
+            return BADGE_CRITERIA_AGGREGATION_ALL;
+        }
+
+        return $aggregation;
+    }
+
+    /**
+     * Checks if badge has expiry period or date set up.
+     *
+     * @return bool A status indicating badge can expire
+     */
+    public function can_expire() {
+        if ($this->expireperiod || $this->expiredate) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Calculates badge expiry date based on either expirydate or expiryperiod.
+     *
+     * @param int $timestamp Time of badge issue
+     * @return int A timestamp
+     */
+    public function calculate_expiry($timestamp) {
+        $expiry = null;
+
+        if (isset($this->expiredate)) {
+            $expiry = $this->expiredate;
+        } else if (isset($this->expireperiod)) {
+            $expiry = $timestamp + $this->expireperiod;
+        }
+
+        return $expiry;
+    }
+
+    /**
+     * Checks if badge has manual award criteria set.
+     *
+     * @return bool A status indicating badge can be awarded manually
+     */
+    public function has_manual_award_criteria() {
+        foreach ($this->criteria as $criterion) {
+            if ($criterion->criteriatype == BADGE_CRITERIA_TYPE_MANUAL) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Marks the badge as archived.
+     * For reporting and historical purposed we cannot completely delete badges.
+     * We will just change their status to BADGE_STATUS_ARCHIVED.
+     */
+    public function delete() {
+        $this->status = BADGE_STATUS_ARCHIVED;
+        $this->save();
+    }
+}
+
+/**
+ * Sends notifications to users about awarded badges.
+ *
+ * @param badge $badge Badge that was issued
+ * @param int $userid Recipient ID
+ * @param string $issued Unique hash of an issued badge
+ * @param string $filepathhash File path hash of an issued badge for attachments
+ */
+function badges_notify_badge_award(badge $badge, $userid, $issued, $filepathhash) {
+    global $CFG, $DB;
+
+    $admin = get_admin();
+    $userfrom = new stdClass();
+    $userfrom->id = $admin->id;
+    $userfrom->email = !empty($CFG->badges_defaultissuercontact) ? $CFG->badges_defaultissuercontact : $admin->email;
+    $userfrom->firstname = !empty($CFG->badges_defaultissuername) ? $CFG->badges_defaultissuername : $admin->firstname;
+    $userfrom->lastname = !empty($CFG->badges_defaultissuername) ? '' : $admin->lastname;
+    $userfrom->maildisplay = true;
+
+    $issuedlink = html_writer::link(new moodle_url('/badges/badge.php', array('hash' => $issued)), $badge->name);
+    $userto = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
+
+    $params = new stdClass();
+    $params->badgename = $badge->name;
+    $params->username = fullname($userto);
+    $params->badgelink = $issuedlink;
+    $message = badge_message_from_template($badge->message, $params);
+    $plaintext = format_text_email($message, FORMAT_HTML);
+
+    if ($badge->attachment && $filepathhash) {
+        $fs = get_file_storage();
+        $file = $fs->get_file_by_hash($filepathhash);
+        $attachment = $file->copy_content_to_temp();
+        email_to_user($userto,
+            $userfrom,
+            $badge->messagesubject,
+            $plaintext,
+            $message,
+            str_replace($CFG->dataroot, '', $attachment),
+            str_replace(' ', '_', $badge->name) . ".png"
+        );
+        @unlink($attachment);
+    } else {
+        email_to_user($userto, $userfrom, $badge->messagesubject, $plaintext, $message);
+    }
+
+    // Notify badge creator about the award if they receive notifications every time.
+    if ($badge->notification == 1) {
+        $creator = $DB->get_record('user', array('id' => $badge->usercreated), '*', MUST_EXIST);
+        $a = new stdClass();
+        $a->user = fullname($userto);
+        $a->link = $issuedlink;
+        $creatormessage = get_string('creatorbody', 'badges', $a);
+        $creatorsubject = get_string('creatorsubject', 'badges', $badge->name);
+
+        $eventdata = new stdClass();
+        $eventdata->component         = 'moodle';
+        $eventdata->name              = 'instantmessage';
+        $eventdata->userfrom          = $userfrom;
+        $eventdata->userto            = $creator;
+        $eventdata->notification      = 1;
+        $eventdata->subject           = $creatorsubject;
+        $eventdata->fullmessage       = $creatormessage;
+        $eventdata->fullmessageformat = FORMAT_PLAIN;
+        $eventdata->fullmessagehtml   = format_text($creatormessage, FORMAT_HTML);
+        $eventdata->smallmessage      = '';
+
+        message_send($eventdata);
+        $DB->set_field('badge_issued', 'issuernotified', time(), array('badgeid' => $badge->id, 'userid' => $userid));
+    }
+}
+
+/**
+ * Caclulates date for the next message digest to badge creators.
+ *
+ * @param in $schedule Type of message schedule BADGE_MESSAGE_DAILY|BADGE_MESSAGE_WEEKLY|BADGE_MESSAGE_MONTHLY.
+ * @return int Timestamp for next cron
+ */
+function badges_calculate_message_schedule($schedule) {
+    $nextcron = 0;
+
+    switch ($schedule) {
+        case BADGE_MESSAGE_DAILY:
+            $nextcron = time() + 60 * 60 * 24;
+            break;
+        case BADGE_MESSAGE_WEEKLY:
+            $nextcron = time() + 60 * 60 * 24 * 7;
+            break;
+        case BADGE_MESSAGE_MONTHLY:
+            $nextcron = time() + 60 * 60 * 24 * 7 * 30;
+            break;
+    }
+
+    return $nextcron;
+}
+
+/**
+ * Replaces variables in a message template and returns text ready to be emailed to a user.
+ *
+ * @param string $message Message body.
+ * @return string Message with replaced values
+ */
+function badge_message_from_template($message, $params) {
+    $msg = $message;
+    foreach ($params as $key => $value) {
+        $msg = str_replace("%$key%", $value, $msg);
+    }
+
+    return $msg;
+}
+
+/**
+ * Get all badges.
+ *
+ * @param int Type of badges to return
+ * @param int Course ID for course badges
+ * @param string $sort An SQL field to sort by
+ * @param string $dir The sort direction ASC|DESC
+ * @param int $page The page or records to return
+ * @param int $perpage The number of records to return per page
+ * @param int $user User specific search
+ * @return array $badge Array of records matching criteria
+ */
+function badges_get_badges($type, $courseid = 0, $sort = '', $dir = '', $page = 0, $perpage = BADGE_PERPAGE, $user = 0) {
+    global $DB;
+    $records = array();
+    $params = array();
+    $where = "b.status != :deleted AND b.type = :type ";
+    $params['deleted'] = BADGE_STATUS_ARCHIVED;
+
+    $userfields = array('b.id, b.name, b.status');
+    $usersql = "";
+    if ($user != 0) {
+        $userfields[] = 'bi.dateissued';
+        $userfields[] = 'bi.uniquehash';
+        $usersql = " LEFT JOIN {badge_issued} bi ON b.id = bi.badgeid AND bi.userid = :userid ";
+        $params['userid'] = $user;
+        $where .= " AND (b.status = 1 OR b.status = 3) ";
+    }
+    $fields = implode(', ', $userfields);
+
+    if ($courseid != 0 ) {
+        $where .= "AND b.courseid = :courseid ";
+        $params['courseid'] = $courseid;
+    }
+
+    $sorting = (($sort != '' && $dir != '') ? 'ORDER BY ' . $sort . ' ' . $dir : '');
+    $params['type'] = $type;
+
+    $sql = "SELECT $fields FROM {badge} b $usersql WHERE $where $sorting";
+    $records = $DB->get_records_sql($sql, $params, $page * $perpage, $perpage);
+
+    $badges = array();
+    foreach ($records as $r) {
+        $badge = new badge($r->id);
+        $badges[$r->id] = $badge;
+        if ($user != 0) {
+            $badges[$r->id]->dateissued = $r->dateissued;
+            $badges[$r->id]->uniquehash = $r->uniquehash;
+        } else {
+            $badges[$r->id]->awards = $DB->count_records('badge_issued', array('badgeid' => $badge->id));
+            $badges[$r->id]->statstring = $badge->get_status_name();
+        }
+    }
+    return $badges;
+}
+
+/**
+ * Get badges for a specific user.
+ *
+ * @param int $userid User ID
+ * @param int $courseid Badges earned by a user in a specific course
+ * @param int $page The page or records to return
+ * @param int $perpage The number of records to return per page
+ * @param string $search A simple string to search for
+ * @param bool $onlypublic Return only public badges
+ * @return array of badges ordered by decreasing date of issue
+ */
+function badges_get_user_badges($userid, $courseid = 0, $page = 0, $perpage = 0, $search = '', $onlypublic = false) {
+    global $DB;
+    $badges = array();
+
+    $params[] = $userid;
+    $sql = 'SELECT
+                bi.uniquehash,
+                bi.dateissued,
+                bi.dateexpire,
+                bi.id as issuedid,
+                bi.visible,
+                u.email,
+                b.*
+            FROM
+                {badge} b,
+                {badge_issued} bi,
+                {user} u
+            WHERE b.id = bi.badgeid
+                AND u.id = bi.userid
+                AND bi.userid = ?';
+
+    if (!empty($search)) {
+        $sql .= ' AND (' . $DB->sql_like('b.name', '?', false) . ') ';
+        $params[] = "%$search%";
+    }
+    if ($onlypublic) {
+        $sql .= ' AND (bi.visible = 1) ';
+    }
+
+    if ($courseid != 0) {
+        $sql .= ' AND (b.courseid = ' . $courseid . ') ';
+    }
+    $sql .= ' ORDER BY bi.dateissued DESC';
+    $badges = $DB->get_records_sql($sql, $params, $page * $perpage, $perpage);
+
+    return $badges;
+}
+
+/**
+ * Get issued badge details for assertion URL
+ *
+ * @param string $hash
+ */
+function badges_get_issued_badge_info($hash) {
+    global $DB, $CFG;
+
+    $a = array();
+
+    $record = $DB->get_record_sql('
+            SELECT
+                bi.dateissued,
+                bi.dateexpire,
+                u.email,
+                b.*
+            FROM
+                {badge} b,
+                {badge_issued} bi,
+                {user} u
+            WHERE b.id = bi.badgeid
+                AND u.id = bi.userid
+                AND ' . $DB->sql_compare_text('bi.uniquehash', 40) . ' = ' . $DB->sql_compare_text(':hash', 40),
+            array('hash' => $hash), IGNORE_MISSING);
+
+    if ($record) {
+        if ($record->type == BADGE_TYPE_SITE) {
+            $context = context_system::instance();
+        } else {
+            $context = context_course::instance($record->courseid);
+        }
+
+        $url = new moodle_url('/badges/badge.php', array('hash' => $hash));
+
+        // Recipient's email is hashed: <algorithm>$<hash(email + salt)>.
+        $a['recipient'] = 'sha256$' . hash('sha256', $record->email . $CFG->badges_badgesalt);
+        $a['salt'] = $CFG->badges_badgesalt;
+
+        if ($record->dateexpire) {
+            $a['expires'] = date('Y-m-d', $record->dateexpire);
+        }
+
+        $a['issued_on'] = date('Y-m-d', $record->dateissued);
+        $a['evidence'] = $url->out(); // Issued badge URL.
+        $a['badge'] = array();
+        $a['badge']['version'] = '0.5.0'; // Version of OBI specification, 0.5.0 - current beta.
+        $a['badge']['name'] = $record->name;
+        $a['badge']['image'] = moodle_url::make_pluginfile_url($context->id, 'badges', 'badgeimage', $record->id, '/', 'f1')->out();
+        $a['badge']['description'] = $record->description;
+        $a['badge']['criteria'] = $url->out(); // Issued badge URL.
+        $a['badge']['issuer'] = array();
+        $a['badge']['issuer']['origin'] = $record->issuerurl;
+        $a['badge']['issuer']['name'] = $record->issuername;
+        $a['badge']['issuer']['contact'] = $record->issuercontact;
+    }
+
+    return $a;
+}
+
+/**
+ * Extends the course administration navigation with the Badges page
+ *
+ * @param navigation_node $coursenode
+ * @param object $course
+ */
+function badges_add_course_navigation(navigation_node $coursenode, stdClass $course) {
+    global $CFG, $SITE;
+
+    $coursecontext = context_course::instance($course->id);
+    $isfrontpage = (!$coursecontext || $course->id == $SITE->id);
+
+    if ($CFG->enablebadges && $CFG->badges_allowcoursebadges && !$isfrontpage) {
+        if (has_capability('moodle/badges:configuredetails', $coursecontext)) {
+            $coursenode->add(get_string('coursebadges', 'badges'), null,
+                    navigation_node::TYPE_CONTAINER, null, 'coursebadges',
+                    new pix_icon('i/badge', get_string('coursebadges', 'badges')));
+
+            if (has_capability('moodle/badges:viewawarded', $coursecontext)) {
+                $url = new moodle_url($CFG->wwwroot . '/badges/index.php',
+                        array('type' => BADGE_TYPE_COURSE, 'id' => $course->id));
+
+                $coursenode->get('coursebadges')->add(get_string('managebadges', 'badges'), $url,
+                    navigation_node::TYPE_SETTING, null, 'coursebadges');
+            }
+
+            if (has_capability('moodle/badges:createbadge', $coursecontext)) {
+                $url = new moodle_url($CFG->wwwroot . '/badges/newbadge.php',
+                        array('type' => BADGE_TYPE_COURSE, 'id' => $course->id));
+
+                $coursenode->get('coursebadges')->add(get_string('newbadge', 'badges'), $url,
+                        navigation_node::TYPE_SETTING, null, 'newbadge');
+            }
+        }
+    }
+}
+
+/**
+ * Triggered when 'course_completed' event happens.
+ *
+ * @param   object $eventdata
+ * @return  boolean
+ */
+function badges_award_handle_course_criteria_review(stdClass $eventdata) {
+    global $DB, $CFG;
+
+    if (!empty($CFG->enablebadges)) {
+        $userid = $eventdata->userid;
+        $courseid = $eventdata->course;
+
+        // Need to take into account that course can be a part of course_completion and courseset_completion criteria.
+        if ($rs = $DB->get_records('badge_criteria_param', array('name' => 'course_' . $courseid, 'value' => $courseid))) {
+            foreach ($rs as $r) {
+                $crit = $DB->get_record('badge_criteria', array('id' => $r->critid), 'badgeid, criteriatype', MUST_EXIST);
+                $badge = new badge($crit->badgeid);
+                if (!$badge->is_active() || $badge->is_issued($userid)) {
+                    continue;
+                }
+
+                if ($badge->criteria[$crit->criteriatype]->review($userid)) {
+                    $badge->criteria[$crit->criteriatype]->mark_complete($userid);
+
+                    if ($badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($userid)) {
+                        $badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($userid);
+                        $badge->issue($userid);
+                    }
+                }
+            }
+        }
+    }
+
+    return true;
+}
+
+/**
+ * Triggered when 'activity_completed' event happens.
+ *
+ * @param   object $eventdata
+ * @return  boolean
+ */
+function badges_award_handle_activity_criteria_review(stdClass $eventdata) {
+    global $DB, $CFG;
+
+    if (!empty($CFG->enablebadges)) {
+        $userid = $eventdata->userid;
+        $mod = $eventdata->coursemoduleid;
+
+        if ($eventdata->completionstate == COMPLETION_COMPLETE
+            || $eventdata->completionstate == COMPLETION_COMPLETE_PASS
+            || $eventdata->completionstate == COMPLETION_COMPLETE_FAIL) {
+            // Need to take into account that there can be more than one badge with the same activity in its criteria.
+            if ($rs = $DB->get_records('badge_criteria_param', array('name' => 'module_' . $mod, 'value' => $mod))) {
+                foreach ($rs as $r) {
+                    $bid = $DB->get_field('badge_criteria', 'badgeid', array('id' => $r->critid), MUST_EXIST);
+                    $badge = new badge($bid);
+                    if (!$badge->is_active() || $badge->is_issued($userid)) {
+                        continue;
+                    }
+
+                    if ($badge->criteria[BADGE_CRITERIA_TYPE_ACTIVITY]->review($userid)) {
+                        $badge->criteria[BADGE_CRITERIA_TYPE_ACTIVITY]->mark_complete($userid);
+
+                        if ($badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($userid)) {
+                            $badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($userid);
+                            $badge->issue($userid);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    return true;
+}
+
+/**
+ * Triggered when 'user_updated' event happens.
+ *
+ * @param   object $eventdata Holds all information about a user.
+ * @return  boolean
+ */
+function badges_award_handle_profile_criteria_review(stdClass $eventdata) {
+    global $DB, $CFG;
+
+    if (!empty($CFG->enablebadges)) {
+        $userid = $eventdata->id;
+
+        if ($rs = $DB->get_records('badge_criteria', array('criteriatype' => BADGE_CRITERIA_TYPE_PROFILE))) {
+            foreach ($rs as $r) {
+                $badge = new badge($r->badgeid);
+                if (!$badge->is_active() || $badge->is_issued($userid)) {
+                    continue;
+                }
+
+                if ($badge->criteria[BADGE_CRITERIA_TYPE_PROFILE]->review($userid)) {
+                    $badge->criteria[BADGE_CRITERIA_TYPE_PROFILE]->mark_complete($userid);
+
+                    if ($badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($userid)) {
+                        $badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($userid);
+                        $badge->issue($userid);
+                    }
+                }
+            }
+        }
+    }
+
+    return true;
+}
+
+/**
+ * Triggered when badge is manually awarded.
+ *
+ * @param   object      $data
+ * @return  boolean
+ */
+function badges_award_handle_manual_criteria_review(stdClass $data) {
+    $criteria = $data->crit;
+    $userid = $data->userid;
+    $badge = new badge($criteria->badgeid);
+
+    if (!$badge->is_active() || $badge->is_issued($userid)) {
+        return true;
+    }
+
+    if ($criteria->review($userid)) {
+        $criteria->mark_complete($userid);
+
+        if ($badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($userid)) {
+            $badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($userid);
+            $badge->issue($userid);
+        }
+    }
+
+    return true;
+}
+
+/**
+ * Process badge image from form data
+ *
+ * @param badge $badge Badge object
+ * @param string $iconfile Original file
+ */
+function badges_process_badge_image(badge $badge, $iconfile) {
+    global $CFG, $USER;
+    require_once($CFG->libdir. '/gdlib.php');
+
+    if (!empty($CFG->gdversion)) {
+        if ($fileid = (int)process_new_icon($badge->get_context(), 'badges', 'badgeimage', $badge->id, $iconfile)) {
+            $badge->image = $fileid;
+            $badge->save();
+        }
+        @unlink($iconfile);
+
+        // Clean up file draft area after badge image has been saved.
+        $context = context_user::instance($USER->id, MUST_EXIST);
+        $fs = get_file_storage();
+        $fs->delete_area_files($context->id, 'user', 'draft');
+    }
+}
+
+/**
+ * Print badge image.
+ *
+ * @param badge $badge Badge object
+ * @param stdClass $context
+ * @param string $size
+ */
+function print_badge_image(badge $badge, stdClass $context, $size = 'small') {
+    $fsize = ($size == 'small') ? 'f2' : 'f1';
+
+    $imageurl = moodle_url::make_pluginfile_url($context->id, 'badges', 'badgeimage', $badge->id, '/', $fsize, false);
+    // Appending a random parameter to image link to forse browser reload the image.
+    $attributes = array('src' => $imageurl . '?' . rand(1, 10000), 'alt' => s($badge->name), 'class' => 'activatebadge');
+
+    return html_writer::empty_tag('img', $attributes);
+}
+
+/**
+ * Bake issued badge.
+ *
+ * @param string $hash Unique hash of an issued badge.
+ * @param int $badgeid ID of the original badge.
+ * @param int $userid ID of badge recipient (optional).
+ * @param boolean $pathhash Return file pathhash instead of image url (optional).
+ * @return string|url Returns either new file path hash or new file URL
+ */
+function badges_bake($hash, $badgeid, $userid = 0, $pathhash = false) {
+    global $CFG, $USER;
+    require_once(dirname(dirname(__FILE__)) . '/badges/lib/bakerlib.php');
+
+    $badge = new badge($badgeid);
+    $badge_context = $badge->get_context();
+    $userid = ($userid) ? $userid : $USER->id;
+    $user_context = context_user::instance($userid);
+
+    $fs = get_file_storage();
+    if (!$fs->file_exists($user_context->id, 'badges', 'userbadge', $badge->id, '/', $hash . '.png')) {
+        if ($file = $fs->get_file($badge_context->id, 'badges', 'badgeimage', $badge->id, '/', 'f1.png')) {
+            $contents = $file->get_content();
+
+            $filehandler = new PNG_MetaDataHandler($contents);
+            $assertion = new moodle_url('/badges/assertion.php', array('b' => $hash));
+            if ($filehandler->check_chunks("tEXt", "openbadges")) {
+                // Add assertion URL tExt chunk.
+                $newcontents = $filehandler->add_chunks("tEXt", "openbadges", $assertion->out(false));
+                $fileinfo = array(
+                        'contextid' => $user_context->id,
+                        'component' => 'badges',
+                        'filearea' => 'userbadge',
+                        'itemid' => $badge->id,
+                        'filepath' => '/',
+                        'filename' => $hash . '.png',
+                );
+
+                // Create a file with added contents.
+                $newfile = $fs->create_file_from_string($fileinfo, $newcontents);
+                if ($pathhash) {
+                    return $newfile->get_pathnamehash();
+                }
+            }
+        } else {
+            debugging('Error baking badge image!');
+        }
+    }
+
+    $fileurl = moodle_url::make_pluginfile_url($user_context->id, 'badges', 'userbadge', $badge->id, '/', $hash, true);
+    return $fileurl;
+}
+
+/**
+ * Returns backpack service settings.
+ *
+ * @param int $userid Backpack user ID.
+ * @return null|object Returns null is there is no backpack or object with backpack settings.
+ */
+function get_backpack_settings($userid) {
+    global $DB;
+    require_once(dirname(dirname(__FILE__)) . '/badges/lib/backpacklib.php');
+
+    $record = $DB->get_record('badge_backpack', array('userid' => $userid), '*', IGNORE_MISSING);
+    if ($record) {
+        $backpack = new OpenBadgesBackpackHandler($record);
+        $out = new stdClass();
+        $out->backpackurl = $backpack->get_url();
+        $badges = $backpack->get_badges();
+        $out->badges = isset($badges->badges) ? $badges->badges : array();
+        $out->totalbadges = count($out->badges);
+        return $out;
+    }
+
+    return null;
+}
+
+/**
+ * Download all user badges in zip archive.
+ *
+ * @param int $userid ID of badge owner.
+ */
+function badges_download($userid) {
+    global $CFG, $DB;
+    $context = context_user::instance($userid);
+    $records = $DB->get_records('badge_issued', array('userid' => $userid));
+
+    // Get list of files to download.
+    $fs = get_file_storage();
+    $filelist = array();
+    foreach ($records as $issued) {
+        $badge = new badge($issued->badgeid);
+        // Need to make image name user-readable and unique using filename safe characters.
+        $name =  $badge->name . ' ' . userdate($issued->dateissued, '%d %b %Y') . ' ' . hash('crc32', $badge->id);
+        $name = str_replace(' ', '_', $name);
+        if ($file = $fs->get_file($context->id, 'badges', 'userbadge', $issued->badgeid, '/', $issued->uniquehash . '.png')) {
+            $filelist[$name . '.png'] = $file;
+        }
+    }
+
+    // Zip files and sent them to a user.
+    $tempzip = tempnam($CFG->tempdir.'/', 'mybadges');
+    $zipper = new zip_packer();
+    if ($zipper->archive_to_pathname($filelist, $tempzip)) {
+        send_temp_file($tempzip, 'badges.zip');
+    } else {
+        debugging("Problems with archiving the files.");
+    }
+}
+
+/**
+ * Print badges on user profile page.
+ *
+ * @param int $userid User ID.
+ * @param int $courseid Course if we need to filter badges (optional).
+ */
+function profile_display_badges($userid, $courseid = 0) {
+    global $CFG, $PAGE, $USER, $SITE;
+    require_once($CFG->dirroot . '/badges/renderer.php');
+
+    if ($USER->id == $userid || has_capability('moodle/badges:viewotherbadges', context_user::instance($USER->id))) {
+        $records = badges_get_user_badges($userid, $courseid, null, null, null, true);
+        $renderer = new core_badges_renderer($PAGE, '');
+
+        // Print local badges.
+        if ($records) {
+            $left = get_string('localbadgesp', 'badges', $SITE->fullname);
+            $right = $renderer->print_badges_list($records, $userid, true);
+            echo html_writer::tag('dt', $left);
+            echo html_writer::tag('dd', $right);
+        }
+
+        // Print external badges.
+        if ($courseid == 0 && $CFG->badges_allowexternalbackpack) {
+            $backpack = get_backpack_settings($userid);
+            if (isset($backpack->totalbadges) && $backpack->totalbadges !== 0) {
+                $left = get_string('externalbadgesp', 'badges');
+                $right = $renderer->print_badges_list($backpack->badges, $userid, true, true);
+                echo html_writer::tag('dt', $left);
+                echo html_writer::tag('dd', $right);
+            }
+        }
+    }
+}
+
+/**
+ * Checks if badges can be pushed to external backpack.
+ *
+ * @return string Code of backpack accessibility status.
+ */
+function badges_check_backpack_accessibility() {
+    global $CFG;
+    include_once $CFG->libdir . '/filelib.php';
+
+    // Using fake assertion url to check whether backpack can access the web site.
+    $fakeassertion = new moodle_url('/badges/assertion.php', array('b' => 'abcd1234567890'));
+
+    // Curl request to http://backpack.openbadges.org/baker.
+    $curl = new curl();
+    $options = array(
+        'FRESH_CONNECT' => true,
+        'RETURNTRANSFER' => true,
+        'FORBID_REUSE' => true,
+        'HEADER' => 0,
+        'CONNECTTIMEOUT_MS' => 1000,
+    );
+    $location = 'http://backpack.openbadges.org/baker';
+    $out = $curl->get($location, array('assertion' => $fakeassertion->out(false)), $options);
+
+    $data = json_decode($out);
+    if (!empty($curl->error)) {
+        return 'curl-request-timeout';
+    } else {
+        if (isset($data->code) && $data->code == 'http-unreachable') {
+            return 'http-unreachable';
+        } else {
+            return 'available';
+        }
+    }
+
+    return false;
+}
index 235c4be..389312f 100644 (file)
@@ -1036,6 +1036,8 @@ class completion_info {
         }
         $transaction->allow_commit();
 
+        events_trigger('activity_completion_changed', $data);
+
         if ($data->userid == $USER->id) {
             $SESSION->completioncache[$cm->course][$cm->id] = $data;
             // reset modinfo for user (no need to call rebuild_course_cache())
index 4950571..0f33af8 100644 (file)
@@ -482,6 +482,11 @@ function cron_run() {
         }
     }
 
+    // Run badges review cron.
+    mtrace("Starting badges cron...");
+    require_once($CFG->dirroot . '/badges/cron.php');
+    badge_cron();
+    mtrace('done.');
 
     // cleanup file trash - not very important
     $fs = get_file_storage();
index 1ad3a58..d5fa85f 100644 (file)
@@ -1878,5 +1878,148 @@ $capabilities = array(
             'manager' => CAP_ALLOW,
             'editingteacher' => CAP_ALLOW,
         )
+    ),
+
+    // Badges.
+    'moodle/badges:manageglobalsettings' => array(
+        'riskbitmask'  => RISK_DATALOSS | RISK_CONFIG,
+        'captype'      => 'write',
+        'contextlevel' => CONTEXT_SYSTEM,
+        'archetypes'   => array(
+            'manager'       => CAP_ALLOW,
+            'student'       => CAP_PREVENT
+        )
+    ),
+
+    // View available bad