Merge branch 'm25_MDL-38790_Missing_setType_in_My_private_files_form' of https:/...
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 2 Apr 2013 19:06:21 +0000 (21:06 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 2 Apr 2013 19:06:21 +0000 (21:06 +0200)
178 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/courses.php
admin/settings/subsystems.php
admin/settings/top.php
admin/tests/behat/behat_admin.php [new file with mode: 0644]
admin/tests/behat/display_short_names.feature [new file with mode: 0644]
admin/tests/behat/upload_users.feature [new file with mode: 0644]
admin/tool/behat/cli/init.php
admin/tool/behat/renderer.php
backup/util/ui/renderer.php
backup/util/ui/restore_ui_components.php
backup/util/ui/tests/behat/backup_courses.feature [new file with mode: 0644]
backup/util/ui/tests/behat/behat_backup.php [new file with mode: 0644]
backup/util/ui/tests/behat/duplicate_activities.feature [new file with mode: 0644]
backup/util/ui/tests/behat/import_course.feature [new file with mode: 0644]
backup/util/ui/tests/behat/restore_moodle2_courses.feature [new file with mode: 0644]
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]
blocks/comments/tests/behat/add_comment.feature [new file with mode: 0644]
blocks/comments/tests/behat/behat_block_comments.php [new file with mode: 0644]
blocks/comments/tests/behat/delete_comment.feature [new file with mode: 0644]
blocks/tests/behat/add_blocks.feature
blocks/tests/behat/behat_blocks.php
completion/tests/behat/behat_completion.php [new file with mode: 0644]
completion/tests/behat/enable_manual_complete_mark.feature [new file with mode: 0644]
completion/tests/behat/restrict_section_availability.feature [new file with mode: 0644]
course/lib.php
course/modlib.php
course/tests/behat/activities_group_icons.feature [new file with mode: 0644]
course/tests/behat/activities_indentation.feature [new file with mode: 0644]
course/tests/behat/activities_visibility_icons.feature [new file with mode: 0644]
course/tests/behat/add_activities.feature
course/tests/behat/behat_course.php
course/tests/behat/force_group_mode.feature [new file with mode: 0644]
course/tests/behat/paged_course_navigation.feature [new file with mode: 0644]
course/tests/behat/restrict_available_activities.feature [new file with mode: 0644]
course/tests/behat/section_highlighting.feature [new file with mode: 0644]
course/tests/behat/section_visibility.feature [new file with mode: 0644]
course/tests/courselib_test.php
enrol/cohort/edit_form.php
enrol/locallib.php
enrol/manual/edit_form.php
enrol/paypal/edit.php
enrol/paypal/edit_form.php
enrol/renderer.php
enrol/self/edit_form.php
enrol/users.php
enrol/users_forms.php
lang/en/backup.php
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/behat/behat_base.php
lib/behat/behat_field_manager.php
lib/behat/form_field/behat_form_field.php
lib/behat/form_field/behat_form_modvisible.php [new file with mode: 0644]
lib/completionlib.php
lib/cronlib.php
lib/datalib.php
lib/db/access.php
lib/db/events.php
lib/db/install.xml
lib/db/upgrade.php
lib/filelib.php
lib/formslib.php
lib/moodlelib.php
lib/navigationlib.php
lib/pluginlib.php
lib/tests/behat/behat_forms.php
lib/tests/behat/behat_general.php
lib/tests/datalib_test.php
lib/tests/fixtures/upload_users.csv
lib/tests/statslib_test.php
mod/forum/tests/behat/add_forum.feature [new file with mode: 0644]
mod/forum/tests/behat/behat_mod_forum.php [new file with mode: 0644]
mod/forum/tests/behat/completion_condition_number_discussions.feature [new file with mode: 0644]
mod/forum/tests/behat/discussion_display.feature [new file with mode: 0644]
mod/forum/tests/behat/edit_post_student.feature [new file with mode: 0644]
mod/forum/tests/behat/edit_post_teacher.feature [new file with mode: 0644]
mod/forum/tests/behat/single_forum_discussion.feature [new file with mode: 0644]
mod/forum/tests/behat/track_read_posts.feature [new file with mode: 0644]
mod/glossary/mod_form.php
mod/quiz/attempt.php
mod/quiz/attemptlib.php
mod/quiz/autosave.ajax.php [new file with mode: 0644]
mod/quiz/lang/en/quiz.php
mod/quiz/settings.php
mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave-coverage.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave-debug.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave-min.js [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave.js [new file with mode: 0644]
mod/quiz/yui/src/autosave/build.json [new file with mode: 0644]
mod/quiz/yui/src/autosave/js/autosave.js [new file with mode: 0644]
mod/quiz/yui/src/autosave/meta/autosave.json [new file with mode: 0644]
mod/scorm/mod_form.php
mod/survey/tests/behat/survey_types.feature [new file with mode: 0644]
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]
question/behaviour/behaviourbase.php
question/engine/datalib.php
question/engine/lib.php
question/engine/questionattempt.php
question/engine/questionusage.php
question/engine/tests/helpers.php
question/engine/tests/questionattempt_db_test.php [new file with mode: 0644]
question/engine/tests/questionattempt_test.php
question/engine/tests/questionattempt_with_steps_test.php [new file with mode: 0644]
question/engine/tests/questionusage_autosave_test.php [new file with mode: 0644]
question/engine/tests/unitofwork_test.php
question/tests/generator/lib.php
question/type/match/tests/walkthrough_test.php
question/type/multianswer/edit_multianswer_form.php
question/type/multianswer/tests/walkthrough_test.php
question/type/numerical/tests/walkthrough_test.php
question/type/shortanswer/tests/helper.php
question/type/upgrade.txt
repository/tests/behat/behat_filepicker.php [moved from lib/tests/behat/behat_filepicker.php with 99% similarity]
repository/tests/behat/create_folders.feature [new file with mode: 0644]
repository/tests/behat/zip_and_unzip.feature [moved from admin/tool/behat/tests/behat/manipulate_filepicker.feature with 66% similarity]
repository/upload/tests/behat/upload_file.feature
theme/base/style/core.css
theme/mymobile/config.php
user/profile.php
user/selector/lib.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..7e6f41e
--- /dev/null
@@ -0,0 +1,84 @@
+<?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;
+
+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))) {
+
+    require_once($CFG->libdir . '/badgeslib.php');
+
+    $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 75b3246..8be5652 100644 (file)
@@ -135,6 +135,11 @@ if ($hassiteconfig
     $temp->add(new admin_setting_configcheckbox_with_lock('backup/backup_general_histories', new lang_string('generalhistories','backup'), new lang_string('configgeneralhistories','backup'), array('value'=>0, 'locked'=>0)));
     $ADMIN->add('backups', $temp);
 
+    // Create a page for general import configuration and defaults.
+    $temp = new admin_settingpage('importgeneralsettings', new lang_string('importgeneralsettings', 'backup'), 'moodle/backup:backupcourse');
+    $temp->add(new admin_setting_configtext('backup/import_general_maxresults', new lang_string('importgeneralmaxresults', 'backup'), new lang_string('importgeneralmaxresults_desc', 'backup'), 10));
+    $ADMIN->add('backups', $temp);
+
     // Create a page for automated backups configuration and defaults.
     $temp = new admin_settingpage('automated', new lang_string('automatedsetup','backup'), 'moodle/backup:backupcourse');
 
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/admin/tests/behat/behat_admin.php b/admin/tests/behat/behat_admin.php
new file mode 100644 (file)
index 0000000..cdca881
--- /dev/null
@@ -0,0 +1,97 @@
+<?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/>.
+
+/**
+ * Steps definitions related with administration.
+ *
+ * @package   core
+ * @category  test
+ * @copyright 2013 David Monllaó
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
+
+require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
+require_once(__DIR__ . '/../../../lib/behat/behat_field_manager.php');
+
+use Behat\Gherkin\Node\TableNode as TableNode,
+    Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
+
+/**
+ * Site administration level steps definitions.
+ *
+ * @package    core
+ * @category   test
+ * @copyright  2013 David Monllaó
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class behat_admin extends behat_base {
+
+    /**
+     * Sets the specified site settings. A table with | Setting label | value | is expected.
+     *
+     * @Given /^I set the following administration settings values:$/
+     * @param TableNode $table
+     */
+    public function i_set_the_following_administration_settings_values(TableNode $table) {
+
+        if (!$data = $table->getRowsHash()) {
+            return;
+        }
+
+        foreach ($data as $label => $value) {
+
+            // We expect admin block to be visible, otherwise go to homepage.
+            if (!$this->getSession()->getPage()->find('css', '.block_settings')) {
+                $this->getSession()->visit($this->locate_path('/'));
+                $this->getSession()->wait(self::TIMEOUT, '(document.readyState === "complete")');
+            }
+
+            // Search by label.
+            $searchbox = $this->find_field('Search in settings');
+            $searchbox->setValue($label);
+            $submitsearch = $this->find('css', 'form.adminsearchform input[type=submit]');
+            $submitsearch->press();
+
+            $this->getSession()->wait(self::TIMEOUT, '(document.readyState === "complete")');
+
+            // Admin settings does not use the same DOM structure than other moodle forms
+            // but we also need to use lib/behat/form_field/* to deal with the different moodle form elements.
+            $exception = new ElementNotFoundException($this->getSession(), '"' . $label . '" administration setting ');
+            $fieldxpath = "//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]
+[@id=//label[contains(normalize-space(string(.)), '" . $label . "')]/@for]";
+            $fieldnode = $this->find('xpath', $fieldxpath, $exception);
+            $formfieldtypenode = $this->find('xpath', $fieldxpath . "/ancestor::div[@class='form-setting']
+/child::div[contains(concat(' ', @class, ' '),  ' form-')]/child::*/parent::div");
+
+            // Getting the class which contains the field type.
+            $classes = explode(' ', $formfieldtypenode->getAttribute('class'));
+            foreach ($classes as $class) {
+                if (substr($class, 0, 5) == 'form-') {
+                    $type = substr($class, 5);
+                }
+            }
+
+            // Instantiating the appropiate field type.
+            $field = behat_field_manager::get_field_instance($type, $fieldnode, $this->getSession());
+            $field->set_value($value);
+
+            $this->find_button('Save changes')->press();
+        }
+    }
+
+}
diff --git a/admin/tests/behat/display_short_names.feature b/admin/tests/behat/display_short_names.feature
new file mode 100644 (file)
index 0000000..836d753
--- /dev/null
@@ -0,0 +1,22 @@
+@admin
+Feature: Display extended course names
+  In order to display more info about the courses
+  As a moodle admin
+  I need to display courses short names along with courses full names
+
+  Background:
+    Given the following "courses" exists:
+      | fullname | shortname | category |
+      | Course fullname | C_shortname | 0 |
+    And I log in as "admin"
+
+  Scenario: Courses list without extended course names (default value)
+    Then I should see "Course fullname"
+    And I should not see "C_shortname Course fullname"
+
+  Scenario: Courses list with extended course names
+    Given I click on "Courses" "link" in the "//div[@id='settingsnav']//descendant::li[contains(concat(' ', @class, ' '), ' type_setting ')][not(contains(., 'Site administration'))][contains(., 'Appearance')]" "xpath_element"
+    And I check "Display extended course names"
+    When I press "Save changes"
+    And I am on homepage
+    Then I should see "C_shortname Course fullname"
diff --git a/admin/tests/behat/upload_users.feature b/admin/tests/behat/upload_users.feature
new file mode 100644 (file)
index 0000000..e61208e
--- /dev/null
@@ -0,0 +1,46 @@
+@admin @_only_local
+Feature: Upload users
+  In order to add users to the system
+  As a moodle admin
+  I need to upload files containing the users data
+
+  @javascript
+  Scenario: Upload users enrolling them on courses and groups
+    Given the following "courses" exists:
+      | fullname | shortname | category |
+      | Maths | math102 | 0 |
+    And the following "groups" exists:
+      | name | course | idnumber |
+      | Section 1 | math102 | S1 |
+      | Section 3 | math102 | S3 |
+    And I log in as "admin"
+    And I expand "Front page settings" node
+    And I expand "Site administration" node
+    And I expand "Users" node
+    And I expand "Accounts" node
+    And I follow "Upload users"
+    When I upload "lib/tests/fixtures/upload_users.csv" file to "File" filepicker
+    And I press "Upload users"
+    Then I should see "Upload users preview"
+    And I should see "Tom"
+    And I should see "Jones"
+    And I should see "verysecret"
+    And I should see "jonest@someplace.edu"
+    And I should see "Reznor"
+    And I should see "course1"
+    And I should see "math102"
+    And I should see "group1"
+    And I should see "Section 1"
+    And I press "Upload users"
+    And I press "Continue"
+    And I follow "Browse list of users"
+    And I should see "Tom Jones"
+    And I should see "Trent Reznor"
+    And I should see "reznor@someplace.edu"
+    And I am on homepage
+    And I follow "Maths"
+    And I expand "Users" node
+    And I follow "Groups"
+    And I select "Section 1 (1)" from "groups"
+    And I wait "4" seconds
+    And the "members" select box should contain "Tom Jones"
index 1916367..ca68c16 100644 (file)
@@ -65,7 +65,7 @@ if ($code == 0) {
     // Changing to moodle dirroot to run composer related commands at project level.
     chdir(__DIR__ . '/../../../..');
     if (!file_exists(__DIR__ . '/../../../../composer.phar')) {
-        passthru("curl http://getcomposer.org/install | php", $code);
+        passthru("curl http://getcomposer.org/installer | php", $code);
         if ($code != 0) {
             exit($code);
         }
index 0df7e3a..503bc09 100644 (file)
@@ -89,7 +89,7 @@ class tool_behat_renderer extends plugin_renderer_base {
             $stepsdefinitions = implode('', $stepsdefinitions);
 
             // Replace text selector type arguments with a user-friendly select.
-            $stepsdefinitions = preg_replace_callback('/(TEXT_SELECTOR_STRING)/',
+            $stepsdefinitions = preg_replace_callback('/(TEXT_SELECTOR\d?_STRING)/',
                 function ($matches) {
                     return html_writer::select(behat_command::$allowedtextselectors, uniqid());
                 },
@@ -97,7 +97,7 @@ class tool_behat_renderer extends plugin_renderer_base {
             );
 
             // Replace selector type arguments with a user-friendly select.
-            $stepsdefinitions = preg_replace_callback('/(SELECTOR_STRING)/',
+            $stepsdefinitions = preg_replace_callback('/(SELECTOR\d?_STRING)/',
                 function ($matches) {
                     return html_writer::select(behat_command::$allowedselectors, uniqid());
                 },
index 7b8dfe2..b9a2277 100644 (file)
@@ -590,8 +590,14 @@ class core_backup_renderer extends plugin_renderer_base {
             return $output;
         }
 
-        $output .= html_writer::tag('div', get_string('totalcoursesearchresults', 'backup', $component->get_count()), array('class'=>'ics-totalresults'));
+        $countstr = '';
+        if ($component->has_more_results()) {
+            $countstr = get_string('morecoursesearchresults', 'backup', $component->get_count());
+        } else {
+            $countstr = get_string('totalcoursesearchresults', 'backup', $component->get_count());
+        }
 
+        $output .= html_writer::tag('div', $countstr, array('class'=>'ics-totalresults'));
         $output .= html_writer::start_tag('div', array('class' => 'ics-results'));
 
         $table = new html_table();
@@ -610,6 +616,14 @@ class core_backup_renderer extends plugin_renderer_base {
             );
             $table->data[] = $row;
         }
+        if ($component->has_more_results()) {
+            $cell = new html_table_cell(get_string('moreresults', 'backup'));
+            $cell->colspan = 3;
+            $cell->attributes['class'] = 'notifyproblem';
+            $row = new html_table_row(array($cell));
+            $row->attributes['class'] = 'rcs-course';
+            $table->data[] = $row;
+        }
         $output .= html_writer::table($table);
         $output .= html_writer::end_tag('div');
 
index 7ebaea8..e1aca51 100644 (file)
@@ -39,7 +39,6 @@ abstract class restore_search_base implements renderable {
      */
     static $VAR_SEARCH = 'search';
 
-    static $MAXRESULTS = 10;
     /**
      * The current search string
      * @var string|null
@@ -65,6 +64,16 @@ abstract class restore_search_base implements renderable {
      * @var array
      */
     private $requiredcapabilities = array();
+    /**
+     * Max number of courses to return in a search.
+     * @var int
+     */
+    private $maxresults = null;
+    /**
+     * Indicates if we have more than maxresults found.
+     * @var boolean
+     */
+    private $hasmoreresults = false;
 
     /**
      * Constructor
@@ -73,6 +82,7 @@ abstract class restore_search_base implements renderable {
     public function __construct(array $config=array()) {
 
         $this->search = optional_param($this->get_varsearch(), self::DEFAULT_SEARCH, PARAM_NOTAGS);
+        $this->maxresults = get_config('backup', 'import_general_maxresults');
 
         foreach ($config as $name=>$value) {
             $method = 'set_'.$name;
@@ -177,8 +187,8 @@ abstract class restore_search_base implements renderable {
         foreach ($this->requiredcapabilities as $cap) {
             $requiredcaps[] = $cap['capability'];
         }
-        // Iterate while we have records and haven't reached MAXRESULTS
-        while ($totalcourses > $offs and $this->totalcount < self::$MAXRESULTS) {
+        // Iterate while we have records and haven't reached $this->maxresults.
+        while ($totalcourses > $offs and $this->totalcount < $this->maxresults) {
             $resultset = $DB->get_records_sql($sql, $params, $offs, $blocksz);
             foreach ($resultset as $result) {
                 context_instance_preload($result);
@@ -189,11 +199,14 @@ abstract class restore_search_base implements renderable {
                         continue;
                     }
                 }
-                $this->results[$result->id] = $result;
-                $this->totalcount++;
-                if ($this->totalcount >= self::$MAXRESULTS) {
+                // Check if we are over the limit.
+                if ($this->totalcount+1 > $this->maxresults) {
+                    $this->hasmoreresults = true;
                     break;
                 }
+                // If not, then continue.
+                $this->totalcount++;
+                $this->results[$result->id] = $result;
             }
             $offs += $blocksz;
         }
@@ -202,7 +215,10 @@ abstract class restore_search_base implements renderable {
     }
 
     final public function has_more_results() {
-        return $this->get_count() >= self::$MAXRESULTS;
+        if ($this->results === null) {
+            $this->search();
+        }
+        return $this->hasmoreresults;
     }
 
     /**
diff --git a/backup/util/ui/tests/behat/backup_courses.feature b/backup/util/ui/tests/behat/backup_courses.feature
new file mode 100644 (file)
index 0000000..584354e
--- /dev/null
@@ -0,0 +1,37 @@
+@backup
+Feature: Backup Moodle courses
+  In order to save and store course contents
+  As a moodle admin
+  I need to create backups of courses
+
+  Background:
+    Given the following "courses" exists:
+      | fullname | shortname | category |
+      | Course 1 | C1 | 0 |
+    And I log in as "admin"
+
+  @javascript
+  Scenario: Backup a course providing options
+    When I backup "Course 1" course using this options:
+    Then I should see "Restore"
+    And I click on "Restore" "link" in the ".backup-files-table" "css_element"
+    And I should see "URL of backup"
+    And I should see "Anonymize user information"
+
+  @javascript
+  Scenario: Backup a course with default options
+    When I backup "Course 1" course using this options:
+      | Filename | test_backup.mbz |
+      | Include calendar events | 0 |
+      | Include course logs | 1 |
+      | setting_section_section_5_userinfo | 0 |
+      | setting_section_section_5_included | 0 |
+    Then I should see "Restore"
+    And I click on "Restore" "link" in the ".backup-files-table" "css_element"
+    And I should not see "Section 3"
+    And I press "Continue"
+    And I click on "Continue" "button" in the ".bcs-current-course" "css_element"
+    And I click on "//div[contains(concat(' ', @class, ' '), ' fitem ')][contains(., 'Include calendar events')]/descendant::img" "xpath_element"
+    And I click on "setting_root_logs" "checkbox" in the "//div[contains(@class, 'fitem')][contains(., 'Include course logs')]" "xpath_element"
+    And I press "Cancel"
+    And I click on "Cancel" "button" in the ".confirmation-dialogue" "css_element"
diff --git a/backup/util/ui/tests/behat/behat_backup.php b/backup/util/ui/tests/behat/behat_backup.php
new file mode 100644 (file)
index 0000000..79c05e1
--- /dev/null
@@ -0,0 +1,346 @@
+<?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/>.
+
+/**
+ * Backup and restore actions to help behat feature files writting.
+ *
+ * @package    core
+ * @category   test
+ * @copyright  2013 David Monllaó
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
+
+require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
+require_once(__DIR__ . '/../../../../../lib/behat/behat_field_manager.php');
+
+use Behat\Gherkin\Node\TableNode as TableNode,
+    Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException,
+    Behat\Mink\Exception\ExpectationException as ExpectationException;
+
+/**
+ * Backup-related steps definitions.
+ *
+ * @package    core
+ * @category   test
+ * @copyright  2013 David Monllaó
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class behat_backup extends behat_base {
+
+    /**
+     * Backups the specified course using the provided options. If you are interested in restoring this backup would be useful to provide a 'Filename' option.
+     *
+     * @Given /^I backup "(?P<course_fullname_string>(?:[^"]|\\")*)" course using this options:$/
+     * @param string $backupcourse
+     * @param TableNode $options Backup options or false if no options provided
+     */
+    public function i_backup_course_using_this_options($backupcourse, $options = false) {
+
+        // We can not use other steps here as we don't know where the provided data
+        // table elements are used, and we need to catch exceptions contantly.
+
+        // Go to homepage.
+        $this->getSession()->visit($this->locate_path('/'));
+
+        // Click the course link.
+        $this->find_link($backupcourse)->click();
+
+        // Click the backup link.
+        $this->find_link('Backup')->click();
+
+        // Initial settings.
+        $this->fill_backup_restore_form($options);
+        $this->find_button('Next')->press();
+
+        // Schema settings.
+        $this->fill_backup_restore_form($options);
+        $this->find_button('Next')->press();
+
+        // Confirmation and review, backup filename can also be specified.
+        $this->fill_backup_restore_form($options);
+        $this->find_button('Perform backup')->press();
+
+        // Waiting for it to finish.
+        $this->wait(10);
+
+        // Last backup continue button.
+        $this->find_button('Continue')->press();
+    }
+
+    /**
+     * Imports the specified origin course into the other course using the provided options.
+     *
+     * Keeping it separatelly from backup & restore, it the number of
+     * steps and duplicate code becomes bigger a common method should
+     * be generalized.
+     *
+     * @Given /^I import "(?P<from_course_fullname_string>(?:[^"]|\\")*)" course into "(?P<to_course_fullname_string>(?:[^"]|\\")*)" course using this options:$/
+     * @param string $fromcourse
+     * @param string $tocourse
+     * @param TableNode $options
+     */
+    public function i_import_course_into_course($fromcourse, $tocourse, $options = false) {
+
+        // We can not use other steps here as we don't know where the provided data
+        // table elements are used, and we need to catch exceptions contantly.
+
+        // Go to homepage.
+        $this->getSession()->visit($this->locate_path('/'));
+
+        // Click the course link.
+        $this->find_link($tocourse)->click();
+
+        // Click the backup link.
+        $this->find_link('Import')->click();
+
+        // Select the course.
+        $exception = new ExpectationException('"' . $fromcourse . '" course not found in the list of courses to import from', $this->getSession());
+
+        $fromcourse = str_replace("'", "\'", $fromcourse);
+        $xpath = "//div[contains(concat(' ', @class, ' '), ' ics-results ')]
+/descendant::tr[contains(., '" . $fromcourse . "')]
+/descendant::input[@type='radio']";
+        $radionode = $this->find('xpath', $xpath, $exception);
+        $radionode->check();
+        $radionode->click();
+
+        $this->find_button('Continue')->press();
+
+        // Initial settings.
+        $this->fill_backup_restore_form($options);
+        $this->find_button('Next')->press();
+
+        // Schema settings.
+        $this->fill_backup_restore_form($options);
+        $this->find_button('Next')->press();
+
+        // Run it.
+        $this->find_button('Perform import')->press();
+        $this->wait();
+
+        // Continue and redirect to 'to' course.
+        $this->find_button('Continue')->press();
+    }
+
+    /**
+     * Restores the backup into the specified course and the provided options. You should be in the 'Restore' page where the backup is.
+     *
+     * @Given /^I restore "(?P<backup_filename_string>(?:[^"]|\\")*)" backup into "(?P<existing_course_fullname_string>(?:[^"]|\\")*)" course using this options:$/
+     * @param string $backupfilename
+     * @param string $existingcourse
+     * @param TableNode $options Restore forms options or false if no options provided
+     */
+    public function i_restore_backup_into_course_using_this_options($backupfilename, $existingcourse, $options = false) {
+
+        // Confirm restore.
+        $this->select_backup($backupfilename);
+
+        // Selecting the specified course (we can not call behat_forms::select_radio here as is in another behat subcontext).
+        $existingcourse = str_replace("'", "\'", $existingcourse);
+        $radionode = $this->find('xpath', "//div[contains(@class, 'bcs-existing-course')]
+/descendant::div[@class='restore-course-search']
+/descendant::tr[contains(., '" . $existingcourse . "')]
+/descendant::input[@type='radio']");
+        $radionode->check();
+        $radionode->click();
+
+        // Pressing the continue button of the restore into an existing course section.
+        $continuenode = $this->find('xpath', "//div[contains(@class, 'bcs-existing-course')]/descendant::input[@type='submit'][@value='Continue']");
+        $continuenode->click();
+        $this->wait();
+
+        // Common restore process using provided key/value options.
+        $this->process_restore($options);
+    }
+
+    /**
+     * Restores the specified backup into a new course using the provided options. You should be in the 'Restore' page where the backup is.
+     *
+     * @Given /^I restore "(?P<backup_filename_string>(?:[^"]|\\")*)" backup into a new course using this options:$/
+     * @param string $backupfilename
+     * @param TableNode $options Restore forms options or false if no options provided
+     */
+    public function i_restore_backup_into_a_new_course_using_this_options($backupfilename, $options = false) {
+
+        // Confirm restore.
+        $this->select_backup($backupfilename);
+
+        // The first category in the list.
+        $radionode = $this->find('xpath', "//div[contains(@class, 'bcs-new-course')]
+/descendant::div[@class='restore-course-search']
+/descendant::input[@type='radio']");
+        $radionode->check();
+        $radionode->click();
+
+        // Pressing the continue button of the restore into an existing course section.
+        $continuenode = $this->find('xpath', "//div[contains(@class, 'bcs-new-course')]/descendant::input[@type='submit'][@value='Continue']");
+        $continuenode->click();
+        $this->wait();
+
+        // Common restore process using provided key/value options.
+        $this->process_restore($options);
+    }
+
+    /**
+     * Merges the backup into the current course using the provided restore options. You should be in the 'Restore' page where the backup is.
+     *
+     * @Given /^I merge "(?P<backup_filename_string>(?:[^"]|\\")*)" backup into the current course using this options:$/
+     * @param string $backupfilename
+     * @param TableNode $options Restore forms options or false if no options provided
+     */
+    public function i_merge_backup_into_the_current_course($backupfilename, $options = false) {
+
+        // Confirm restore.
+        $this->select_backup($backupfilename);
+
+        // Merge without deleting radio option.
+        $radionode = $this->find('xpath', "//div[contains(@class, 'bcs-current-course')]
+/descendant::input[@type='radio'][@name='target'][@value='1']");
+        $radionode->check();
+        $radionode->click();
+
+        // Pressing the continue button of the restore merging section.
+        $continuenode = $this->find('xpath', "//div[contains(@class, 'bcs-current-course')]/descendant::input[@type='submit'][@value='Continue']");
+        $continuenode->click();
+        $this->wait();
+
+        // Common restore process using provided key/value options.
+        $this->process_restore($options);
+    }
+
+    /**
+     * Merges the backup into the current course after deleting this contents, using the provided restore options. You should be in the 'Restore' page where the backup is.
+     *
+     * @Given /^I merge "(?P<backup_filename_string>(?:[^"]|\\")*)" backup into the current course after deleting it's contents using this options:$/
+     * @param string $backupfilename
+     * @param TableNode $options Restore forms options or false if no options provided
+     */
+    public function i_merge_backup_into_current_course_deleting_its_contents($backupfilename, $options = false) {
+
+        // Confirm restore.
+        $this->select_backup($backupfilename);
+
+        // Delete contents radio option.
+        $radionode = $this->find('xpath', "//div[contains(@class, 'bcs-current-course')]
+/descendant::input[@type='radio'][@name='target'][@value='0']");
+        $radionode->check();
+        $radionode->click();
+
+        // Pressing the continue button of the restore merging section.
+        $continuenode = $this->find('xpath', "//div[contains(@class, 'bcs-current-course')]/descendant::input[@type='submit'][@value='Continue']");
+        $continuenode->click();
+        $this->wait();
+
+        // Common restore process using provided key/value options.
+        $this->process_restore($options);
+    }
+
+    /**
+     * Selects the backup to restore.
+     *
+     * @throws ExpectationException
+     * @param string $backupfilename
+     * @return void
+     */
+    protected function select_backup($backupfilename) {
+
+        // Using xpath as there are other restore links before this one.
+        $exception = new ExpectationException('The "' . $backupfilename . '" backup file can not be found in this page', $this->getSession());
+        $xpath = "//tr[contains(., '" . $backupfilename . "')]/descendant::a[contains(., 'Restore')]";
+        $restorelink = $this->find('xpath', $xpath, $exception);
+        $restorelink->click();
+
+        // Confirm the backup contents.
+        $restore = $this->find_button('Continue')->press();
+    }
+
+    /**
+     * Executes the common steps of all restore processes.
+     *
+     * @param TableNode $options The backup and restore options or false if no options provided
+     * @return void
+     */
+    protected function process_restore($options) {
+
+        // We can not use other steps here as we don't know where the provided data
+        // table elements are used, and we need to catch exceptions contantly.
+
+        // Settings.
+        $this->fill_backup_restore_form($options);
+        $this->find_button('Next')->press();
+
+        // Schema.
+        $this->fill_backup_restore_form($options);
+        $this->find_button('Next')->press();
+
+        // Review, no options here.
+        $this->find_button('Perform restore')->press();
+        $this->wait(10);
+
+        // Last restore continue button, redirected to restore course after this.
+        $this->find_button('Continue')->press();
+    }
+
+    /**
+     * Tries to fill the current page form elements with the provided options.
+     *
+     * This step is slow as it spins over each provided option, we are
+     * not expected to have lots of provided options, anyways, is better
+     * to be conservative and wait for the elements to appear rather than
+     * to have false failures.
+     *
+     * @param TableNode $options The backup and restore options or false if no options provided
+     * @return void
+     */
+    protected function fill_backup_restore_form($options) {
+
+        // Nothing to fill if no options are provided.
+        if (!$options) {
+            return;
+        }
+
+        // If we find any of the provided options in the current form we should set the value.
+        $datahash = $options->getRowsHash();
+        foreach ($datahash as $locator => $value) {
+
+            try {
+                $fieldnode = $this->find_field($locator);
+                $field = behat_field_manager::get_field($fieldnode, $locator, $this->getSession());
+                $field->set_value($value);
+
+            } catch (ElementNotFoundException $e) {
+                // Next provided option then, this one should be part of another page's fields.
+            }
+        }
+    }
+
+    /**
+     * Waits until the DOM is ready.
+     *
+     * @param int To override the default timeout
+     * @return void
+     */
+    protected function wait($timeout = false) {
+
+        if (!$timeout) {
+            $timeout = self::TIMEOUT;
+        }
+        $this->getSession()->wait($timeout, '(document.readyState === "complete")');
+    }
+
+}
diff --git a/backup/util/ui/tests/behat/duplicate_activities.feature b/backup/util/ui/tests/behat/duplicate_activities.feature
new file mode 100644 (file)
index 0000000..f23efc4
--- /dev/null
@@ -0,0 +1,33 @@
+@backup
+Feature: Duplicate activities
+  In order to set up my course contents quickly
+  As a moodle teacher
+  I need to duplicate activities inside the same course
+
+  @javascript
+  Scenario: Duplicate an activity
+    Given the following "courses" exists:
+      | fullname | shortname | category |
+      | Course 1 | C1 | 0 |
+    And the following "users" exists:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@asd.com |
+    And the following "course enrolments" exists:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add a "Database" to section "1" and I fill the form with:
+      | Name | Test database name |
+      | Description | Test database description |
+    When I click on "Duplicate" "link" in the "#section-1" "css_element"
+    And I press "Continue"
+    And I press "Edit the new copy"
+    And I fill the moodle form with:
+      | Name | Duplicated database name |
+      | Description | Duplicated database description |
+    And I press "Save and return to course"
+    Then I should see "Test database name" in the "#section-1" "css_element"
+    And I should see "Duplicated database name" in the "#section-1" "css_element"
+    And "Test database name" "link" should appear before "Duplicated database name" "link"
diff --git a/backup/util/ui/tests/behat/import_course.feature b/backup/util/ui/tests/behat/import_course.feature
new file mode 100644 (file)
index 0000000..1acf15c
--- /dev/null
@@ -0,0 +1,35 @@
+@backup
+Feature: Import course's contents into another course
+  In order to move and copy contents between courses
+  As a moodle teacher
+  I need to import a course contents into another course selecting what I want to import
+
+  @javascript
+  Scenario: Import course's contents to another course
+    Given the following "courses" exists:
+      | fullname | shortname | category |
+      | Course 1 | C1 | 0 |
+      | Course 2 | C2 | 0 |
+    And the following "users" exists:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@asd.com |
+    And the following "course enrolments" exists:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+      | teacher1 | C2 | editingteacher |
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add a "Database" to section "1" and I fill the form with:
+      | Name | Test database name |
+      | Description | Test database description |
+    And I add a "Forum" to section "2" and I fill the form with:
+      | Forum name | Test forum name |
+      | Description | Test forum description |
+    And I add the "Comments" block
+    And I add the "Recent blog entries" block
+    When I import "Course 1" course into "Course 2" course using this options:
+    Then I should see "Test database name"
+    And I should see "Test forum name"
+    And I should see "Comments"
+    And I should see "Recent blog entries"
diff --git a/backup/util/ui/tests/behat/restore_moodle2_courses.feature b/backup/util/ui/tests/behat/restore_moodle2_courses.feature
new file mode 100644 (file)
index 0000000..177a92b
--- /dev/null
@@ -0,0 +1,130 @@
+@backup
+Feature: Restore Moodle 2 course backups
+  In order to continue using my stored course contents
+  As a moodle teacher and as a moodle admin
+  I need to restore them inside other Moodle courses or in new courses
+
+  Background:
+    Given the following "courses" exists:
+      | fullname | shortname | category | format | numsections | coursedisplay |
+      | Course 1 | C1 | 0 | topics | 15 | 1 |
+      | Course 2 | C2 | 0 | topics | 5 | 0 |
+    And I log in as "admin"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add a "forum" to section "1" and I fill the form with:
+      | Forum name | Test forum name |
+      | Description | Test forum description |
+    And I add the "Community finder" block
+
+  @javascript
+  Scenario: Restore a course in another existing course
+    When I backup "Course 1" course using this options:
+      | Filename | test_backup.mbz |
+    And I restore "test_backup.mbz" backup into "Course 2" course using this options:
+    Then I should see "Course 2"
+    And I should see "Community finder"
+    And I should see "Test forum name"
+
+  @javascript
+  Scenario: Restore a course in a new course
+    When I backup "Course 1" course using this options:
+      | Filename | test_backup.mbz |
+    And I restore "test_backup.mbz" backup into a new course using this options:
+      | Course name | Course 1 restored in a new course |
+    Then I should see "Course 1 restored in a new course"
+    And I should see "Community finder"
+    And I should see "Test forum name"
+    And I follow "Edit settings"
+    And I expand all fieldsets
+    And the "id_format" field should match "Topics format" value
+    And the "Number of sections" field should match "15" value
+    And the "Course layout" field should match "Show one section per page" value
+    And I press "Cancel"
+
+  @javascript
+  Scenario: Restore a backup into the same course
+    When I backup "Course 1" course using this options:
+      | Filename | test_backup.mbz |
+    And I merge "test_backup.mbz" backup into the current course using this options:
+      | setting_section_section_5_included | 0 |
+      | setting_section_section_5_userinfo | 0 |
+    Then I should see "Course 1"
+    And I should not see "Section 3"
+    And I should see "Community finder"
+    And I should see "Test forum name"
+
+  @javascript
+  Scenario: Restore a backup into the same course removing it's contents before that
+    When I backup "Course 1" course using this options:
+      | Filename | test_backup.mbz |
+    And I follow "Course 1"
+    And I add a "Forum" to section "1" and I fill the form with:
+      | Forum name | Test forum post backup name |
+      | Description | Test forum post backup description |
+    And I follow "Restore"
+    And I merge "test_backup.mbz" backup into the current course after deleting it's contents using this options:
+      | setting_section_section_5_userinfo | 0 |
+      | setting_section_section_5_included | 0 |
+    Then I should see "Course 1"
+    And I should not see "Section 3"
+    And I should not see "Test forum post backup name"
+    And I should see "Community finder"
+    And I should see "Test forum name"
+
+  @javascript
+  Scenario: Restore a backup into a new course changing the course format afterwards
+    Given I backup "Course 1" course using this options:
+      | Filename | test_backup.mbz |
+    When I restore "test_backup.mbz" backup into a new course using this options:
+    Then I should see "Topic 1"
+    And I should see "Test forum name"
+    And I follow "Edit settings"
+    And the "id_format" field should match "Topics format" value
+    And I fill the moodle form with:
+      | id_startdate_day | 1 |
+      | id_startdate_month | January |
+      | id_startdate_year | 2020 |
+      | id_format | Weekly format |
+    And I press "Save changes"
+    And I should see "1 January - 7 January"
+    And I should see "Test forum name"
+    And I follow "Edit settings"
+    And the "id_format" field should match "Weekly format" value
+    And I fill the moodle form with:
+      | id_format | Social format |
+    And I press "Save changes"
+    And I should see "An open forum for chatting about anything you want to"
+    And I follow "Edit settings"
+    And the "id_format" field should match "Social format" value
+    And I fill the moodle form with:
+      | id_format | SCORM format |
+    And I press "Save changes"
+    And I should see "Adding a new SCORM package"
+    And I follow "Edit settings"
+    And the "id_format" field should match "SCORM format" value
+    And I press "Cancel"
+
+  @javascript
+  Scenario: Restore a backup in an existing course retaining the backup course settings
+    Given I add a "URL" to section "3" and I fill the form with:
+      | Name | Test URL name |
+      | Description | Test URL description |
+      | id_externalurl | http://www.moodle.org |
+    And I hide section "3"
+    And I hide section "7"
+    When I backup "Course 1" course using this options:
+      | Filename | test_backup.mbz |
+    And I restore "test_backup.mbz" backup into "Course 2" course using this options:
+      | Overwrite course configuration | Yes |
+    And I follow "Edit settings"
+    And I expand all fieldsets
+    Then the "id_format" field should match "Topics format" value
+    And the "Number of sections" field should match "15" value
+    And the "Course layout" field should match "Show one section per page" value
+    And I press "Cancel"
+    And section "3" should be hidden
+    And section "7" should be hidden
+    And section "15" should be visible
+    And I should see "Test URL name" in the "#section-3" "css_element"
+    And I should see "Test forum name" in the "#section-1" "css_element"
diff --git a/badges/action.php b/badges/action.php
new file mode 100644 (file)
index 0000000..33181a5
--- /dev/null
@@ -0,0 +1,160 @@
+<?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_sesskey();
+    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..64d6fe8
--- /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/>.
+
+/**
+ * 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 {
+            require_sesskey();
+            $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 {
+        require_sesskey();
+        $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.