first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+424
View File
@@ -0,0 +1,424 @@
<?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 assertion 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();
/**
* Open Badges Assertions specification 1.0 {@link https://github.com/mozilla/openbadges-backpack/wiki/Assertions}
*
* Badge asserion is defined by three parts:
* - Badge Assertion (information regarding a specific badge that was awarded to a badge earner)
* - Badge Class (general information about a badge and what it is intended to represent)
* - Issuer Class (general information of an issuing organisation)
*/
require_once($CFG->libdir . '/badgeslib.php');
require_once($CFG->dirroot . '/badges/renderer.php');
/**
* Class that represents badge assertion.
*
*/
class core_badges_assertion {
/** @var object Issued badge information from database */
private $_data;
/** @var moodle_url Issued badge url */
private $_url;
/** @var int $obversion to control version JSON-LD. */
private $_obversion = OPEN_BADGES_V2;
/**
* Constructs with issued badge unique hash.
*
* @param string $hash Badge unique hash from badge_issued table.
* @param int $obversion to control version JSON-LD.
*/
public function __construct($hash, $obversion = OPEN_BADGES_V2) {
global $DB;
$this->_data = $DB->get_record_sql('
SELECT
bi.dateissued,
bi.dateexpire,
bi.uniquehash,
u.email,
b.*,
bb.email as backpackemail
FROM
{badge} b
JOIN {badge_issued} bi
ON b.id = bi.badgeid
JOIN {user} u
ON u.id = bi.userid
LEFT JOIN {badge_backpack} bb
ON bb.userid = bi.userid
WHERE ' . $DB->sql_compare_text('bi.uniquehash', 40) . ' = ' . $DB->sql_compare_text(':hash', 40),
array('hash' => $hash), IGNORE_MISSING);
if ($this->_data) {
$this->_url = new moodle_url('/badges/badge.php', array('hash' => $this->_data->uniquehash));
} else {
$this->_url = new moodle_url('/badges/badge.php');
}
$this->_obversion = $obversion;
}
/**
* Get the local id for this badge.
*
* @return int
*/
public function get_badge_id() {
$badgeid = 0;
if ($this->_data) {
$badgeid = $this->_data->id;
}
return $badgeid;
}
/**
* Get the local id for this badge assertion.
*
* @return string
*/
public function get_assertion_hash() {
$hash = '';
if ($this->_data) {
$hash = $this->_data->uniquehash;
}
return $hash;
}
/**
* Get badge assertion.
*
* @param boolean $issued Include the nested badge issued information.
* @param boolean $usesalt Hash the identity and include the salt information for the hash.
* @return array Badge assertion.
*/
public function get_badge_assertion($issued = true, $usesalt = true) {
global $CFG;
$assertion = array();
if ($this->_data) {
$hash = $this->_data->uniquehash;
$email = empty($this->_data->backpackemail) ? $this->_data->email : $this->_data->backpackemail;
$assertionurl = new moodle_url('/badges/assertion.php', array('b' => $hash, 'obversion' => $this->_obversion));
if ($this->_obversion >= OPEN_BADGES_V2) {
$classurl = new moodle_url('/badges/badge_json.php', array('id' => $this->get_badge_id()));
} else {
$classurl = new moodle_url('/badges/assertion.php', array('b' => $hash, 'action' => 1));
}
// Required.
$assertion['uid'] = $hash;
$assertion['recipient'] = array();
if ($usesalt) {
$assertion['recipient']['identity'] = 'sha256$' . hash('sha256', $email . $CFG->badges_badgesalt);
} else {
$assertion['recipient']['identity'] = $email;
}
$assertion['recipient']['type'] = 'email'; // Currently the only supported type.
$assertion['recipient']['hashed'] = true; // We are always hashing recipient.
if ($usesalt) {
$assertion['recipient']['salt'] = $CFG->badges_badgesalt;
}
if ($issued) {
$assertion['badge'] = $classurl->out(false);
}
$assertion['verify'] = array();
$assertion['verify']['type'] = 'hosted'; // 'Signed' is not implemented yet.
$assertion['verify']['url'] = $assertionurl->out(false);
$assertion['issuedOn'] = $this->_data->dateissued;
if ($issued) {
$assertion['evidence'] = $this->_url->out(false); // Currently issued badge URL.
}
// Optional.
if (!empty($this->_data->dateexpire)) {
$assertion['expires'] = $this->_data->dateexpire;
}
$tags = $this->get_tags();
if (is_array($tags) && count($tags) > 0) {
$assertion['tags'] = $tags;
}
$this->embed_data_badge_version2($assertion, OPEN_BADGES_V2_TYPE_ASSERTION);
}
return $assertion;
}
/**
* Get badge class information.
*
* @param boolean $issued Include the nested badge issuer information.
* @return array Badge Class information.
*/
public function get_badge_class($issued = true) {
$class = [];
if ($this->_data) {
if (empty($this->_data->courseid)) {
$context = context_system::instance();
} else {
$context = context_course::instance($this->_data->courseid);
}
// Required.
$class['name'] = $this->_data->name;
$class['description'] = $this->_data->description;
$storage = get_file_storage();
$imagefile = $storage->get_file($context->id, 'badges', 'badgeimage', $this->_data->id, '/', 'f3.png');
if ($imagefile) {
$imagedata = base64_encode($imagefile->get_content());
} else {
if (defined('PHPUNIT_TEST') && PHPUNIT_TEST) {
// Unit tests the file might not exist yet.
$imagedata = '';
} else {
throw new coding_exception('Image file does not exist.');
}
}
$class['image'] = 'data:image/png;base64,' . $imagedata;
$params = ['id' => $this->get_badge_id()];
$badgeurl = new moodle_url('/badges/badgeclass.php', $params);
$class['criteria'] = $badgeurl->out(false); // Currently badge URL.
if ($issued) {
$params = ['id' => $this->get_badge_id(), 'obversion' => $this->_obversion];
$issuerurl = new moodle_url('/badges/issuer_json.php', $params);
$class['issuer'] = $issuerurl->out(false);
}
$tags = $this->get_tags();
if (is_array($tags) && count($tags) > 0) {
$class['tags'] = $tags;
}
$this->embed_data_badge_version2($class, OPEN_BADGES_V2_TYPE_BADGE);
if (!$issued) {
unset($class['issuer']);
}
}
return $class;
}
/**
* Get badge issuer information.
*
* @return array Issuer information.
*/
public function get_issuer() {
global $CFG;
$issuer = array();
if ($this->_data) {
// Required.
if ($this->_obversion == OPEN_BADGES_V1) {
$issuer['name'] = $this->_data->issuername;
$issuer['url'] = $this->_data->issuerurl;
// Optional.
if (!empty($this->_data->issuercontact)) {
$issuer['email'] = $this->_data->issuercontact;
} else {
$issuer['email'] = $CFG->badges_defaultissuercontact;
}
} else {
$badge = new badge($this->get_badge_id());
$issuer = $badge->get_badge_issuer();
}
}
$this->embed_data_badge_version2($issuer, OPEN_BADGES_V2_TYPE_ISSUER);
return $issuer;
}
/**
* Get related badges of the badge.
*
* @param badge $badge Badge object.
* @return array|bool List related badges.
*/
public function get_related_badges(badge $badge) {
global $DB;
$arraybadges = array();
$relatedbadges = $badge->get_related_badges(true);
if ($relatedbadges) {
foreach ($relatedbadges as $rb) {
$url = new moodle_url('/badges/badge_json.php', array('id' => $rb->id));
$arraybadges[] = array(
'id' => $url->out(false),
'version' => $rb->version,
'@language' => $rb->language
);
}
}
return $arraybadges;
}
/**
* Get endorsement of the badge.
*
* @return false|stdClass Endorsement information.
*/
public function get_endorsement() {
global $DB;
$endorsement = array();
$record = $DB->get_record_select('badge_endorsement', 'badgeid = ?', array($this->_data->id));
return $record;
}
/**
* Get criteria of badge class.
*
* @return array|string Criteria information.
*/
public function get_criteria_badge_class() {
$badge = new badge($this->_data->id);
$narrative = $badge->markdown_badge_criteria();
$params = ['id' => $this->get_badge_id()];
$badgeurl = new moodle_url('/badges/badgeclass.php', $params);
if (!empty($narrative)) {
$criteria = [];
$criteria['id'] = $badgeurl->out(false);
$criteria['narrative'] = $narrative;
return $criteria;
} else {
return $badgeurl->out(false);
}
}
/**
* Get alignment of the badge.
*
* @return array information.
*/
public function get_alignments() {
global $DB;
$badgeid = $this->_data->id;
$alignments = array();
$items = $DB->get_records_select('badge_alignment', 'badgeid = ?', array($badgeid));
foreach ($items as $item) {
$alignment = array('targetName' => $item->targetname, 'targetUrl' => $item->targeturl);
if ($item->targetdescription) {
$alignment['targetDescription'] = $item->targetdescription;
}
if ($item->targetframework) {
$alignment['targetFramework'] = $item->targetframework;
}
if ($item->targetcode) {
$alignment['targetCode'] = $item->targetcode;
}
$alignments[] = $alignment;
}
return $alignments;
}
/**
* Embed data of Open Badges Specification Version 2.0 to json.
*
* @param array $json for assertion, badges, issuer.
* @param string $type Content type.
*/
protected function embed_data_badge_version2(&$json, $type = OPEN_BADGES_V2_TYPE_ASSERTION) {
// Specification Version 2.0.
if ($this->_obversion >= OPEN_BADGES_V2) {
$badge = new badge($this->_data->id);
if (empty($this->_data->courseid)) {
$context = context_system::instance();
} else {
$context = context_course::instance($this->_data->courseid);
}
$hash = $this->_data->uniquehash;
$assertionsurl = new moodle_url('/badges/assertion.php', array('b' => $hash, 'obversion' => $this->_obversion));
$classurl = new moodle_url(
'/badges/badge_json.php',
array('id' => $this->get_badge_id())
);
$issuerurl = new moodle_url('/badges/issuer_json.php', ['id' => $this->get_badge_id()]);
// For assertion.
if ($type == OPEN_BADGES_V2_TYPE_ASSERTION) {
$json['@context'] = OPEN_BADGES_V2_CONTEXT;
$json['type'] = OPEN_BADGES_V2_TYPE_ASSERTION;
$json['id'] = $assertionsurl->out(false);
$json['badge'] = $this->get_badge_class();
$json['issuedOn'] = date('c', $this->_data->dateissued);
if (!empty($this->_data->dateexpire)) {
$json['expires'] = date('c', $this->_data->dateexpire);
}
unset($json['uid']);
}
// For Badge.
if ($type == OPEN_BADGES_V2_TYPE_BADGE) {
$json['@context'] = OPEN_BADGES_V2_CONTEXT;
$json['id'] = $classurl->out(false);
$json['type'] = OPEN_BADGES_V2_TYPE_BADGE;
$json['version'] = $this->_data->version;
$json['criteria'] = $this->get_criteria_badge_class();
$json['issuer'] = $this->get_issuer();
$json['@language'] = $this->_data->language;
if (!empty($relatedbadges = $this->get_related_badges($badge))) {
$json['related'] = $relatedbadges;
}
if ($endorsement = $this->get_endorsement()) {
$endorsementurl = new moodle_url('/badges/endorsement_json.php', array('id' => $this->_data->id));
$json['endorsement'] = $endorsementurl->out(false);
}
if ($alignments = $this->get_alignments()) {
$json['alignments'] = $alignments;
}
if ($this->_data->imageauthorname ||
$this->_data->imageauthoremail ||
$this->_data->imageauthorurl ||
$this->_data->imagecaption) {
$storage = get_file_storage();
$imagefile = $storage->get_file($context->id, 'badges', 'badgeimage', $this->_data->id, '/', 'f3.png');
if ($imagefile) {
$imagedata = base64_encode($imagefile->get_content());
} else {
// The file might not exist in unit tests.
if (defined('PHPUNIT_TEST') && PHPUNIT_TEST) {
$imagedata = '';
} else {
throw new coding_exception('Image file does not exist.');
}
}
$json['image'] = 'data:image/png;base64,' . $imagedata;
}
}
// For issuer.
if ($type == OPEN_BADGES_V2_TYPE_ISSUER) {
$json['@context'] = OPEN_BADGES_V2_CONTEXT;
$json['id'] = $issuerurl->out(false);
$json['type'] = OPEN_BADGES_V2_TYPE_ISSUER;
}
}
}
/**
* Get tags of the badge.
*
* @return array tags.
*/
public function get_tags(): array {
return array_values(\core_tag_tag::get_item_tags_array('core_badges', 'badge', $this->get_badge_id()));
}
}
+730
View File
@@ -0,0 +1,730 @@
<?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/>.
/**
* Communicate with backpacks.
*
* @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>
*/
namespace core_badges;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/filelib.php');
use cache;
use coding_exception;
use core_badges\external\assertion_exporter;
use core_badges\external\collection_exporter;
use core_badges\external\issuer_exporter;
use core_badges\external\badgeclass_exporter;
use curl;
use stdClass;
use context_system;
define('BADGE_ACCESS_TOKEN', 'access');
define('BADGE_USER_ID_TOKEN', 'user_id');
define('BADGE_BACKPACK_ID_TOKEN', 'backpack_id');
define('BADGE_REFRESH_TOKEN', 'refresh');
define('BADGE_EXPIRES_TOKEN', 'expires');
/**
* Class for communicating with backpacks.
*
* @package core_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
*/
class backpack_api {
/** @var string The email address of the issuer or the backpack owner. */
private $email;
/** @var string The base url used for api requests to this backpack. */
private $backpackapiurl;
/** @var integer The backpack api version to use. */
private $backpackapiversion;
/** @var string The password to authenticate requests. */
private $password;
/** @var boolean User or site api requests. */
private $isuserbackpack;
/** @var integer The id of the backpack we are talking to. */
private $backpackid;
/** @var \backpack_api_mapping[] List of apis for the user or site using api version 1 or 2. */
private $mappings = [];
/**
* Create a wrapper to communicate with the backpack.
*
* The resulting class can only do either site backpack communication or
* user backpack communication.
*
* @param stdClass $sitebackpack The site backpack record
* @param mixed $userbackpack Optional - if passed it represents the users backpack.
*/
public function __construct($sitebackpack, $userbackpack = false) {
global $CFG;
$admin = get_admin();
$this->backpackapiurl = $sitebackpack->backpackapiurl;
$this->backpackapiversion = $sitebackpack->apiversion;
$this->password = $sitebackpack->password;
$this->email = $sitebackpack->backpackemail;
$this->isuserbackpack = false;
$this->backpackid = $sitebackpack->id;
if (!empty($userbackpack)) {
$this->isuserbackpack = true;
$this->password = $userbackpack->password;
$this->email = $userbackpack->email;
}
$this->define_mappings();
// Clear the last authentication error.
backpack_api_mapping::set_authentication_error('');
}
/**
* Define the mappings supported by this usage and api version.
*/
private function define_mappings() {
if ($this->backpackapiversion == OPEN_BADGES_V2) {
if ($this->isuserbackpack) {
$mapping = [];
$mapping[] = [
'collections', // Action.
'[URL]/backpack/collections', // URL
[], // Post params.
'', // Request exporter.
'core_badges\external\collection_exporter', // Response exporter.
true, // Multiple.
'get', // Method.
true, // JSON Encoded.
true // Auth required.
];
$mapping[] = [
'user', // Action.
'[SCHEME]://[HOST]/o/token', // URL
['username' => '[EMAIL]', 'password' => '[PASSWORD]'], // Post params.
'', // Request exporter.
'oauth_token_response', // Response exporter.
false, // Multiple.
'post', // Method.
false, // JSON Encoded.
false, // Auth required.
];
$mapping[] = [
'assertion', // Action.
// Badgr.io does not return the public information about a badge
// if the issuer is associated with another user. We need to pass
// the expand parameters which are not in any specification to get
// additional information about the assertion in a single request.
'[URL]/backpack/assertions/[PARAM2]?expand=badgeclass&expand=issuer',
[], // Post params.
'', // Request exporter.
'core_badges\external\assertion_exporter', // Response exporter.
false, // Multiple.
'get', // Method.
true, // JSON Encoded.
true // Auth required.
];
$mapping[] = [
'importbadge', // Action.
// Badgr.io does not return the public information about a badge
// if the issuer is associated with another user. We need to pass
// the expand parameters which are not in any specification to get
// additional information about the assertion in a single request.
'[URL]/backpack/import',
['url' => '[PARAM]'], // Post params.
'', // Request exporter.
'core_badges\external\assertion_exporter', // Response exporter.
false, // Multiple.
'post', // Method.
true, // JSON Encoded.
true // Auth required.
];
$mapping[] = [
'badges', // Action.
'[URL]/backpack/collections/[PARAM1]', // URL
[], // Post params.
'', // Request exporter.
'core_badges\external\collection_exporter', // Response exporter.
true, // Multiple.
'get', // Method.
true, // JSON Encoded.
true // Auth required.
];
foreach ($mapping as $map) {
$map[] = true; // User api function.
$map[] = OPEN_BADGES_V2; // V2 function.
$this->mappings[] = new backpack_api_mapping(...$map);
}
} else {
$mapping = [];
$mapping[] = [
'user', // Action.
'[SCHEME]://[HOST]/o/token', // URL
['username' => '[EMAIL]', 'password' => '[PASSWORD]'], // Post params.
'', // Request exporter.
'oauth_token_response', // Response exporter.
false, // Multiple.
'post', // Method.
false, // JSON Encoded.
false // Auth required.
];
$mapping[] = [
'issuers', // Action.
'[URL]/issuers', // URL
'[PARAM]', // Post params.
'core_badges\external\issuer_exporter', // Request exporter.
'core_badges\external\issuer_exporter', // Response exporter.
false, // Multiple.
'post', // Method.
true, // JSON Encoded.
true // Auth required.
];
$mapping[] = [
'badgeclasses', // Action.
'[URL]/issuers/[PARAM2]/badgeclasses', // URL
'[PARAM]', // Post params.
'core_badges\external\badgeclass_exporter', // Request exporter.
'core_badges\external\badgeclass_exporter', // Response exporter.
false, // Multiple.
'post', // Method.
true, // JSON Encoded.
true // Auth required.
];
$mapping[] = [
'assertions', // Action.
'[URL]/badgeclasses/[PARAM2]/assertions', // URL
'[PARAM]', // Post params.
'core_badges\external\assertion_exporter', // Request exporter.
'core_badges\external\assertion_exporter', // Response exporter.
false, // Multiple.
'post', // Method.
true, // JSON Encoded.
true // Auth required.
];
$mapping[] = [
'updateassertion', // Action.
'[URL]/assertions/[PARAM2]?expand=badgeclass&expand=issuer',
'[PARAM]', // Post params.
'core_badges\external\assertion_exporter', // Request exporter.
'core_badges\external\assertion_exporter', // Response exporter.
false, // Multiple.
'put', // Method.
true, // JSON Encoded.
true // Auth required.
];
foreach ($mapping as $map) {
$map[] = false; // Site api function.
$map[] = OPEN_BADGES_V2; // V2 function.
$this->mappings[] = new backpack_api_mapping(...$map);
}
}
} else {
if ($this->isuserbackpack) {
$mapping = [];
$mapping[] = [
'user', // Action.
'[URL]/displayer/convert/email', // URL
['email' => '[EMAIL]'], // Post params.
'', // Request exporter.
'convert_email_response', // Response exporter.
false, // Multiple.
'post', // Method.
false, // JSON Encoded.
false // Auth required.
];
$mapping[] = [
'groups', // Action.
'[URL]/displayer/[PARAM1]/groups.json', // URL
[], // Post params.
'', // Request exporter.
'', // Response exporter.
false, // Multiple.
'get', // Method.
true, // JSON Encoded.
true // Auth required.
];
$mapping[] = [
'badges', // Action.
'[URL]/displayer/[PARAM2]/group/[PARAM1].json', // URL
[], // Post params.
'', // Request exporter.
'', // Response exporter.
false, // Multiple.
'get', // Method.
true, // JSON Encoded.
true // Auth required.
];
foreach ($mapping as $map) {
$map[] = true; // User api function.
$map[] = OPEN_BADGES_V1; // V1 function.
$this->mappings[] = new backpack_api_mapping(...$map);
}
} else {
$mapping = [];
$mapping[] = [
'user', // Action.
'[URL]/displayer/convert/email', // URL
['email' => '[EMAIL]'], // Post params.
'', // Request exporter.
'convert_email_response', // Response exporter.
false, // Multiple.
'post', // Method.
false, // JSON Encoded.
false // Auth required.
];
foreach ($mapping as $map) {
$map[] = false; // Site api function.
$map[] = OPEN_BADGES_V1; // V1 function.
$this->mappings[] = new backpack_api_mapping(...$map);
}
}
}
}
/**
* Make an api request
*
* @param string $action The api function.
* @param string $collection An api parameter
* @param string $entityid An api parameter
* @param string $postdata The body of the api request.
* @return mixed
*/
private function curl_request($action, $collection = null, $entityid = null, $postdata = null) {
global $CFG, $SESSION;
$curl = new curl();
$authrequired = false;
if ($this->backpackapiversion == OPEN_BADGES_V1) {
$useridkey = $this->get_token_key(BADGE_USER_ID_TOKEN);
if (isset($SESSION->$useridkey)) {
if ($collection == null) {
$collection = $SESSION->$useridkey;
} else {
$entityid = $SESSION->$useridkey;
}
}
}
foreach ($this->mappings as $mapping) {
if ($mapping->is_match($action)) {
return $mapping->request(
$this->backpackapiurl,
$collection,
$entityid,
$this->email,
$this->password,
$postdata,
$this->backpackid
);
}
}
throw new coding_exception('Unknown request');
}
/**
* Get the id to use for requests with this api.
*
* @return integer
*/
private function get_auth_user_id() {
global $USER;
if ($this->isuserbackpack) {
return $USER->id;
} else {
// The access tokens for the system backpack are shared.
return -1;
}
}
/**
* Get the name of the key to store this access token type.
*
* @param string $type
* @return string
*/
private function get_token_key($type) {
// This should be removed when everything has a mapping.
$prefix = 'badges_';
if ($this->isuserbackpack) {
$prefix .= 'user_backpack_';
} else {
$prefix .= 'site_backpack_';
}
$prefix .= $type . '_token';
return $prefix;
}
/**
* Normalise the return from a missing user request.
*
* @param string $status
* @return mixed
*/
private function check_status($status) {
// V1 ONLY.
switch($status) {
case "missing":
$response = array(
'status' => $status,
'message' => get_string('error:nosuchuser', 'badges')
);
return $response;
}
return false;
}
/**
* Make an api request to get an assertion
*
* @param string $entityid The id of the assertion.
* @return mixed
*/
public function get_assertion($entityid) {
// V2 Only.
if ($this->backpackapiversion == OPEN_BADGES_V1) {
throw new coding_exception('Not supported in this backpack API');
}
return $this->curl_request('assertion', null, $entityid);
}
/**
* Create a badgeclass assertion.
*
* @param string $entityid The id of the badge class.
* @param string $data The structure of the badge class assertion.
* @return mixed
*/
public function put_badgeclass_assertion($entityid, $data) {
// V2 Only.
if ($this->backpackapiversion == OPEN_BADGES_V1) {
throw new coding_exception('Not supported in this backpack API');
}
return $this->curl_request('assertions', null, $entityid, $data);
}
/**
* Update a badgeclass assertion.
*
* @param string $entityid The id of the badge class.
* @param array $data The structure of the badge class assertion.
* @return mixed
*/
public function update_assertion(string $entityid, array $data) {
// V2 Only.
if ($this->backpackapiversion == OPEN_BADGES_V1) {
throw new coding_exception('Not supported in this backpack API');
}
return $this->curl_request('updateassertion', null, $entityid, $data);
}
/**
* Import a badge assertion into a backpack. This is used to handle cross domain backpacks.
*
* @param string $data The structure of the badge class assertion.
* @return mixed
* @throws coding_exception
*/
public function import_badge_assertion(string $data) {
// V2 Only.
if ($this->backpackapiversion == OPEN_BADGES_V1) {
throw new coding_exception('Not supported in this backpack API');
}
return $this->curl_request('importbadge', null, null, $data);
}
/**
* Select collections from a backpack.
*
* @param string $backpackid The id of the backpack
* @param stdClass[] $collections List of collections with collectionid or entityid.
* @return boolean
*/
public function set_backpack_collections($backpackid, $collections) {
global $DB, $USER;
// Delete any previously selected collections.
$sqlparams = array('backpack' => $backpackid);
$select = 'backpackid = :backpack ';
$DB->delete_records_select('badge_external', $select, $sqlparams);
$badgescache = cache::make('core', 'externalbadges');
// Insert selected collections if they are not in database yet.
foreach ($collections as $collection) {
$obj = new stdClass();
$obj->backpackid = $backpackid;
if ($this->backpackapiversion == OPEN_BADGES_V1) {
$obj->collectionid = (int) $collection;
} else {
$obj->entityid = $collection;
$obj->collectionid = -1;
}
if (!$DB->record_exists('badge_external', (array) $obj)) {
$DB->insert_record('badge_external', $obj);
}
}
$badgescache->delete($USER->id);
return true;
}
/**
* Create a badgeclass
*
* @param string $entityid The id of the entity.
* @param string $data The structure of the badge class.
* @return mixed
*/
public function put_badgeclass($entityid, $data) {
// V2 Only.
if ($this->backpackapiversion == OPEN_BADGES_V1) {
throw new coding_exception('Not supported in this backpack API');
}
return $this->curl_request('badgeclasses', null, $entityid, $data);
}
/**
* Create an issuer
*
* @param string $data The structure of the issuer.
* @return mixed
*/
public function put_issuer($data) {
// V2 Only.
if ($this->backpackapiversion == OPEN_BADGES_V1) {
throw new coding_exception('Not supported in this backpack API');
}
return $this->curl_request('issuers', null, null, $data);
}
/**
* Delete any user access tokens in the session so we will attempt to get new ones.
*
* @return void
*/
public function clear_system_user_session() {
global $SESSION;
$useridkey = $this->get_token_key(BADGE_USER_ID_TOKEN);
unset($SESSION->$useridkey);
$expireskey = $this->get_token_key(BADGE_EXPIRES_TOKEN);
unset($SESSION->$expireskey);
}
/**
* Authenticate using the stored email and password and save the valid access tokens.
*
* @return mixed The id of the authenticated user as returned by the backpack. Can have
* different formats - numeric, empty, object with 'error' property, etc.
*/
public function authenticate() {
global $SESSION;
$backpackidkey = $this->get_token_key(BADGE_BACKPACK_ID_TOKEN);
$backpackid = isset($SESSION->$backpackidkey) ? $SESSION->$backpackidkey : 0;
// If the backpack is changed we need to expire sessions.
if ($backpackid == $this->backpackid) {
if ($this->backpackapiversion == OPEN_BADGES_V2) {
$useridkey = $this->get_token_key(BADGE_USER_ID_TOKEN);
$authuserid = isset($SESSION->$useridkey) ? $SESSION->$useridkey : 0;
if ($authuserid == $this->get_auth_user_id()) {
$expireskey = $this->get_token_key(BADGE_EXPIRES_TOKEN);
if (isset($SESSION->$expireskey)) {
$expires = $SESSION->$expireskey;
if ($expires > time()) {
// We have a current access token for this user
// that has not expired.
return -1;
}
}
}
} else {
$useridkey = $this->get_token_key(BADGE_USER_ID_TOKEN);
$authuserid = isset($SESSION->$useridkey) ? $SESSION->$useridkey : 0;
if (!empty($authuserid)) {
return $authuserid;
}
}
}
return $this->curl_request('user', $this->email);
}
/**
* Get all collections in this backpack.
*
* @return stdClass[] The collections.
*/
public function get_collections() {
global $PAGE;
if ($this->authenticate()) {
if ($this->backpackapiversion == OPEN_BADGES_V1) {
$result = $this->curl_request('groups');
if (isset($result->groups)) {
$result = $result->groups;
}
} else {
$result = $this->curl_request('collections');
}
if ($result) {
return $result;
}
}
return [];
}
/**
* Get one collection by id.
*
* @param integer $collectionid
* @return stdClass The collection.
*/
public function get_collection_record($collectionid) {
global $DB;
if ($this->backpackapiversion == OPEN_BADGES_V1) {
return $DB->get_fieldset_select('badge_external', 'collectionid', 'backpackid = :bid', array('bid' => $collectionid));
} else {
return $DB->get_fieldset_select('badge_external', 'entityid', 'backpackid = :bid', array('bid' => $collectionid));
}
}
/**
* Disconnect the backpack from this user.
*
* @param integer $userid The user in Moodle
* @param integer $backpackid The backpack to disconnect
* @return boolean
*/
public function disconnect_backpack($userid, $backpackid) {
global $DB, $USER;
if (\core\session\manager::is_loggedinas() || $userid != $USER->id) {
// Can't change someone elses backpack settings.
return false;
}
$badgescache = cache::make('core', 'externalbadges');
$DB->delete_records('badge_external', array('backpackid' => $backpackid));
$DB->delete_records('badge_backpack', array('userid' => $userid));
$badgescache->delete($userid);
$this->clear_system_user_session();
return true;
}
/**
* Handle the response from getting a collection to map to an id.
*
* @param stdClass $data The response data.
* @return string The collection id.
*/
public function get_collection_id_from_response($data) {
if ($this->backpackapiversion == OPEN_BADGES_V1) {
return $data->groupId;
} else {
return $data->entityId;
}
}
/**
* Get the last error message returned during an authentication request.
*
* @return string
*/
public function get_authentication_error() {
return backpack_api_mapping::get_authentication_error();
}
/**
* Get the list of badges in a collection.
*
* @param stdClass $collection The collection to deal with.
* @param boolean $expanded Fetch all the sub entities.
* @return stdClass[]
*/
public function get_badges($collection, $expanded = false) {
global $PAGE;
if ($this->authenticate()) {
if ($this->backpackapiversion == OPEN_BADGES_V1) {
if (empty($collection->collectionid)) {
return [];
}
$result = $this->curl_request('badges', $collection->collectionid);
return $result->badges;
} else {
if (empty($collection->entityid)) {
return [];
}
// Now we can make requests.
$badges = $this->curl_request('badges', $collection->entityid);
if (count($badges) == 0) {
return [];
}
$badges = $badges[0];
if ($expanded) {
$publicassertions = [];
$context = context_system::instance();
$output = $PAGE->get_renderer('core', 'badges');
foreach ($badges->assertions as $assertion) {
$remoteassertion = $this->get_assertion($assertion);
// Remote badge was fetched nested in the assertion.
$remotebadge = $remoteassertion->badgeclass;
if (!$remotebadge) {
continue;
}
$apidata = badgeclass_exporter::map_external_data($remotebadge, $this->backpackapiversion);
$exporterinstance = new badgeclass_exporter($apidata, ['context' => $context]);
$remotebadge = $exporterinstance->export($output);
$remoteissuer = $remotebadge->issuer;
$apidata = issuer_exporter::map_external_data($remoteissuer, $this->backpackapiversion);
$exporterinstance = new issuer_exporter($apidata, ['context' => $context]);
$remoteissuer = $exporterinstance->export($output);
$badgeclone = clone $remotebadge;
$badgeclone->issuer = $remoteissuer;
$remoteassertion->badge = $badgeclone;
$remotebadge->assertion = $remoteassertion;
$publicassertions[] = $remotebadge;
}
$badges = $publicassertions;
}
return $badges;
}
}
}
}
+306
View File
@@ -0,0 +1,306 @@
<?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/>.
/**
* Communicate with backpacks.
*
* @copyright 2020 Tung Thai based on Totara Learning Solutions Ltd {@link http://www.totaralms.com/} dode
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Tung Thai <Tung.ThaiDuc@nashtechglobal.com>
*/
namespace core_badges;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/filelib.php');
use cache;
use coding_exception;
use context_system;
use moodle_url;
use core_badges\backpack_api2p1_mapping;
use core_badges\oauth2\client;
use curl;
use stdClass;
use core\oauth2\issuer;
use core\oauth2\endpoint;
use core\oauth2\discovery\imsbadgeconnect;
/**
* To process badges with backpack and control api request and this class using for Open Badge API v2.1 methods.
*
* @package core_badges
* @copyright 2020 Tung Thai
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backpack_api2p1 {
/** @var object is the external backpack. */
private $externalbackpack;
/** @var array define api mapping. */
private $mappings = [];
/** @var false|null|stdClass|\core_badges\backpack_api2p1 to */
private $tokendata;
/** @var null clienid. */
private $clientid = null;
/** @var null version api of the backpack. */
protected $backpackapiversion;
/** @var issuer The OAuth2 Issuer for this backpack */
protected issuer $issuer;
/** @var endpoint The apiBase endpoint */
protected endpoint $apibase;
/**
* backpack_api2p1 constructor.
*
* @param object $externalbackpack object
* @throws coding_exception error message
*/
public function __construct($externalbackpack) {
if (!empty($externalbackpack)) {
$this->externalbackpack = $externalbackpack;
$this->backpackapiversion = $externalbackpack->apiversion;
$this->get_clientid($externalbackpack->oauth2_issuerid);
if (!($this->tokendata = $this->get_stored_token($externalbackpack->id))
&& $this->backpackapiversion != OPEN_BADGES_V2P1) {
throw new coding_exception('Backpack incorrect');
}
}
$this->define_mappings();
}
/**
* Initialises or returns the OAuth2 issuer associated to this backpack.
*
* @return issuer
*/
protected function get_issuer(): issuer {
if (!isset($this->issuer)) {
$this->issuer = new \core\oauth2\issuer($this->externalbackpack->oauth2_issuerid);
}
return $this->issuer;
}
/**
* Gets the apiBase url associated to this backpack.
*
* @return string
*/
protected function get_api_base_url(): string {
if (!isset($this->apibase)) {
$apibase = endpoint::get_record([
'issuerid' => $this->externalbackpack->oauth2_issuerid,
'name' => 'apiBase',
]);
if (empty($apibase)) {
imsbadgeconnect::create_endpoints($this->get_issuer());
$apibase = endpoint::get_record([
'issuerid' => $this->externalbackpack->oauth2_issuerid,
'name' => 'apiBase',
]);
}
$this->apibase = $apibase;
}
return $this->apibase->get('url');
}
/**
* Define the mappings supported by this usage and api version.
*/
private function define_mappings() {
if ($this->backpackapiversion == OPEN_BADGES_V2P1) {
$mapping = [];
$mapping[] = [
'post.assertions', // Action.
'[URL]/assertions', // URL
'[PARAM]', // Post params.
false, // Multiple.
'post', // Method.
true, // JSON Encoded.
true // Auth required.
];
$mapping[] = [
'get.assertions', // Action.
'[URL]/assertions', // URL
'[PARAM]', // Post params.
false, // Multiple.
'get', // Method.
true, // JSON Encoded.
true // Auth required.
];
foreach ($mapping as $map) {
$map[] = false; // Site api function.
$map[] = OPEN_BADGES_V2P1; // V2 function.
$this->mappings[] = new backpack_api2p1_mapping(...$map);
}
}
}
/**
* Disconnect the backpack from this user.
*
* @param object $backpack to disconnect.
* @return bool
* @throws \dml_exception
*/
public function disconnect_backpack($backpack) {
global $USER, $DB;
if ($backpack) {
$DB->delete_records_select('badge_external', 'backpackid = :backpack', ['backpack' => $backpack->id]);
$DB->delete_records('badge_backpack', ['id' => $backpack->id]);
$DB->delete_records('badge_backpack_oauth2', ['externalbackpackid' => $this->externalbackpack->id,
'userid' => $USER->id]);
return true;
}
return false;
}
/**
* Make an api request.
*
* @param string $action The api function.
* @param string $postdata The body of the api request.
* @return mixed
*/
public function curl_request($action, $postdata = null) {
$tokenkey = $this->tokendata->token;
foreach ($this->mappings as $mapping) {
if ($mapping->is_match($action)) {
return $mapping->request(
$this->get_api_base_url(),
$tokenkey,
$postdata
);
}
}
throw new coding_exception('Unknown request');
}
/**
* Get token.
*
* @param int $externalbackpackid ID of external backpack.
* @return oauth2\badge_backpack_oauth2|false|stdClass|null
*/
protected function get_stored_token($externalbackpackid) {
global $USER;
$token = \core_badges\oauth2\badge_backpack_oauth2::get_record(
['externalbackpackid' => $externalbackpackid, 'userid' => $USER->id]);
if ($token !== false) {
$token = $token->to_record();
return $token;
}
return null;
}
/**
* Get client id.
*
* @param int $issuerid id of Oauth2 service.
* @throws coding_exception
*/
private function get_clientid($issuerid) {
$issuer = \core\oauth2\api::get_issuer($issuerid);
if (!empty($issuer)) {
$this->issuer = $issuer;
$this->clientid = $issuer->get('clientid');
}
}
/**
* Export a badge to the backpack site.
*
* @param string $hash of badge issued.
* @return array
* @throws \moodle_exception
* @throws coding_exception
*/
public function put_assertions($hash) {
$data = [];
if (!$hash) {
return false;
}
$issuer = $this->get_issuer();
$client = new client($issuer, new moodle_url('/badges/mybadges.php'), '', $this->externalbackpack);
if (!$client->is_logged_in()) {
$redirecturl = new moodle_url('/badges/mybadges.php', ['error' => 'backpackexporterror']);
redirect($redirecturl);
}
$this->tokendata = $this->get_stored_token($this->externalbackpack->id);
$assertion = new \core_badges_assertion($hash, OPEN_BADGES_V2);
$data['assertion'] = $assertion->get_badge_assertion();
$response = $this->curl_request('post.assertions', $data);
if ($response && isset($response->status->statusCode) && $response->status->statusCode == 200) {
$msg['status'] = \core\output\notification::NOTIFY_SUCCESS;
$msg['message'] = get_string('addedtobackpack', 'badges');
} else {
if ($response) {
// Although the specification defines that status error is a string, some providers, like Badgr, are wrongly
// returning an array. It has been reported, but adding these extra checks doesn't hurt, just in case.
if (
property_exists($response, 'status') &&
is_object($response->status) &&
property_exists($response->status, 'error')
) {
$statuserror = $response->status->error;
if (is_array($statuserror)) {
$statuserror = implode($statuserror);
}
} else if (property_exists($response, 'error')) {
$statuserror = $response->error;
if (property_exists($response, 'message')) {
$statuserror .= '. Message: ' . $response->message;
}
}
} else {
$statuserror = 'Empty response';
}
$data = [
'badgename' => $data['assertion']['badge']['name'],
'error' => $statuserror,
];
$msg['status'] = \core\output\notification::NOTIFY_ERROR;
$msg['message'] = get_string('backpackexporterrorwithinfo', 'badges', $data);
}
return $msg;
}
}
+184
View File
@@ -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/>.
/**
* Represent the url for each method and the encoding of the parameters and response.
*
* The code is based on badges/classes/backpack_api_mapping.php by Yuliya Bozhko <yuliya.bozhko@totaralms.com>.
*
* @copyright 2020 Tung Thai
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Tung Thai <Tung.ThaiDuc@nashtechglobal.com>
*/
namespace core_badges;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir . '/filelib.php');
use context_system;
use curl;
/**
* Represent a single method for the remote api and this class using for Open Badge API v2.1 methods.
*
* @package core_badges
* @copyright 2020 Tung Thai
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backpack_api2p1_mapping {
/** @var string The action of this method. */
public $action;
/** @var string The base url of this backpack. */
private $url;
/** @var array List of parameters for this method. */
public $params;
/** @var boolean This method returns an array of responses. */
public $multiple;
/** @var string get or post methods. */
public $method;
/** @var boolean json decode the response. */
public $json;
/** @var boolean Authentication is required for this request. */
public $authrequired;
/** @var boolean Differentiate the function that can be called on a user backpack or a site backpack. */
private $isuserbackpack;
/** @var mixed List of parameters for this method. */
protected $postparams;
/** @var int OpenBadges version 1 or 2. */
protected $backpackapiversion;
/**
* Create a mapping.
*
* @param string $action The action of this method.
* @param string $url The base url of this backpack.
* @param mixed $postparams List of parameters for this method.
* @param boolean $multiple This method returns an array of responses.
* @param string $method get or post methods.
* @param boolean $json json decode the response.
* @param boolean $authrequired Authentication is required for this request.
* @param boolean $isuserbackpack user backpack or a site backpack.
* @param integer $backpackapiversion OpenBadges version 1 or 2.
*/
public function __construct($action, $url, $postparams,
$multiple, $method, $json, $authrequired, $isuserbackpack, $backpackapiversion) {
$this->action = $action;
$this->url = $url;
$this->postparams = $postparams;
$this->multiple = $multiple;
$this->method = $method;
$this->json = $json;
$this->authrequired = $authrequired;
$this->isuserbackpack = $isuserbackpack;
$this->backpackapiversion = $backpackapiversion;
}
/**
* Does the action match this mapping?
*
* @param string $action The action.
* @return boolean
*/
public function is_match($action) {
return $this->action == $action;
}
/**
* Parse the method url and insert parameters.
*
* @param string $apiurl The raw apiurl.
* @return string
*/
private function get_url($apiurl) {
$urlscheme = parse_url($apiurl, PHP_URL_SCHEME);
$urlhost = parse_url($apiurl, PHP_URL_HOST);
$url = $this->url;
$url = str_replace('[SCHEME]', $urlscheme, $url);
$url = str_replace('[HOST]', $urlhost, $url);
$url = str_replace('[URL]', $apiurl, $url);
return $url;
}
/**
* Standard options used for all curl requests.
*
* @return array
*/
private function get_curl_options() {
return array(
'FRESH_CONNECT' => true,
'RETURNTRANSFER' => true,
'FORBID_REUSE' => true,
'HEADER' => 0,
'CONNECTTIMEOUT' => 3,
'CONNECTTIMEOUT' => 3,
// Follow redirects with the same type of request when sent 301, or 302 redirects.
'CURLOPT_POSTREDIR' => 3,
);
}
/**
* Make an api request and parse the response.
*
* @param string $apiurl Raw request url.
* @param string $tokenkey to verify authorization.
* @param array $post request method.
* @return bool|mixed
*/
public function request($apiurl, $tokenkey, $post = []) {
$curl = new curl();
$url = $this->get_url($apiurl);
if ($tokenkey) {
$curl->setHeader('Authorization: Bearer ' . $tokenkey);
}
if ($this->json) {
$curl->setHeader(array('Content-type: application/json'));
if ($this->method == 'post') {
$post = json_encode($post);
}
}
$curl->setHeader(array('Accept: application/json', 'Expect:'));
$options = $this->get_curl_options();
if ($this->method == 'get') {
$response = $curl->get($url, $post, $options);
} else if ($this->method == 'post') {
$response = $curl->post($url, $post, $options);
}
$response = json_decode($response);
if (isset($response->result)) {
$response = $response->result;
}
return $response;
}
}
+410
View File
@@ -0,0 +1,410 @@
<?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/>.
/**
* Represent the url for each method and the encoding of the parameters and response.
*
* @package core_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>
*/
namespace core_badges;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir . '/filelib.php');
use context_system;
use core_badges\external\assertion_exporter;
use core_badges\external\collection_exporter;
use core_badges\external\issuer_exporter;
use core_badges\external\badgeclass_exporter;
use curl;
/**
* Represent a single method for the remote api.
*
* @package core_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
*/
class backpack_api_mapping {
/** @var string The action of this method. */
public $action;
/** @var string The base url of this backpack. */
private $url;
/** @var array List of parameters for this method. */
public $params;
/** @var string Name of a class to export parameters for this method. */
public $requestexporter;
/** @var string Name of a class to export response for this method. */
public $responseexporter;
/** @var boolean This method returns an array of responses. */
public $multiple;
/** @var string get or post methods. */
public $method;
/** @var boolean json decode the response. */
public $json;
/** @var boolean Authentication is required for this request. */
public $authrequired;
/** @var boolean Differentiate the function that can be called on a user backpack or a site backpack. */
private $isuserbackpack;
/** @var string Error string from authentication request. */
private static $authenticationerror = '';
/** @var mixed List of parameters for this method. */
protected $postparams;
/** @var int OpenBadges version 1 or 2. */
protected $backpackapiversion;
/**
* Create a mapping.
*
* @param string $action The action of this method.
* @param string $url The base url of this backpack.
* @param mixed $postparams List of parameters for this method.
* @param string $requestexporter Name of a class to export parameters for this method.
* @param string $responseexporter Name of a class to export response for this method.
* @param boolean $multiple This method returns an array of responses.
* @param string $method get or post methods.
* @param boolean $json json decode the response.
* @param boolean $authrequired Authentication is required for this request.
* @param boolean $isuserbackpack user backpack or a site backpack.
* @param integer $backpackapiversion OpenBadges version 1 or 2.
*/
public function __construct($action, $url, $postparams, $requestexporter, $responseexporter,
$multiple, $method, $json, $authrequired, $isuserbackpack, $backpackapiversion) {
$this->action = $action;
$this->url = $url;
$this->postparams = $postparams;
$this->requestexporter = $requestexporter;
$this->responseexporter = $responseexporter;
$this->multiple = $multiple;
$this->method = $method;
$this->json = $json;
$this->authrequired = $authrequired;
$this->isuserbackpack = $isuserbackpack;
$this->backpackapiversion = $backpackapiversion;
}
/**
* Get the unique key for the token.
*
* @param string $type The type of token.
* @return string
*/
private function get_token_key($type) {
$prefix = 'badges_';
if ($this->isuserbackpack) {
$prefix .= 'user_backpack_';
} else {
$prefix .= 'site_backpack_';
}
$prefix .= $type . '_token';
return $prefix;
}
/**
* Remember the error message in a static variable.
*
* @param string $msg The message.
*/
public static function set_authentication_error($msg) {
self::$authenticationerror = $msg;
}
/**
* Get the last authentication error in this request.
*
* @return string
*/
public static function get_authentication_error() {
return self::$authenticationerror;
}
/**
* Does the action match this mapping?
*
* @param string $action The action.
* @return boolean
*/
public function is_match($action) {
return $this->action == $action;
}
/**
* Parse the method url and insert parameters.
*
* @param string $apiurl The raw apiurl.
* @param string $param1 The first parameter.
* @param string $param2 The second parameter.
* @return string
*/
private function get_url($apiurl, $param1, $param2) {
$urlscheme = parse_url($apiurl, PHP_URL_SCHEME);
$urlhost = parse_url($apiurl, PHP_URL_HOST);
$url = $this->url;
$url = str_replace('[SCHEME]', $urlscheme, $url);
$url = str_replace('[HOST]', $urlhost, $url);
$url = str_replace('[URL]', $apiurl, $url);
$url = str_replace('[PARAM1]', $param1 ?? '', $url);
$url = str_replace('[PARAM2]', $param2 ?? '', $url);
return $url;
}
/**
* Parse the post parameters and insert replacements.
*
* @param string $email The api username.
* @param string $password The api password.
* @param string $param The parameter.
* @return mixed
*/
private function get_post_params($email, $password, $param) {
global $PAGE;
if ($this->method == 'get') {
return '';
}
$request = $this->postparams;
if ($request === '[PARAM]') {
$value = $param;
foreach ($value as $key => $keyvalue) {
if (gettype($value[$key]) == 'array') {
$newkey = 'related_' . $key;
$value[$newkey] = $value[$key];
unset($value[$key]);
}
}
} else if (is_array($request)) {
foreach ($request as $key => $value) {
if ($value == '[EMAIL]') {
$value = $email;
$request[$key] = $value;
} else if ($value == '[PASSWORD]') {
$value = $password;
$request[$key] = $value;
} else if ($value == '[PARAM]') {
$request[$key] = is_array($param) ? $param[0] : $param;
}
}
}
$context = context_system::instance();
$exporter = $this->requestexporter;
$output = $PAGE->get_renderer('core', 'badges');
if (!empty($exporter)) {
$exporterinstance = new $exporter($value, ['context' => $context]);
$request = $exporterinstance->export($output);
}
if ($this->json) {
return json_encode($request);
}
return $request;
}
/**
* Read the response from a V1 user request and save the userID.
*
* @param string $response The request response.
* @param integer $backpackid The backpack id.
* @return mixed
*/
private function convert_email_response($response, $backpackid) {
global $SESSION;
if (isset($response->status) && $response->status == 'okay') {
// Remember the tokens.
$useridkey = $this->get_token_key(BADGE_USER_ID_TOKEN);
$backpackidkey = $this->get_token_key(BADGE_BACKPACK_ID_TOKEN);
$SESSION->$useridkey = $response->userId;
$SESSION->$backpackidkey = $backpackid;
return $response->userId;
}
if (!empty($response->error)) {
self::set_authentication_error($response->error);
}
return false;
}
/**
* Get the user id from a previous user request.
*
* @return integer
*/
private function get_auth_user_id() {
global $USER;
if ($this->isuserbackpack) {
return $USER->id;
} else {
// The access tokens for the system backpack are shared.
return -1;
}
}
/**
* Parse the response from an openbadges 2 login.
*
* @param string $response The request response data.
* @param integer $backpackid The id of the backpack.
* @return mixed
*/
private function oauth_token_response($response, $backpackid) {
global $SESSION;
if (isset($response->access_token) && isset($response->refresh_token)) {
// Remember the tokens.
$accesskey = $this->get_token_key(BADGE_ACCESS_TOKEN);
$refreshkey = $this->get_token_key(BADGE_REFRESH_TOKEN);
$expireskey = $this->get_token_key(BADGE_EXPIRES_TOKEN);
$useridkey = $this->get_token_key(BADGE_USER_ID_TOKEN);
$backpackidkey = $this->get_token_key(BADGE_BACKPACK_ID_TOKEN);
if (isset($response->expires_in)) {
$timeout = $response->expires_in;
} else {
$timeout = 15 * 60; // 15 minute timeout if none set.
}
$expires = $timeout + time();
$SESSION->$expireskey = $expires;
$SESSION->$useridkey = $this->get_auth_user_id();
$SESSION->$accesskey = $response->access_token;
$SESSION->$refreshkey = $response->refresh_token;
$SESSION->$backpackidkey = $backpackid;
return -1;
} else if (isset($response->error_description)) {
self::set_authentication_error($response->error_description);
}
return $response;
}
/**
* Standard options used for all curl requests.
*
* @return array
*/
private function get_curl_options() {
return array(
'FRESH_CONNECT' => true,
'RETURNTRANSFER' => true,
'FOLLOWLOCATION' => true,
'FORBID_REUSE' => true,
'HEADER' => 0,
'CONNECTTIMEOUT' => 3,
'CONNECTTIMEOUT' => 3,
// Follow redirects with the same type of request when sent 301, or 302 redirects.
'CURLOPT_POSTREDIR' => 3,
);
}
/**
* Make an api request and parse the response.
*
* @param string $apiurl Raw request url.
* @param string $urlparam1 Parameter for the request.
* @param string $urlparam2 Parameter for the request.
* @param string $email User email for authentication.
* @param string $password for authentication.
* @param mixed $postparam Raw data for the post body.
* @param string $backpackid the id of the backpack to use.
* @return mixed
*/
public function request($apiurl, $urlparam1, $urlparam2, $email, $password, $postparam, $backpackid) {
global $SESSION, $PAGE;
$curl = new curl();
$url = $this->get_url($apiurl, $urlparam1, $urlparam2);
if ($this->authrequired) {
$accesskey = $this->get_token_key(BADGE_ACCESS_TOKEN);
if (isset($SESSION->$accesskey)) {
$token = $SESSION->$accesskey;
$curl->setHeader('Authorization: Bearer ' . $token);
}
}
if ($this->json) {
$curl->setHeader(array('Content-type: application/json'));
}
$curl->setHeader(array('Accept: application/json', 'Expect:'));
$options = $this->get_curl_options();
$post = $this->get_post_params($email, $password, $postparam);
if ($this->method == 'get') {
$response = $curl->get($url, $post, $options);
} else if ($this->method == 'post') {
$response = $curl->post($url, $post, $options);
} else if ($this->method == 'put') {
$response = $curl->put($url, $post, $options);
}
$response = json_decode($response);
if (isset($response->result)) {
$response = $response->result;
}
$context = context_system::instance();
$exporter = $this->responseexporter;
if (class_exists($exporter)) {
$output = $PAGE->get_renderer('core', 'badges');
if (!$this->multiple) {
if (count($response)) {
$response = $response[0];
}
if (empty($response)) {
return null;
}
$apidata = $exporter::map_external_data($response, $this->backpackapiversion);
$exporterinstance = new $exporter($apidata, ['context' => $context]);
$data = $exporterinstance->export($output);
return $data;
} else {
$multiple = [];
if (empty($response)) {
return $multiple;
}
foreach ($response as $data) {
$apidata = $exporter::map_external_data($data, $this->backpackapiversion);
$exporterinstance = new $exporter($apidata, ['context' => $context]);
$multiple[] = $exporterinstance->export($output);
}
return $multiple;
}
} else if (method_exists($this, $exporter)) {
return $this->$exporter($response, $backpackid);
}
return $response;
}
}
+994
View File
@@ -0,0 +1,994 @@
<?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 assertion 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>
*/
namespace core_badges;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/lib/badgeslib.php');
use context_system;
use context_course;
use context_user;
use moodle_exception;
use moodle_url;
use core_text;
use award_criteria;
use core_php_time_limit;
use html_writer;
use stdClass;
/**
* Class that represents badge.
*
* @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
*/
class badge {
/** @var int Badge id */
public $id;
/** @var string Badge name */
public $name;
/** @var string Badge description */
public $description;
/** @var integer Timestamp this badge was created */
public $timecreated;
/** @var integer Timestamp this badge was modified */
public $timemodified;
/** @var int The user who created this badge */
public $usercreated;
/** @var int The user who modified this badge */
public $usermodified;
/** @var string The name of the issuer of this badge */
public $issuername;
/** @var string The url of the issuer of this badge */
public $issuerurl;
/** @var string The email of the issuer of this badge */
public $issuercontact;
/** @var integer Timestamp this badge will expire */
public $expiredate;
/** @var integer Duration this badge is valid for */
public $expireperiod;
/** @var integer Site or course badge */
public $type;
/** @var integer The course this badge belongs to */
public $courseid;
/** @var string The message this badge includes. */
public $message;
/** @var string The subject of the message for this badge */
public $messagesubject;
/** @var int Is this badge image baked. */
public $attachment;
/** @var int Send a message when this badge is awarded. */
public $notification;
/** @var int Lifecycle status for this badge. */
public $status = 0;
/** @var int Timestamp to next run cron for this badge. */
public $nextcron;
/** @var int What backpack api version to use for this badge. */
public $version;
/** @var string What language is this badge written in. */
public $language;
/** @var string The author of the image for this badge. */
public $imageauthorname;
/** @var string The email of the author of the image for this badge. */
public $imageauthoremail;
/** @var string The url of the author of the image for this badge. */
public $imageauthorurl;
/** @var string The caption of the image for this badge. */
public $imagecaption;
/** @var array Badge criteria */
public $criteria = array();
/** @var int|null Total users which have the award. Called from badges_get_badges() */
public $awards;
/** @var string|null The name of badge status. Called from badges_get_badges() */
public $statstring;
/** @var int|null The date the badges were issued. Called from badges_get_badges() */
public $dateissued;
/** @var string|null Unique hash. Called from badges_get_badges() */
public $uniquehash;
/** @var string|null Message format. Called from file_prepare_standard_editor() */
public $messageformat;
/** @var array Message editor. Called from file_prepare_standard_editor() */
public $message_editor = [];
/**
* Constructs with badge details.
*
* @param int $badgeid badge ID.
*/
public function __construct($badgeid) {
global $DB;
$this->id = $badgeid;
$data = $DB->get_record('badge', array('id' => $badgeid));
if (empty($data)) {
throw new moodle_exception('error:nosuchbadge', 'badges', '', $badgeid);
}
foreach ((array)$data as $field => $value) {
if (property_exists($this, $field)) {
$this->{$field} = $value;
}
}
if (badges_open_badges_backpack_api() != OPEN_BADGES_V1) {
// For Open Badges 2 we need to use a single site issuer with no exceptions.
$issuer = badges_get_default_issuer();
$this->issuername = $issuer['name'];
$this->issuercontact = $issuer['email'];
$this->issuerurl = $issuer['url'];
}
$this->criteria = self::get_criteria();
}
/**
* Use to get context instance of a badge.
*
* @return \context|void instance.
*/
public function get_context() {
if ($this->type == BADGE_TYPE_SITE) {
return context_system::instance();
} else if ($this->type == BADGE_TYPE_COURSE) {
return context_course::instance($this->courseid);
} else {
debugging('Something is wrong...');
}
}
/**
* Return array of aggregation methods
*
* @return array
*/
public static function get_aggregation_methods() {
return array(
BADGE_CRITERIA_AGGREGATION_ALL => get_string('all', 'badges'),
BADGE_CRITERIA_AGGREGATION_ANY => get_string('any', 'badges'),
);
}
/**
* Return array of accepted criteria types for this badge
*
* @return array
*/
public function get_accepted_criteria() {
global $CFG;
$criteriatypes = array();
if ($this->type == BADGE_TYPE_COURSE) {
$criteriatypes = array(
BADGE_CRITERIA_TYPE_OVERALL,
BADGE_CRITERIA_TYPE_MANUAL,
BADGE_CRITERIA_TYPE_COURSE,
BADGE_CRITERIA_TYPE_BADGE,
BADGE_CRITERIA_TYPE_ACTIVITY,
BADGE_CRITERIA_TYPE_COMPETENCY
);
} else if ($this->type == BADGE_TYPE_SITE) {
$criteriatypes = array(
BADGE_CRITERIA_TYPE_OVERALL,
BADGE_CRITERIA_TYPE_MANUAL,
BADGE_CRITERIA_TYPE_COURSESET,
BADGE_CRITERIA_TYPE_BADGE,
BADGE_CRITERIA_TYPE_PROFILE,
BADGE_CRITERIA_TYPE_COHORT,
BADGE_CRITERIA_TYPE_COMPETENCY
);
}
$alltypes = badges_list_criteria();
foreach ($criteriatypes as $index => $type) {
if (!isset($alltypes[$type])) {
unset($criteriatypes[$index]);
}
}
return $criteriatypes;
}
/**
* Save/update badge information in 'badge' table only.
* Cannot be used for updating awards and criteria settings.
*
* @return boolean Returns true on success.
*/
public function save() {
global $DB;
$fordb = new stdClass();
foreach (get_object_vars($this) as $k => $v) {
$fordb->{$k} = $v;
}
// TODO: We need to making it more simple.
// Since the variables are not exist in the badge table,
// unsetting them is a must to avoid errors.
unset($fordb->criteria);
unset($fordb->awards);
unset($fordb->statstring);
unset($fordb->dateissued);
unset($fordb->uniquehash);
unset($fordb->messageformat);
unset($fordb->message_editor);
$fordb->timemodified = time();
if ($DB->update_record_raw('badge', $fordb)) {
// Trigger event, badge updated.
$eventparams = array('objectid' => $this->id, 'context' => $this->get_context());
$event = \core\event\badge_updated::create($eventparams);
$event->trigger();
return true;
} else {
throw new moodle_exception('error:save', 'badges');
return false;
}
}
/**
* Creates and saves a clone of badge with all its properties.
* Clone is not active by default and has 'Copy of' attached to its name.
*
* @return int ID of new badge.
*/
public function make_clone() {
global $DB, $USER, $PAGE;
$fordb = new stdClass();
foreach (get_object_vars($this) as $k => $v) {
$fordb->{$k} = $v;
}
$fordb->name = get_string('copyof', 'badges', $this->name);
$fordb->status = BADGE_STATUS_INACTIVE;
$fordb->usercreated = $USER->id;
$fordb->usermodified = $USER->id;
$fordb->timecreated = time();
$fordb->timemodified = time();
$tags = $this->get_badge_tags();
unset($fordb->id);
if ($fordb->notification > 1) {
$fordb->nextcron = badges_calculate_message_schedule($fordb->notification);
}
$criteria = $fordb->criteria;
unset($fordb->criteria);
if ($new = $DB->insert_record('badge', $fordb, true)) {
$newbadge = new badge($new);
// Copy badge tags.
\core_tag_tag::set_item_tags('core_badges', 'badge', $newbadge->id, $this->get_context(), $tags);
// Copy badge image.
$fs = get_file_storage();
if ($file = $fs->get_file($this->get_context()->id, 'badges', 'badgeimage', $this->id, '/', 'f3.png')) {
if ($imagefile = $file->copy_content_to_temp()) {
badges_process_badge_image($newbadge, $imagefile);
}
}
// Copy badge criteria.
foreach ($this->criteria as $crit) {
$crit->make_clone($new);
}
// Trigger event, badge duplicated.
$eventparams = array('objectid' => $new, 'context' => $PAGE->context);
$event = \core\event\badge_duplicated::create($eventparams);
$event->trigger();
return $new;
} else {
throw new moodle_exception('error:clone', 'badges');
return false;
}
}
/**
* Checks if badges is active.
* Used in badge award.
*
* @return boolean A status indicating badge is active
*/
public function is_active() {
if (($this->status == BADGE_STATUS_ACTIVE) ||
($this->status == BADGE_STATUS_ACTIVE_LOCKED)) {
return true;
}
return false;
}
/**
* Use to get the name of badge status.
*
* @return string
*/
public function get_status_name() {
return get_string('badgestatus_' . $this->status, 'badges');
}
/**
* Use to set badge status.
* Only active badges can be earned/awarded/issued.
*
* @param int $status Status from BADGE_STATUS constants
*/
public function set_status($status = 0) {
$this->status = $status;
$this->save();
if ($status == BADGE_STATUS_ACTIVE) {
// Trigger event, badge enabled.
$eventparams = array('objectid' => $this->id, 'context' => $this->get_context());
$event = \core\event\badge_enabled::create($eventparams);
$event->trigger();
} else if ($status == BADGE_STATUS_INACTIVE) {
// Trigger event, badge disabled.
$eventparams = array('objectid' => $this->id, 'context' => $this->get_context());
$event = \core\event\badge_disabled::create($eventparams);
$event->trigger();
}
}
/**
* Checks if badges is locked.
* Used in badge award and editing.
*
* @return boolean A status indicating badge is locked
*/
public function is_locked() {
if (($this->status == BADGE_STATUS_ACTIVE_LOCKED) ||
($this->status == BADGE_STATUS_INACTIVE_LOCKED)) {
return true;
}
return false;
}
/**
* Checks if badge has been awarded to users.
* Used in badge editing.
*
* @return boolean A status indicating badge has been awarded at least once
*/
public function has_awards() {
global $DB;
$awarded = $DB->record_exists_sql('SELECT b.uniquehash
FROM {badge_issued} b INNER JOIN {user} u ON b.userid = u.id
WHERE b.badgeid = :badgeid AND u.deleted = 0', array('badgeid' => $this->id));
return $awarded;
}
/**
* Gets list of users who have earned an instance of this badge.
*
* @return array An array of objects with information about badge awards.
*/
public function get_awards() {
global $DB;
$awards = $DB->get_records_sql(
'SELECT b.userid, b.dateissued, b.uniquehash, u.firstname, u.lastname
FROM {badge_issued} b INNER JOIN {user} u
ON b.userid = u.id
WHERE b.badgeid = :badgeid AND u.deleted = 0', array('badgeid' => $this->id));
return $awards;
}
/**
* Indicates whether badge has already been issued to a user.
*
* @param int $userid User to check
* @return boolean
*/
public function is_issued($userid) {
global $DB;
return $DB->record_exists('badge_issued', array('badgeid' => $this->id, 'userid' => $userid));
}
/**
* Issue a badge to user.
*
* @param int $userid User who earned the badge
* @param boolean $nobake Not baking actual badges (for testing purposes)
*/
public function issue($userid, $nobake = false) {
global $DB, $CFG;
$now = time();
$issued = new stdClass();
$issued->badgeid = $this->id;
$issued->userid = $userid;
$issued->uniquehash = sha1(rand() . $userid . $this->id . $now);
$issued->dateissued = $now;
if ($this->can_expire()) {
$issued->dateexpire = $this->calculate_expiry($now);
} else {
$issued->dateexpire = null;
}
// Take into account user badges privacy settings.
// If none set, badges default visibility is set to public.
$issued->visible = get_user_preferences('badgeprivacysetting', 1, $userid);
$result = $DB->insert_record('badge_issued', $issued, true);
if ($result) {
// Trigger badge awarded event.
$eventdata = array (
'context' => $this->get_context(),
'objectid' => $this->id,
'relateduserid' => $userid,
'other' => array('dateexpire' => $issued->dateexpire, 'badgeissuedid' => $result)
);
\core\event\badge_awarded::create($eventdata)->trigger();
// Lock the badge, so that its criteria could not be changed any more.
if ($this->status == BADGE_STATUS_ACTIVE) {
$this->set_status(BADGE_STATUS_ACTIVE_LOCKED);
}
// Update details in criteria_met table.
$compl = $this->get_criteria_completions($userid);
foreach ($compl as $c) {
$obj = new stdClass();
$obj->id = $c->id;
$obj->issuedid = $result;
$DB->update_record('badge_criteria_met', $obj, true);
}
if (!$nobake) {
// Bake a badge image.
$pathhash = badges_bake($issued->uniquehash, $this->id, $userid, true);
// Notify recipients and badge creators.
badges_notify_badge_award($this, $userid, $issued->uniquehash, $pathhash);
}
}
}
/**
* Reviews all badge criteria and checks if badge can be instantly awarded.
*
* @return int Number of awards
*/
public function review_all_criteria() {
global $DB, $CFG;
$awards = 0;
// Raise timelimit as this could take a while for big web sites.
core_php_time_limit::raise();
raise_memory_limit(MEMORY_HUGE);
foreach ($this->criteria as $crit) {
// Overall criterion is decided when other criteria are reviewed.
if ($crit->criteriatype == BADGE_CRITERIA_TYPE_OVERALL) {
continue;
}
list($extrajoin, $extrawhere, $extraparams) = $crit->get_completed_criteria_sql();
// For site level badges, get all active site users who can earn this badge and haven't got it yet.
if ($this->type == BADGE_TYPE_SITE) {
$sql = "SELECT DISTINCT u.id, bi.badgeid
FROM {user} u
{$extrajoin}
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 " . $extrawhere;
$params = array_merge(array('badgeid' => $this->id, 'guestid' => $CFG->siteguest), $extraparams);
$toearn = $DB->get_fieldset_sql($sql, $params);
} else {
// For course level badges, get all users who already earned the badge in this course.
// Then find the ones who are enrolled in the course and don't have a badge yet.
$earned = $DB->get_fieldset_select(
'badge_issued',
'userid AS id',
'badgeid = :badgeid',
array('badgeid' => $this->id)
);
$wheresql = '';
$earnedparams = array();
if (!empty($earned)) {
list($earnedsql, $earnedparams) = $DB->get_in_or_equal($earned, SQL_PARAMS_NAMED, 'u', false);
$wheresql = ' WHERE u.id ' . $earnedsql;
}
list($enrolledsql, $enrolledparams) = get_enrolled_sql($this->get_context(), 'moodle/badges:earnbadge', 0, true);
$sql = "SELECT DISTINCT u.id
FROM {user} u
{$extrajoin}
JOIN ({$enrolledsql}) je ON je.id = u.id " . $wheresql . $extrawhere;
$params = array_merge($enrolledparams, $earnedparams, $extraparams);
$toearn = $DB->get_fieldset_sql($sql, $params);
}
foreach ($toearn as $uid) {
$reviewoverall = false;
if ($crit->review($uid, true)) {
$crit->mark_complete($uid);
if ($this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->method == BADGE_CRITERIA_AGGREGATION_ANY) {
$this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($uid);
$this->issue($uid);
$awards++;
} else {
$reviewoverall = true;
}
} else {
// Will be reviewed some other time.
$reviewoverall = false;
}
// Review overall if it is required.
if ($reviewoverall && $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($uid)) {
$this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($uid);
$this->issue($uid);
$awards++;
}
}
}
return $awards;
}
/**
* Gets an array of completed criteria from 'badge_criteria_met' table.
*
* @param int $userid Completions for a user
* @return array Records of criteria completions
*/
public function get_criteria_completions($userid) {
global $DB;
$completions = array();
$sql = "SELECT bcm.id, bcm.critid
FROM {badge_criteria_met} bcm
INNER JOIN {badge_criteria} bc ON bcm.critid = bc.id
WHERE bc.badgeid = :badgeid AND bcm.userid = :userid ";
$completions = $DB->get_records_sql($sql, array('badgeid' => $this->id, 'userid' => $userid));
return $completions;
}
/**
* Checks if badges has award criteria set up.
*
* @return boolean A status indicating badge has at least one criterion
*/
public function has_criteria() {
if (count($this->criteria) > 0) {
return true;
}
return false;
}
/**
* Returns badge award criteria
*
* @return array An array of badge criteria
*/
public function get_criteria() {
global $DB;
$criteria = array();
if ($records = (array)$DB->get_records('badge_criteria', array('badgeid' => $this->id))) {
foreach ($records as $record) {
$criteria[$record->criteriatype] = award_criteria::build((array)$record);
}
}
return $criteria;
}
/**
* Get aggregation method for badge criteria
*
* @param int $criteriatype If none supplied, get overall aggregation method (optional)
* @return int One of BADGE_CRITERIA_AGGREGATION_ALL or BADGE_CRITERIA_AGGREGATION_ANY
*/
public function get_aggregation_method($criteriatype = 0) {
global $DB;
$params = array('badgeid' => $this->id, 'criteriatype' => $criteriatype);
$aggregation = $DB->get_field('badge_criteria', 'method', $params, IGNORE_MULTIPLE);
if (!$aggregation) {
return BADGE_CRITERIA_AGGREGATION_ALL;
}
return $aggregation;
}
/**
* Checks if badge has expiry period or date set up.
*
* @return boolean A status indicating badge can expire
*/
public function can_expire() {
if ($this->expireperiod || $this->expiredate) {
return true;
}
return false;
}
/**
* Calculates badge expiry date based on either expirydate or expiryperiod.
*
* @param int $timestamp Time of badge issue
* @return int A timestamp
*/
public function calculate_expiry($timestamp) {
$expiry = null;
if (isset($this->expiredate)) {
$expiry = $this->expiredate;
} else if (isset($this->expireperiod)) {
$expiry = $timestamp + $this->expireperiod;
}
return $expiry;
}
/**
* Checks if badge has manual award criteria set.
*
* @return boolean A status indicating badge can be awarded manually
*/
public function has_manual_award_criteria() {
foreach ($this->criteria as $criterion) {
if ($criterion->criteriatype == BADGE_CRITERIA_TYPE_MANUAL) {
return true;
}
}
return false;
}
/**
* Fully deletes the badge or marks it as archived.
*
* @param boolean $archive Achive a badge without actual deleting of any data.
*/
public function delete($archive = true) {
global $DB;
if ($archive) {
$this->status = BADGE_STATUS_ARCHIVED;
$this->save();
// Trigger event, badge archived.
$eventparams = array('objectid' => $this->id, 'context' => $this->get_context());
$event = \core\event\badge_archived::create($eventparams);
$event->trigger();
return;
}
$fs = get_file_storage();
// Remove all issued badge image files and badge awards.
// Cannot bulk remove area files here because they are issued in user context.
$awards = $this->get_awards();
foreach ($awards as $award) {
$usercontext = context_user::instance($award->userid);
$fs->delete_area_files($usercontext->id, 'badges', 'userbadge', $this->id);
}
$DB->delete_records('badge_issued', array('badgeid' => $this->id));
// Remove all badge criteria.
$criteria = $this->get_criteria();
foreach ($criteria as $criterion) {
$criterion->delete();
}
// Delete badge images.
$badgecontext = $this->get_context();
$fs->delete_area_files($badgecontext->id, 'badges', 'badgeimage', $this->id);
// Delete endorsements, competencies and related badges.
$DB->delete_records('badge_endorsement', array('badgeid' => $this->id));
$relatedsql = 'badgeid = :badgeid OR relatedbadgeid = :relatedbadgeid';
$relatedparams = array(
'badgeid' => $this->id,
'relatedbadgeid' => $this->id
);
$DB->delete_records_select('badge_related', $relatedsql, $relatedparams);
$DB->delete_records('badge_alignment', array('badgeid' => $this->id));
// Delete all tags.
\core_tag_tag::remove_all_item_tags('core_badges', 'badge', $this->id);
// Finally, remove badge itself.
$DB->delete_records('badge', array('id' => $this->id));
// Trigger event, badge deleted.
$eventparams = array('objectid' => $this->id,
'context' => $this->get_context(),
'other' => array('badgetype' => $this->type, 'courseid' => $this->courseid)
);
$event = \core\event\badge_deleted::create($eventparams);
$event->trigger();
}
/**
* Add multiple related badges.
*
* @param array $relatedids Id of badges.
*/
public function add_related_badges($relatedids) {
global $DB;
$relatedbadges = array();
foreach ($relatedids as $relatedid) {
$relatedbadge = new stdClass();
$relatedbadge->badgeid = $this->id;
$relatedbadge->relatedbadgeid = $relatedid;
$relatedbadges[] = $relatedbadge;
}
$DB->insert_records('badge_related', $relatedbadges);
}
/**
* Delete an related badge.
*
* @param int $relatedid Id related badge.
* @return boolean A status for delete an related badge.
*/
public function delete_related_badge($relatedid) {
global $DB;
$sql = "(badgeid = :badgeid AND relatedbadgeid = :relatedid) OR " .
"(badgeid = :relatedid2 AND relatedbadgeid = :badgeid2)";
$params = ['badgeid' => $this->id, 'badgeid2' => $this->id, 'relatedid' => $relatedid, 'relatedid2' => $relatedid];
return $DB->delete_records_select('badge_related', $sql, $params);
}
/**
* Checks if badge has related badges.
*
* @return boolean A status related badge.
*/
public function has_related() {
global $DB;
$sql = "SELECT DISTINCT b.id
FROM {badge_related} br
JOIN {badge} b ON (br.relatedbadgeid = b.id OR br.badgeid = b.id)
WHERE (br.badgeid = :badgeid OR br.relatedbadgeid = :badgeid2) AND b.id != :badgeid3";
return $DB->record_exists_sql($sql, ['badgeid' => $this->id, 'badgeid2' => $this->id, 'badgeid3' => $this->id]);
}
/**
* Get related badges of badge.
*
* @param boolean $activeonly Do not get the inactive badges when is true.
* @return array Related badges information.
*/
public function get_related_badges($activeonly = false) {
global $DB;
$params = array('badgeid' => $this->id, 'badgeid2' => $this->id, 'badgeid3' => $this->id);
$query = "SELECT DISTINCT b.id, b.name, b.version, b.language, b.type
FROM {badge_related} br
JOIN {badge} b ON (br.relatedbadgeid = b.id OR br.badgeid = b.id)
WHERE (br.badgeid = :badgeid OR br.relatedbadgeid = :badgeid2) AND b.id != :badgeid3";
if ($activeonly) {
$query .= " AND b.status <> :status";
$params['status'] = BADGE_STATUS_INACTIVE;
}
$relatedbadges = $DB->get_records_sql($query, $params);
return $relatedbadges;
}
/**
* Insert/update alignment information of badge.
*
* @param stdClass $alignment Data of a alignment.
* @param int $alignmentid ID alignment.
* @return bool|int A status/ID when insert or update data.
*/
public function save_alignment($alignment, $alignmentid = 0) {
global $DB;
$record = $DB->record_exists('badge_alignment', array('id' => $alignmentid));
if ($record) {
$alignment->id = $alignmentid;
return $DB->update_record('badge_alignment', $alignment);
} else {
return $DB->insert_record('badge_alignment', $alignment, true);
}
}
/**
* Delete a alignment of badge.
*
* @param int $alignmentid ID alignment.
* @return boolean A status for delete a alignment.
*/
public function delete_alignment($alignmentid) {
global $DB;
return $DB->delete_records('badge_alignment', array('badgeid' => $this->id, 'id' => $alignmentid));
}
/**
* Get alignments of badge.
*
* @return array List content alignments.
*/
public function get_alignments() {
global $DB;
return $DB->get_records('badge_alignment', array('badgeid' => $this->id));
}
/**
* Insert/update Endorsement information of badge.
*
* @param stdClass $endorsement Data of an endorsement.
* @return bool|int A status/ID when insert or update data.
*/
public function save_endorsement($endorsement) {
global $DB;
$record = $DB->get_record('badge_endorsement', array('badgeid' => $this->id));
if ($record) {
$endorsement->id = $record->id;
return $DB->update_record('badge_endorsement', $endorsement);
} else {
return $DB->insert_record('badge_endorsement', $endorsement, true);
}
}
/**
* Get endorsement of badge.
*
* @return array|stdClass Endorsement information.
*/
public function get_endorsement() {
global $DB;
return $DB->get_record('badge_endorsement', array('badgeid' => $this->id));
}
/**
* Markdown language support for criteria.
*
* @return string $output Markdown content to output.
*/
public function markdown_badge_criteria() {
$agg = $this->get_aggregation_methods();
if (empty($this->criteria)) {
return get_string('nocriteria', 'badges');
}
$overalldescr = '';
$overall = $this->criteria[BADGE_CRITERIA_TYPE_OVERALL];
if (!empty($overall->description)) {
$overalldescr = format_text($overall->description, $overall->descriptionformat,
array('context' => $this->get_context())) . '\n';
}
// Get the condition string.
if (count($this->criteria) == 2) {
$condition = get_string('criteria_descr', 'badges');
} else {
$condition = get_string('criteria_descr_' . BADGE_CRITERIA_TYPE_OVERALL, 'badges',
core_text::strtoupper($agg[$this->get_aggregation_method()]));
}
unset($this->criteria[BADGE_CRITERIA_TYPE_OVERALL]);
$items = array();
// If only one criterion left, make sure its description goe to the top.
if (count($this->criteria) == 1) {
$c = reset($this->criteria);
if (!empty($c->description)) {
$overalldescr = $c->description . '\n';
}
if (count($c->params) == 1) {
$items[] = ' * ' . get_string('criteria_descr_single_' . $c->criteriatype, 'badges') .
$c->get_details();
} else {
$items[] = '* ' . get_string('criteria_descr_' . $c->criteriatype, 'badges',
core_text::strtoupper($agg[$this->get_aggregation_method($c->criteriatype)])) .
$c->get_details();
}
} else {
foreach ($this->criteria as $type => $c) {
$criteriadescr = '';
if (!empty($c->description)) {
$criteriadescr = $c->description;
}
if (count($c->params) == 1) {
$items[] = ' * ' . get_string('criteria_descr_single_' . $type, 'badges') .
$c->get_details() . $criteriadescr;
} else {
$items[] = '* ' . get_string('criteria_descr_' . $type, 'badges',
core_text::strtoupper($agg[$this->get_aggregation_method($type)])) .
$c->get_details() . $criteriadescr;
}
}
}
return strip_tags($overalldescr . $condition . html_writer::alist($items, array(), 'ul'));
}
/**
* Define issuer information by format Open Badges specification version 2.
*
* @param int $obversion OB version to use.
* @return array Issuer informations of the badge.
*/
public function get_badge_issuer(?int $obversion = null) {
global $DB;
$issuer = [];
if ($obversion == OPEN_BADGES_V1) {
$data = $DB->get_record('badge', ['id' => $this->id]);
$issuer['name'] = $data->issuername;
$issuer['url'] = $data->issuerurl;
$issuer['email'] = $data->issuercontact;
} else {
$issuer['name'] = $this->issuername;
$issuer['url'] = $this->issuerurl;
$issuer['email'] = $this->issuercontact;
$issuer['@context'] = OPEN_BADGES_V2_CONTEXT;
$issueridurl = new moodle_url('/badges/issuer_json.php', array('id' => $this->id));
$issuer['id'] = $issueridurl->out(false);
$issuer['type'] = OPEN_BADGES_V2_TYPE_ISSUER;
}
return $issuer;
}
/**
* Get tags of badge.
*
* @return array Badge tags.
*/
public function get_badge_tags(): array {
return array_values(\core_tag_tag::get_item_tags_array('core_badges', 'badge', $this->id));
}
}
+153
View File
@@ -0,0 +1,153 @@
<?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/>.
/**
* Badges external API
*
* @package core_badges
* @category external
* @copyright 2016 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.1
*/
defined('MOODLE_INTERNAL') || die;
require_once($CFG->libdir . '/badgeslib.php');
use core_badges\external\user_badge_exporter;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
/**
* Badges external functions
*
* @package core_badges
* @category external
* @copyright 2016 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.1
*/
class core_badges_external extends external_api {
/**
* Describes the parameters for get_user_badges.
*
* @return external_function_parameters
* @since Moodle 3.1
*/
public static function get_user_badges_parameters() {
return new external_function_parameters (
array(
'userid' => new external_value(PARAM_INT, 'Badges only for this user id, empty for current user', VALUE_DEFAULT, 0),
'courseid' => new external_value(PARAM_INT, 'Filter badges by course id, empty all the courses', VALUE_DEFAULT, 0),
'page' => new external_value(PARAM_INT, 'The page of records to return.', VALUE_DEFAULT, 0),
'perpage' => new external_value(PARAM_INT, 'The number of records to return per page', VALUE_DEFAULT, 0),
'search' => new external_value(PARAM_RAW, 'A simple string to search for', VALUE_DEFAULT, ''),
'onlypublic' => new external_value(PARAM_BOOL, 'Whether to return only public badges', VALUE_DEFAULT, false),
)
);
}
/**
* Returns the list of badges awarded to a user.
*
* @param int $userid user id
* @param int $courseid course id
* @param int $page page of records to return
* @param int $perpage number of records to return per page
* @param string $search a simple string to search for
* @param bool $onlypublic whether to return only public badges
* @return array array containing warnings and the awarded badges
* @since Moodle 3.1
* @throws moodle_exception
*/
public static function get_user_badges($userid = 0, $courseid = 0, $page = 0, $perpage = 0, $search = '', $onlypublic = false) {
global $CFG, $USER, $PAGE;
$warnings = array();
$params = array(
'userid' => $userid,
'courseid' => $courseid,
'page' => $page,
'perpage' => $perpage,
'search' => $search,
'onlypublic' => $onlypublic,
);
$params = self::validate_parameters(self::get_user_badges_parameters(), $params);
if (empty($CFG->enablebadges)) {
throw new moodle_exception('badgesdisabled', 'badges');
}
if (empty($CFG->badges_allowcoursebadges) && $params['courseid'] != 0) {
throw new moodle_exception('coursebadgesdisabled', 'badges');
}
// Default value for userid.
if (empty($params['userid'])) {
$params['userid'] = $USER->id;
}
// Validate the user.
$user = core_user::get_user($params['userid'], '*', MUST_EXIST);
core_user::require_active_user($user);
$usercontext = context_user::instance($user->id);
self::validate_context($usercontext);
if ($USER->id != $user->id) {
require_capability('moodle/badges:viewotherbadges', $usercontext);
// We are looking other user's badges, we must retrieve only public badges.
$params['onlypublic'] = true;
}
$userbadges = badges_get_user_badges($user->id, $params['courseid'], $params['page'], $params['perpage'], $params['search'],
$params['onlypublic']);
$result = array();
$result['badges'] = array();
$result['warnings'] = $warnings;
foreach ($userbadges as $badge) {
$result['badges'][] = badges_prepare_badge_for_external($badge, $user);
}
return $result;
}
/**
* Describes the get_user_badges return value.
*
* @return external_single_structure
* @since Moodle 3.1
*/
public static function get_user_badges_returns() {
return new external_single_structure(
array(
'badges' => new external_multiple_structure(
user_badge_exporter::get_read_structure()
),
'warnings' => new external_warnings(),
)
);
}
}
+98
View File
@@ -0,0 +1,98 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains alignment class for displaying a badge alignment.
*
* @package core_badges
* @copyright 2018 Dani Palou <dani@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_badges\external;
defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
/**
* Class for displaying a badge alignment.
*
* @package core_badges
* @copyright 2018 Dani Palou <dani@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class alignment_exporter extends exporter {
/**
* Return the list of properties.
*
* @return array
*/
protected static function define_properties() {
return [
'id' => [
'type' => PARAM_INT,
'description' => 'Alignment id',
'optional' => true,
],
'badgeid' => [
'type' => PARAM_INT,
'description' => 'Badge id',
'optional' => true,
],
'targetName' => [
'type' => PARAM_TEXT,
'description' => 'Target name',
'optional' => true,
],
'targetUrl' => [
'type' => PARAM_URL,
'description' => 'Target URL',
'optional' => true,
],
'targetDescription' => [
'type' => PARAM_TEXT,
'description' => 'Target description',
'null' => NULL_ALLOWED,
'optional' => true,
],
'targetFramework' => [
'type' => PARAM_TEXT,
'description' => 'Target framework',
'null' => NULL_ALLOWED,
'optional' => true,
],
'targetCode' => [
'type' => PARAM_TEXT,
'description' => 'Target code',
'null' => NULL_ALLOWED,
'optional' => true,
]
];
}
/**
* Returns a list of objects that are related.
*
* @return array
*/
protected static function define_related() {
return array(
'context' => 'context',
);
}
}
+182
View File
@@ -0,0 +1,182 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains class for displaying a assertion.
*
* @package core_badges
* @copyright 2019 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_badges\external;
defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
use renderer_base;
use stdClass;
/**
* Class for displaying a badge competency.
*
* @package core_badges
* @copyright 2019 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class assertion_exporter extends exporter {
/**
* Constructor - saves the persistent object, and the related objects.
*
* @param mixed $data - Either an stdClass or an array of values.
* @param array $related - An optional list of pre-loaded objects related to this object.
*/
public function __construct($data, $related = array()) {
// Having mixed $data is causing some issues. As this class is treating $data as an object everywhere, it can be converted
// to object at this point, to avoid errors and get the expected behaviour always.
// $data is an array when this class is a request exporter in backpack_api_mapping, but it is an object when this is
// used as a response exporter.
parent::__construct((object) $data, $related);
}
/**
* Map from a request response data to the internal structure.
*
* @param stdClass $data The remote data.
* @param string $apiversion The backpack version used to communicate remotely.
* @return stdClass
*/
public static function map_external_data($data, $apiversion) {
$mapped = new \stdClass();
if (isset($data->entityType)) {
$mapped->type = $data->entityType;
} else {
$mapped->type = $data->type;
}
if (isset($data->entityId)) {
$mapped->id = $data->entityId;
} else {
$mapped->id = $data->id;
}
if (isset($data->issuedOn)) {
$mapped->issuedOn = $data->issuedOn;
}
if (isset($data->recipient)) {
$mapped->recipient = $data->recipient;
}
if (isset($data->badgeclass)) {
$mapped->badgeclass = $data->badgeclass;
}
$propname = '@context';
$mapped->$propname = 'https://w3id.org/openbadges/v2';
return $mapped;
}
/**
* Return the list of additional properties.
*
* @return array
*/
protected static function define_other_properties() {
return array(
'badge' => array(
'type' => badgeclass_exporter::read_properties_definition(),
'optional' => true
),
'recipient' => array(
'type' => recipient_exporter::read_properties_definition(),
'optional' => true
),
'verification' => array(
'type' => verification_exporter::read_properties_definition(),
'optional' => true
)
);
}
/**
* We map from related data passed as data to this exporter to clean exportable values.
*
* @param renderer_base $output
* @return array
*/
protected function get_other_values(renderer_base $output) {
global $DB;
$result = [];
if (property_exists($this->data, 'related_badge')) {
$exporter = new badgeclass_exporter($this->data->related_badge, $this->related);
$result['badge'] = $exporter->export($output);
}
if (property_exists($this->data, 'related_recipient')) {
$exporter = new recipient_exporter($this->data->related_recipient, $this->related);
$result['recipient'] = $exporter->export($output);
}
if (property_exists($this->data, 'related_verify')) {
$exporter = new verification_exporter($this->data->related_verify, $this->related);
$result['verification'] = $exporter->export($output);
}
return $result;
}
/**
* Return the list of properties.
*
* @return array
*/
protected static function define_properties() {
return [
'type' => [
'type' => PARAM_ALPHA,
'description' => 'Issuer',
],
'id' => [
'type' => PARAM_URL,
'description' => 'Unique identifier for this assertion',
],
'badgeclass' => [
'type' => PARAM_RAW,
'description' => 'Identifier of the badge for this assertion',
'optional' => true,
],
'issuedOn' => [
'type' => PARAM_RAW,
'description' => 'Date this badge was issued',
],
'expires' => [
'type' => PARAM_RAW,
'description' => 'Date this badge will expire',
'optional' => true,
],
'@context' => [
'type' => PARAM_URL,
'description' => 'Badge version',
],
];
}
/**
* Returns a list of objects that are related.
*
* @return array
*/
protected static function define_related() {
return array(
'context' => 'context'
);
}
}
+73
View File
@@ -0,0 +1,73 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains competency class for displaying a badge backpack.
*
* @package core_badges
* @copyright 2019 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_badges\external;
defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
/**
* Class for displaying a badge competency.
*
* @package core_badges
* @copyright 2018 Dani Palou <dani@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backpack_exporter extends exporter {
/**
* Return the list of properties.
*
* @return array
*/
protected static function define_properties() {
return [
'id' => [
'type' => PARAM_INT,
'description' => 'Backpack id',
],
'backpackapiurl' => [
'type' => PARAM_URL,
'description' => 'Backpack API URL',
],
'backpackweburl' => [
'type' => PARAM_URL,
'description' => 'Backpack Website URL',
],
'sitebackpack' => [
'type' => PARAM_BOOL,
'description' => 'Is this the current site backpack',
],
'apiversion' => [
'type' => PARAM_FLOAT,
'description' => 'API version supported',
],
'sortorder' => [
'type' => PARAM_INT,
'description' => 'Sort order'
]
];
}
}
+238
View File
@@ -0,0 +1,238 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains class for displaying a badgeclass.
*
* @package core_badges
* @copyright 2019 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_badges\external;
defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
use renderer_base;
/**
* Class for displaying a badge competency.
*
* @package core_badges
* @copyright 2019 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class badgeclass_exporter extends exporter {
/**
* Constructor - saves the persistent object, and the related objects.
*
* @param mixed $data - Either an stdClass or an array of values.
* @param array $related - An optional list of pre-loaded objects related to this object.
*/
public function __construct($data, $related = array()) {
// Having mixed $data is causing some issues. As this class is treating $data as an object everywhere, it can be converted
// to object at this point, to avoid errors and get the expected behaviour always.
// $data is an array when this class is a request exporter in backpack_api_mapping, but it is an object when this is
// used as a response exporter.
$data = (object) $data;
$pick = $this->pick_related();
foreach ($pick as $one) {
$isarray = false;
// Allow [] to mean an array of values.
if (substr($one, -2) === '[]') {
$one = substr($one, 0, -2);
$isarray = true;
}
$prefixed = 'related_' . $one;
if (property_exists($data, $one) && !array_key_exists($one, $related)) {
if ($isarray) {
$newrelated = [];
foreach ($data->$one as $item) {
$newrelated[] = (object) $item;
}
$related[$one] = $newrelated;
} else {
$related[$one] = (object) $data->$one;
}
unset($data->$one);
} else if (property_exists($data, $prefixed) && !array_key_exists($one, $related)) {
if ($isarray) {
$newrelated = [];
foreach ($data->$prefixed as $item) {
$newrelated[] = (object) $item;
}
$related[$one] = $newrelated;
} else {
$related[$one] = (object) $data->$prefixed;
}
unset($data->$prefixed);
} else if (!array_key_exists($one, $related)) {
$related[$one] = null;
}
}
parent::__construct($data, $related);
}
/**
* List properties passed in $data that should be moved to $related in the constructor.
*
* @return array A list of properties to move from $data to $related.
*/
public static function pick_related() {
return ['alignment[]', 'criteria'];
}
/**
* Map data from a request response to the internal structure.
*
* @param stdClass $data The remote data.
* @param string $apiversion The backpack version used to communicate remotely.
* @return stdClass
*/
public static function map_external_data($data, $apiversion) {
$mapped = new \stdClass();
if (isset($data->entityType)) {
$mapped->type = $data->entityType;
} else {
$mapped->type = $data->type;
}
if (isset($data->entityId)) {
$mapped->id = $data->entityId;
} else {
$mapped->id = $data->id;
}
$mapped->name = $data->name;
$mapped->image = $data->image;
$mapped->issuer = $data->issuer;
$mapped->description = $data->description;
if (isset($data->openBadgeId)) {
$mapped->hostedUrl = $data->openBadgeId;
} else {
$mapped->hostedUrl = $data->id;
}
return $mapped;
}
/**
* Return the list of properties.
*
* @return array
*/
protected static function define_properties() {
return [
'type' => [
'type' => PARAM_ALPHA,
'description' => 'BadgeClass',
],
'id' => [
'type' => PARAM_RAW,
'description' => 'Unique identifier for this badgeclass',
],
'issuer' => [
'type' => PARAM_RAW,
'description' => 'Unique identifier for this badgeclass',
'optional' => true,
],
'name' => [
'type' => PARAM_TEXT,
'description' => 'Name of the badgeclass',
],
'image' => [
'type' => PARAM_URL,
'description' => 'URL to the image.',
],
'description' => [
'type' => PARAM_TEXT,
'description' => 'Description of the badge class.',
],
'hostedUrl' => [
'type' => PARAM_RAW,
'description' => 'Identifier of the open badge for this assertion',
'optional' => true,
],
];
}
/**
* Returns a list of objects that are related.
*
* @return array
*/
protected static function define_related() {
return array(
'context' => 'context',
'alignment' => 'stdClass[]?',
'criteria' => 'stdClass?',
);
}
/**
* Return the list of additional properties.
*
* @return array
*/
protected static function define_other_properties() {
return array(
'alignment' => array(
'type' => alignment_exporter::read_properties_definition(),
'optional' => true,
'multiple' => true
),
'criteriaUrl' => array(
'type' => PARAM_URL,
'optional' => true
),
'criteriaNarrative' => array(
'type' => PARAM_TEXT,
'optional' => true
)
);
}
/**
* We map from related data passed as data to this exporter to clean exportable values.
*
* @param renderer_base $output
* @return array
*/
protected function get_other_values(renderer_base $output) {
global $DB;
$result = [];
if (array_key_exists('alignment', $this->related) && $this->related['alignment'] !== null) {
$alignment = [];
foreach ($this->related['alignment'] as $alignment) {
$exporter = new alignment_exporter($alignment, $this->related);
$alignments[] = $exporter->export($output);
}
$result['alignment'] = $alignments;
}
if (array_key_exists('criteria', $this->related) && $this->related['criteria'] !== null) {
if (property_exists($this->related['criteria'], 'id') && $this->related['criteria']->id !== null) {
$result['criteriaUrl'] = $this->related['criteria']->id;
}
if (property_exists($this->related['criteria'], 'narrative') && $this->related['criteria']->narrative !== null) {
$result['criteriaNarrative'] = $this->related['criteria']->narrative;
}
}
return $result;
}
}
+110
View File
@@ -0,0 +1,110 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains class for displaying a collection.
*
* @package core_badges
* @copyright 2019 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_badges\external;
defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
use stdClass;
/**
* Class for displaying a badge competency.
*
* @package core_badges
* @copyright 2019 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class collection_exporter extends exporter {
/**
* Either map version 1 data to version 2 or return it untouched.
*
* @param stdClass $data The remote data.
* @param string $apiversion The backpack version used to communicate remotely.
* @return stdClass
*/
public static function map_external_data($data, $apiversion) {
if ($apiversion == OPEN_BADGES_V1) {
$result = new stdClass();
$result->entityType = 'BackpackCollection';
$result->entityId = $data->groupId;
$result->name = $data->name;
$result->description = $data->description;
$result->assertions = [];
return $result;
}
return $data;
}
/**
* Return the list of properties.
*
* @return array
*/
protected static function define_properties() {
return [
'entityType' => [
'type' => PARAM_ALPHA,
'description' => 'BackpackCollection',
],
'entityId' => [
'type' => PARAM_RAW,
'description' => 'Unique identifier for this collection',
],
'name' => [
'type' => PARAM_TEXT,
'description' => 'Collection name',
],
'description' => [
'type' => PARAM_TEXT,
'description' => 'Collection description',
],
'share_url' => [
'type' => PARAM_URL,
'description' => 'Url to view this collection',
],
'published' => [
'type' => PARAM_BOOL,
'description' => 'True means this collection is public.',
],
'assertions' => [
'type' => PARAM_RAW,
'description' => 'List of assertion ids in this collection',
'multiple' => true,
],
];
}
/**
* Returns a list of objects that are related.
*
* @return array
*/
protected static function define_related() {
return array(
'context' => 'context',
);
}
}
+95
View File
@@ -0,0 +1,95 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains endorsement class for displaying a badge endorsement.
*
* @package core_badges
* @copyright 2018 Dani Palou <dani@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_badges\external;
defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
/**
* Class for displaying a badge endorsement.
*
* @package core_badges
* @copyright 2018 Dani Palou <dani@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class endorsement_exporter extends exporter {
/**
* Return the list of properties.
*
* @return array
*/
protected static function define_properties() {
return [
'id' => [
'type' => PARAM_INT,
'description' => 'Endorsement id',
],
'badgeid' => [
'type' => PARAM_INT,
'description' => 'Badge id',
],
'issuername' => [
'type' => PARAM_TEXT,
'description' => 'Endorsement issuer name',
],
'issuerurl' => [
'type' => PARAM_URL,
'description' => 'Endorsement issuer URL',
],
'issueremail' => [
'type' => PARAM_RAW,
'description' => 'Endorsement issuer email',
],
'claimid' => [
'type' => PARAM_URL,
'description' => 'Claim URL',
'null' => NULL_ALLOWED,
],
'claimcomment' => [
'type' => PARAM_NOTAGS,
'description' => 'Claim comment',
'null' => NULL_ALLOWED,
],
'dateissued' => [
'type' => PARAM_INT,
'description' => 'Date issued',
'default' => 0,
]
];
}
/**
* Returns a list of objects that are related.
*
* @return array
*/
protected static function define_related() {
return array(
'context' => 'context',
);
}
}
+114
View File
@@ -0,0 +1,114 @@
<?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/>.
namespace core_badges\external;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_multiple_structure;
use core_external\external_value;
use core_external\external_warnings;
use moodle_exception;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir . '/badgeslib.php');
/**
* External service to get user badge.
*
* This is mainly used by the mobile application.
*
* @package core_badges
* @category external
* @copyright 2023 Rodrigo Mady <rodrigo.mady@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 4.3
*/
class get_user_badge_by_hash extends external_api {
/**
* Returns description of method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'hash' => new external_value(PARAM_ALPHANUM, 'Badge issued hash', VALUE_REQUIRED),
]);
}
/**
* Execute the get user badge.
*
* @param string $hash
* @return array
* @throws \restricted_context_exception
*/
public static function execute(string $hash): array {
global $CFG;
// Initialize return variables.
$warnings = [];
$result = [];
// Validate the hash.
[
'hash' => $hash,
] = self::validate_parameters(self::execute_parameters(), [
'hash' => $hash,
]);
if (empty($CFG->enablebadges)) {
throw new moodle_exception('badgesdisabled', 'badges');
}
// Get the badge by hash.
$badge = badges_get_badge_by_hash($hash);
if (!empty($badge)) {
// Get the user that issued the badge.
$user = \core_user::get_user($badge->userid, '*', MUST_EXIST);
$result[] = badges_prepare_badge_for_external($badge, $user);
} else {
$warnings[] = [
'item' => $hash,
'warningcode' => 'badgeawardnotfound',
'message' => get_string('error:badgeawardnotfound', 'badges')
];
}
return [
'badge' => $result,
'warnings' => $warnings
];
}
/**
* Describe the return structure of the external service.
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'badge' => new external_multiple_structure(
user_badge_exporter::get_read_structure()
),
'warnings' => new external_warnings()
]);
}
}
+109
View File
@@ -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/>.
/**
* Contains class for displaying a issuer.
*
* @package core_badges
* @copyright 2019 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_badges\external;
defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
/**
* Class for displaying a badge competency.
*
* @package core_badges
* @copyright 2019 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class issuer_exporter extends exporter {
/**
* Either map version 1 data to version 2 or return it untouched.
*
* @param stdClass $data The remote data.
* @param string $apiversion The backpack version used to communicate remotely.
* @return stdClass
*/
public static function map_external_data($data, $apiversion) {
if ($apiversion == OPEN_BADGES_V1) {
$result = new \stdClass();
return $result;
}
$mapped = new \stdClass();
if (isset($data->entityType)) {
$mapped->type = $data->entityType;
} else {
$mapped->type = $data->type;
}
if (isset($data->entityId)) {
$mapped->id = $data->entityId;
} else {
$mapped->id = $data->id;
}
$mapped->name = $data->name;
$mapped->email = $data->email;
$mapped->url = $data->url;
return $mapped;
}
/**
* Return the list of properties.
*
* @return array
*/
protected static function define_properties() {
return [
'type' => [
'type' => PARAM_ALPHA,
'description' => 'Issuer',
],
'id' => [
'type' => PARAM_RAW,
'description' => 'Unique identifier for this issuer',
],
'name' => [
'type' => PARAM_TEXT,
'description' => 'Name of the issuer',
],
'email' => [
'type' => PARAM_EMAIL,
'description' => 'Email of the issuer',
],
'url' => [
'type' => PARAM_URL,
'description' => 'URL for this issuer',
],
];
}
/**
* Returns a list of objects that are related.
*
* @return array
*/
protected static function define_related() {
return array(
'context' => 'context',
);
}
}
+82
View File
@@ -0,0 +1,82 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains class for displaying a recipient.
*
* @package core_badges
* @copyright 2019 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_badges\external;
defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
/**
* Class for displaying a badge competency.
*
* @package core_badges
* @copyright 2019 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class recipient_exporter extends exporter {
/**
* Return the list of properties.
*
* @return array
*/
protected static function define_properties() {
return [
'identity' => [
'type' => PARAM_RAW,
'description' => 'Hashed email address to issue badge to.',
],
'plaintextIdentity' => [
'type' => PARAM_RAW,
'description' => 'Email address to issue badge to.',
'optional' => true,
],
'salt' => [
'type' => PARAM_RAW,
'description' => 'Salt used to hash email.',
'optional' => true,
],
'type' => [
'type' => PARAM_ALPHA,
'description' => 'Email',
],
'hashed' => [
'type' => PARAM_BOOL,
'description' => 'Should be true',
],
];
}
/**
* Returns a list of objects that are related.
*
* @return array
*/
protected static function define_related() {
return array(
'context' => 'context',
);
}
}
+85
View File
@@ -0,0 +1,85 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains related class for displaying information of a related badge.
*
* @package core_badges
* @copyright 2018 Dani Palou <dani@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_badges\external;
defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
/**
* Class for displaying information of a related badge.
*
* @package core_badges
* @copyright 2018 Dani Palou <dani@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class related_info_exporter extends exporter {
/**
* Return the list of properties.
*
* @return array
*/
protected static function define_properties() {
return [
'id' => [
'type' => PARAM_INT,
'description' => 'Badge id',
],
'name' => [
'type' => PARAM_TEXT,
'description' => 'Badge name',
],
'version' => [
'type' => PARAM_TEXT,
'description' => 'Version',
'optional' => true,
'null' => NULL_ALLOWED,
],
'language' => [
'type' => PARAM_NOTAGS,
'description' => 'Language',
'optional' => true,
'null' => NULL_ALLOWED,
],
'type' => [
'type' => PARAM_INT,
'description' => 'Type',
'optional' => true,
],
];
}
/**
* Returns a list of objects that are related.
*
* @return array
*/
protected static function define_related() {
return array(
'context' => 'context',
);
}
}
+311
View File
@@ -0,0 +1,311 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains user badge class for displaying a badge issued to a user.
*
* @package core_badges
* @copyright 2018 Dani Palou <dani@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_badges\external;
defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
use renderer_base;
use moodle_url;
use core_badges\external\endorsement_exporter;
use core_badges\external\alignment_exporter;
use core_badges\external\related_info_exporter;
/**
* Class for displaying a badge issued to a user.
*
* @package core_badges
* @copyright 2018 Dani Palou <dani@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user_badge_exporter extends exporter {
/**
* Return the list of properties.
*
* @return array
*/
protected static function define_properties() {
return [
'id' => [
'type' => PARAM_INT,
'description' => 'Badge id',
'optional' => true,
],
'name' => [
'type' => PARAM_TEXT,
'description' => 'Badge name',
],
'description' => [
'type' => PARAM_NOTAGS,
'description' => 'Badge description',
'null' => NULL_ALLOWED,
],
'timecreated' => [
'type' => PARAM_INT,
'description' => 'Time created',
'optional' => true,
'default' => 0,
],
'timemodified' => [
'type' => PARAM_INT,
'description' => 'Time modified',
'optional' => true,
'default' => 0,
],
'usercreated' => [
'type' => PARAM_INT,
'description' => 'User created',
'optional' => true,
],
'usermodified' => [
'type' => PARAM_INT,
'description' => 'User modified',
'optional' => true,
],
'issuername' => [
'type' => PARAM_TEXT,
'description' => 'Issuer name',
],
'issuerurl' => [
'type' => PARAM_URL,
'description' => 'Issuer URL',
],
'issuercontact' => [
'type' => PARAM_RAW,
'description' => 'Issuer contact',
'null' => NULL_ALLOWED,
],
'expiredate' => [
'type' => PARAM_INT,
'description' => 'Expire date',
'optional' => true,
'null' => NULL_ALLOWED,
],
'expireperiod' => [
'type' => PARAM_INT,
'description' => 'Expire period',
'optional' => true,
'null' => NULL_ALLOWED,
],
'type' => [
'type' => PARAM_INT,
'description' => 'Type',
'optional' => true,
'default' => 1,
],
'courseid' => [
'type' => PARAM_INT,
'description' => 'Course id',
'optional' => true,
'null' => NULL_ALLOWED,
],
'message' => [
'type' => PARAM_RAW,
'description' => 'Message',
'optional' => true,
],
'messagesubject' => [
'type' => PARAM_TEXT,
'description' => 'Message subject',
'optional' => true,
],
'attachment' => [
'type' => PARAM_INT,
'description' => 'Attachment',
'optional' => true,
'default' => 1,
],
'notification' => [
'type' => PARAM_INT,
'description' => 'Whether to notify when badge is awarded',
'optional' => true,
'default' => 1,
],
'nextcron' => [
'type' => PARAM_INT,
'description' => 'Next cron',
'optional' => true,
'null' => NULL_ALLOWED,
],
'status' => [
'type' => PARAM_INT,
'description' => 'Status',
'optional' => true,
'default' => 0,
],
'issuedid' => [
'type' => PARAM_INT,
'description' => 'Issued id',
'optional' => true,
],
'uniquehash' => [
'type' => PARAM_ALPHANUM,
'description' => 'Unique hash',
],
'dateissued' => [
'type' => PARAM_INT,
'description' => 'Date issued',
'default' => 0,
],
'dateexpire' => [
'type' => PARAM_INT,
'description' => 'Date expire',
'null' => NULL_ALLOWED,
],
'visible' => [
'type' => PARAM_INT,
'description' => 'Visible',
'optional' => true,
'default' => 0,
],
'email' => [
'type' => PARAM_TEXT,
'description' => 'User email',
'optional' => true,
],
'version' => [
'type' => PARAM_TEXT,
'description' => 'Version',
'optional' => true,
'null' => NULL_ALLOWED,
],
'language' => [
'type' => PARAM_NOTAGS,
'description' => 'Language',
'optional' => true,
'null' => NULL_ALLOWED,
],
'imageauthorname' => [
'type' => PARAM_TEXT,
'description' => 'Name of the image author',
'optional' => true,
'null' => NULL_ALLOWED,
],
'imageauthoremail' => [
'type' => PARAM_TEXT,
'description' => 'Email of the image author',
'optional' => true,
'null' => NULL_ALLOWED,
],
'imageauthorurl' => [
'type' => PARAM_URL,
'description' => 'URL of the image author',
'optional' => true,
'null' => NULL_ALLOWED,
],
'imagecaption' => [
'type' => PARAM_TEXT,
'description' => 'Caption of the image',
'optional' => true,
'null' => NULL_ALLOWED,
],
];
}
/**
* Returns a list of objects that are related.
*
* @return array
*/
protected static function define_related() {
return array(
'context' => 'context',
'endorsement' => 'stdClass?',
'alignment' => 'stdClass[]',
'relatedbadges' => 'stdClass[]',
);
}
/**
* Return the list of additional properties.
*
* @return array
*/
protected static function define_other_properties() {
return [
'badgeurl' => [
'type' => PARAM_URL,
'description' => 'Badge URL',
],
'endorsement' => [
'type' => endorsement_exporter::read_properties_definition(),
'description' => 'Badge endorsement',
'optional' => true,
],
'alignment' => [
'type' => alignment_exporter::read_properties_definition(),
'description' => 'Badge alignments',
'multiple' => true,
],
'relatedbadges' => [
'type' => related_info_exporter::read_properties_definition(),
'description' => 'Related badges',
'multiple' => true,
]
];
}
/**
* Get the additional values to inject while exporting.
*
* @param renderer_base $output The renderer.
* @return array Keys are the property names, values are their values.
*/
protected function get_other_values(renderer_base $output) {
$context = $this->related['context'];
$endorsement = $this->related['endorsement'];
$alignments = $this->related['alignment'];
$relatedbadges = $this->related['relatedbadges'];
$values = array(
'badgeurl' => moodle_url::make_webservice_pluginfile_url($context->id, 'badges', 'badgeimage', $this->data->id, '/',
'f3')->out(false),
'alignment' => array(),
'relatedbadges' => array(),
);
if ($endorsement) {
$endorsementexporter = new endorsement_exporter($endorsement, array('context' => $context));
$values['endorsement'] = $endorsementexporter->export($output);
}
if (!empty($alignments)) {
foreach ($alignments as $alignment) {
$alignmentexporter = new alignment_exporter($alignment, array('context' => $context));
$values['alignment'][] = $alignmentexporter->export($output);
}
}
if (!empty($relatedbadges)) {
foreach ($relatedbadges as $badge) {
$relatedexporter = new related_info_exporter($badge, array('context' => $context));
$values['relatedbadges'][] = $relatedexporter->export($output);
}
}
return $values;
}
}
+53
View File
@@ -0,0 +1,53 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains class for displaying a recipient.
*
* @package core_badges
* @copyright 2019 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_badges\external;
defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
/**
* Class for displaying a badge competency.
*
* @package core_badges
* @copyright 2019 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class verification_exporter extends exporter {
/**
* Return the list of properties.
*
* @return array
*/
protected static function define_properties() {
return [
'type' => [
'type' => PARAM_ALPHA,
'description' => 'Type of verification.',
]
];
}
}
+182
View File
@@ -0,0 +1,182 @@
<?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>
*/
namespace core_badges\form;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/formslib.php');
require_once($CFG->libdir . '/badgeslib.php');
use html_writer;
use moodleform;
use stdClass;
/**
* Form to edit backpack initial details.
*
*/
class backpack extends external_backpack {
/**
* Defines the form
*/
public function definition() {
global $USER, $PAGE, $OUTPUT, $CFG;
$mform = $this->_form;
$this->_customdata['userbackpack'] = 1;
$mform->addElement('html', html_writer::tag('span', '', array('class' => 'notconnected', 'id' => 'connection-error')));
$mform->addElement('header', 'backpackheader', get_string('backpackconnection', 'badges'));
$mform->addHelpButton('backpackheader', 'backpackconnection', 'badges');
$mform->addElement('hidden', 'userid', $USER->id);
$mform->setType('userid', PARAM_INT);
$freeze = [];
$status = null;
if (isset($this->_customdata['email'])) {
// Email will be passed in when we're in the process of verifying the user's email address,
// so set the connection status, lock the email field, and provide options to resend the verification
// email or cancel the verification process entirely and start over.
$freeze = ['backpackemail'];
$mform->addElement('hidden', 'password', $this->_customdata['backpackpassword']);
$mform->setType('password', PARAM_RAW);
$mform->addElement('hidden', 'externalbackpackid', $this->_customdata['backpackid']);
$mform->setType('externalbackpackid', PARAM_INT);
$status = html_writer::tag('span', get_string('backpackemailverificationpending', 'badges'),
array('class' => 'notconnected', 'id' => 'connection-status'));
} else {
$sitebackpacks = badges_get_site_backpacks();
$choices = [
'' => get_string('choosedots'),
];
$restrictedoptions = [''];
foreach ($sitebackpacks as $backpack) {
$choices[$backpack->id] = $backpack->backpackweburl;
if ($backpack->apiversion == OPEN_BADGES_V2P1) {
$restrictedoptions[] = $backpack->id;
}
}
$mform->addElement('select', 'externalbackpackid', get_string('backpackprovider', 'badges'), $choices);
$mform->setType('externalbackpackid', PARAM_INT);
$mform->addRule('externalbackpackid', get_string('required'), 'required');
$mform->hideIf('password', 'externalbackpackid', 'in', $restrictedoptions);
$mform->hideIf('backpackemail', 'externalbackpackid', 'in', $restrictedoptions);
// Static form element can't be used because they don't support hideIf. This is a workaround until MDL-66251 is fixed.
$group = [];
$group[] = $mform->createElement('static', 'loginbackpackgroup', '', get_string('loginbackpacktitle', 'badges'));
$mform->addGroup($group, 'loginbackpackgroup', '', '', false);
$mform->hideIf('loginbackpackgroup', 'externalbackpackid', 'in', $restrictedoptions);
}
if ($status) {
// Only display the status if it's set.
$mform->addElement('static', 'status', get_string('status'), $status);
}
$this->add_auth_fields($this->_customdata['email'] ?? $USER->email, !isset($this->_customdata['email']));
// Only display email and password when the user has selected a backpack.
$mform->hideIf('backpackemail', 'externalbackpackid', 'eq', '');
$mform->hideIf('password', 'externalbackpackid', 'eq', '');
$mform->setDisableShortforms(false);
// Freeze any elemnts after definition.
if ($freeze) {
$mform->freeze($freeze);
}
$this->add_action_buttons();
}
/**
* Override add_action_buttons
*
* @param bool $cancel
* @param null|text $submitlabel
*/
public function add_action_buttons($cancel = true, $submitlabel = null) {
$mform = $this->_form;
if (isset($this->_customdata['email'])) {
$buttonarray = [];
$buttonarray[] = &$mform->createElement('submit', 'submitbutton',
get_string('backpackconnectionresendemail', 'badges'));
$buttonarray[] = &$mform->createElement('submit', 'revertbutton',
get_string('backpackconnectioncancelattempt', 'badges'));
$mform->addGroup($buttonarray, 'buttonar', '', [''], false);
$mform->closeHeaderBefore('buttonar');
} else {
// Email isn't present, so provide an input element to get it and a button to start the verification process.
parent::add_action_buttons(false, get_string('backpackconnectionconnect', 'badges'));
}
}
/**
* Validates form data
*/
public function validation($data, $files) {
// Verify that the user has selected a backpack.
if (empty($data['externalbackpackid'])) {
$errors['externalbackpackid'] = get_string('externalbackpack_required', 'badges');
return $errors;
}
// We don't need to verify anything for OBv2.1.
if (badges_open_badges_backpack_api() == OPEN_BADGES_V2P1) {
return [];
}
// We don't need to verify the email address if we're clearing a pending email verification attempt.
if (isset($data['revertbutton'])) {
return [];
}
$errors = [];
// Email and password can't be blank.
if (empty($data['backpackemail'])) {
$errors['backpackemail'] = get_string('backpackemail_required', 'badges');
}
if (empty($data['password'])) {
$errors['password'] = get_string('password_required', 'badges');
}
if (!empty($errors)) {
return $errors;
}
// Check the given credentials (email and password) are valid for this backpack.
$check = new stdClass();
$check->email = $data['backpackemail'];
$check->password = $data['password'];
$sitebackpack = badges_get_site_backpack($data['externalbackpackid']);
$bp = new \core_badges\backpack_api($sitebackpack, $check);
$result = $bp->authenticate();
if ($result === false || !empty($result->error)) {
$msg = $bp->get_authentication_error();
$errors['backpackemail'] = get_string('backpackconnectionunexpectedresult', 'badges', $msg);
}
return $errors;
}
}
+240
View File
@@ -0,0 +1,240 @@
<?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>
*/
namespace core_badges\form;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/formslib.php');
require_once($CFG->libdir . '/badgeslib.php');
require_once($CFG->libdir . '/filelib.php');
use moodleform;
/**
* Form to edit badge details.
*
*/
class badge 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'));
// When downloading badge, it will be necessary to clean the name as PARAM_FILE.
$mform->setType('name', PARAM_TEXT);
$mform->addRule('name', null, 'required');
$mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
$mform->addElement('text', 'version', get_string('version', 'badges'), array('size' => '70'));
$mform->setType('version', PARAM_TEXT);
$mform->addHelpButton('version', 'version', 'badges');
$languages = get_string_manager()->get_list_of_languages();
$mform->addElement('select', 'language', get_string('language'), $languages);
$mform->addHelpButton('language', 'language', 'badges');
$mform->addElement('textarea', 'description', get_string('description', 'badges'), 'wrap="virtual" rows="8" cols="70"');
$mform->setType('description', PARAM_NOTAGS);
$mform->addRule('description', null, 'required');
$str = $action == 'new' ? get_string('badgeimage', 'badges') : get_string('newimage', 'badges');
$imageoptions = array('maxbytes' => 262144, 'accepted_types' => array('optimised_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('text', 'imageauthorname', get_string('imageauthorname', 'badges'), array('size' => '70'));
$mform->setType('imageauthorname', PARAM_TEXT);
$mform->addHelpButton('imageauthorname', 'imageauthorname', 'badges');
$mform->addElement('text', 'imageauthoremail', get_string('imageauthoremail', 'badges'), array('size' => '70'));
$mform->setType('imageauthoremail', PARAM_TEXT);
$mform->addHelpButton('imageauthoremail', 'imageauthoremail', 'badges');
$mform->addElement('text', 'imageauthorurl', get_string('imageauthorurl', 'badges'), array('size' => '70'));
$mform->setType('imageauthorurl', PARAM_URL);
$mform->addHelpButton('imageauthorurl', 'imageauthorurl', 'badges');
$mform->addElement('text', 'imagecaption', get_string('imagecaption', 'badges'), array('size' => '70'));
$mform->setType('imagecaption', PARAM_TEXT);
$mform->addHelpButton('imagecaption', 'imagecaption', 'badges');
$mform->addElement('tags', 'tags', get_string('tags', 'badges'), ['itemtype' => 'badge', 'component' => 'core_badges']);
if (badges_open_badges_backpack_api() == OPEN_BADGES_V1) {
$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');
if (isset($CFG->badges_defaultissuername)) {
$mform->setDefault('issuername', $CFG->badges_defaultissuername);
}
$mform->addHelpButton('issuername', 'issuername', 'badges');
$mform->addElement('text', 'issuercontact', get_string('contact', 'badges'), array('size' => '70'));
if (isset($CFG->badges_defaultissuercontact)) {
$mform->setDefault('issuercontact', $CFG->badges_defaultissuercontact);
}
$mform->setType('issuercontact', PARAM_RAW);
$mform->addHelpButton('issuercontact', 'contact', 'badges');
// 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('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);
$mform->addElement('hidden', 'action', $action);
$mform->setType('action', PARAM_TEXT);
if ($action == 'new') {
// Try to set default badge language to that of current language, or it's parent.
$language = current_language();
if (isset($languages[$language])) {
$defaultlanguage = $language;
} else {
// Calling get_parent_language returns an empty string instead of 'en'.
$defaultlanguage = get_parent_language($language) ?: 'en';
}
$mform->setDefault('language', $defaultlanguage);
$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 $badge object or array of default values
*/
public function set_data($badge) {
$defaultvalues = [];
parent::set_data($badge);
if (!empty($badge->expiredate)) {
$defaultvalues['expiry'] = 1;
$defaultvalues['expiredate'] = $badge->expiredate;
} else if (!empty($badge->expireperiod)) {
$defaultvalues['expiry'] = 2;
$defaultvalues['expireperiod'] = $badge->expireperiod;
}
$defaultvalues['tags'] = \core_tag_tag::get_item_tags_array('core_badges', 'badge', $badge->id);
$defaultvalues['currentimage'] = print_badge_image($badge, $badge->get_context(), 'large');
parent::set_data($defaultvalues);
}
/**
* Validates form data
*/
public function validation($data, $files) {
global $DB;
$errors = parent::validation($data, $files);
if (badges_open_badges_backpack_api() == OPEN_BADGES_V1) {
if (!empty($data['issuercontact']) && !validate_email($data['issuercontact'])) {
$errors['issuercontact'] = get_string('invalidemail');
}
}
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');
}
if ($data['imageauthoremail'] && !validate_email($data['imageauthoremail'])) {
$errors['imageauthoremail'] = get_string('invalidemail');
}
// Check for duplicate badge names.
if ($data['action'] == 'new') {
$duplicate = $DB->record_exists_select('badge', 'name = :name AND status != :deleted',
array('name' => $data['name'], 'deleted' => BADGE_STATUS_ARCHIVED));
} else {
$duplicate = $DB->record_exists_select('badge', 'name = :name AND id != :badgeid AND status != :deleted',
array('name' => $data['name'], 'badgeid' => $data['id'], 'deleted' => BADGE_STATUS_ARCHIVED));
}
if ($duplicate) {
$errors['name'] = get_string('error:duplicatename', 'badges');
}
if ($data['imageauthorurl'] && !preg_match('@^https?://.+@', $data['imageauthorurl'])) {
$errors['imageauthorurl'] = get_string('invalidurl', 'badges');
}
return $errors;
}
}
+124
View File
@@ -0,0 +1,124 @@
<?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>
*/
namespace core_badges\form;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/formslib.php');
require_once($CFG->libdir . '/badgeslib.php');
use html_writer;
use moodleform;
/**
* Form to select backpack collections.
*
* @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
*/
class collections extends moodleform {
/**
* Defines the form
*/
public function definition() {
global $USER;
$mform = $this->_form;
$email = $this->_customdata['email'];
$backpackweburl = $this->_customdata['backpackweburl'];
$selected = $this->_customdata['selected'];
if (isset($this->_customdata['groups'])) {
$groups = $this->_customdata['groups'];
$nogroups = null;
} else {
$groups = null;
$nogroups = $this->_customdata['nogroups'];
}
$backpack = get_backpack_settings($USER->id);
$sitebackpack = badges_get_site_backpack($backpack->backpackid);
$mform->addElement('header', 'backpackheader', get_string('backpackconnection_connected', 'badges'));
$mform->addElement('static', 'url', get_string('url'), $backpackweburl);
$status = html_writer::tag('span', get_string('connected', 'badges'), array('class' => 'connected'));
$mform->addElement('static', 'status', get_string('status'), $status);
$mform->addElement('static', 'email', get_string('email'), $email);
$mform->addElement('submit', 'disconnect', get_string('disconnect', 'badges'));
$mform->addElement('header', 'collectionheader', get_string('backpackimport', 'badges'));
$mform->addHelpButton('collectionheader', 'backpackimport', 'badges');
$hasgroups = false;
if (!empty($groups)) {
foreach ($groups as $group) {
$count = 0;
// Handle attributes based on backpack's supported version.
if ($sitebackpack->apiversion == OPEN_BADGES_V2) {
// OpenBadges v2 data attributes.
if (empty($group->published)) {
// Only public collections.
continue;
}
// Get the number of badges associated with this collection from the assertions array returned.
$count = count($group->assertions);
} else {
// OpenBadges v1 data attributes.
$group->entityId = $group->groupId;
// Get the number of badges associated with this collection. In that case, the number is returned directly.
$count = $group->badges;
}
if (!$hasgroups) {
$mform->addElement('static', 'selectgroup', '', get_string('selectgroup_start', 'badges'));
}
$hasgroups = true;
$name = $group->name . ' (' . $count . ')';
$mform->addElement(
'advcheckbox',
'group[' . $group->entityId . ']',
null,
$name,
array('group' => 1),
array(false, $group->entityId)
);
if (in_array($group->entityId, $selected)) {
$mform->setDefault('group[' . $group->entityId . ']', $group->entityId);
}
}
$mform->addElement('static', 'selectgroup', '', get_string('selectgroup_end', 'badges', $backpackweburl));
}
if (!$hasgroups) {
$mform->addElement('static', 'selectgroup', '', $nogroups);
}
$this->add_action_buttons();
}
}
+184
View File
@@ -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/>.
/**
* External backpack form
*
* @package core_badges
* @copyright 2019 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_badges\form;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/formslib.php');
/**
* Backpack form class.
*
* @package core_badges
* @copyright 2019 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class external_backpack extends \moodleform {
/**
* Create the form.
*
*/
public function definition() {
global $CFG;
$mform = $this->_form;
$backpack = false;
if (isset($this->_customdata['externalbackpack'])) {
$backpack = $this->_customdata['externalbackpack'];
}
$mform->addElement('hidden', 'action', 'edit');
$mform->setType('action', PARAM_ALPHA);
$apiversions = badges_get_badge_api_versions();
$mform->addElement('select', 'apiversion', get_string('apiversion', 'core_badges'), $apiversions);
$mform->setType('apiversion', PARAM_RAW);
$mform->setDefault('apiversion', OPEN_BADGES_V2P1);
$mform->addRule('apiversion', null, 'required', null, 'client');
$mform->addElement('text', 'backpackweburl', get_string('backpackweburl', 'core_badges'));
$mform->setType('backpackweburl', PARAM_URL);
$mform->addRule('backpackweburl', null, 'required', null, 'client');
$mform->addRule('backpackweburl', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
$mform->addElement('text', 'backpackapiurl', get_string('backpackapiurl', 'core_badges'));
$mform->setType('backpackapiurl', PARAM_URL);
$mform->addRule('backpackapiurl', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
$mform->addElement('hidden', 'id', ($backpack->id ?? null));
$mform->setType('id', PARAM_INT);
$mform->addElement('hidden', 'badgebackpack', 0);
$mform->setType('badgebackpack', PARAM_INT);
$mform->addElement('hidden', 'userid', 0);
$mform->setType('userid', PARAM_INT);
$mform->addElement('hidden', 'backpackuid', 0);
$mform->setType('backpackuid', PARAM_INT);
$mform->addElement('advcheckbox', 'includeauthdetails', null, get_string('includeauthdetails', 'core_badges'));
if (!empty($backpack->backpackemail) || !empty($backpack->password)) {
$mform->setDefault('includeauthdetails', 1);
}
$issuercontact = $CFG->badges_defaultissuercontact;
$this->add_auth_fields($issuercontact);
if ($backpack) {
$this->set_data($backpack);
}
$mform->hideIf('includeauthdetails', 'apiversion', 'in', [OPEN_BADGES_V2P1]);
$mform->hideIf('backpackemail', 'includeauthdetails');
$mform->hideIf('backpackemail', 'apiversion', 'in', [OPEN_BADGES_V2P1]);
$mform->hideIf('password', 'includeauthdetails');
$mform->hideIf('password', 'apiversion', 'in', [OPEN_BADGES_V1, OPEN_BADGES_V2P1]);
$mform->hideIf('backpackapiurl', 'apiversion', 'in', [OPEN_BADGES_V1, OPEN_BADGES_V2P1]);
// Disable short forms.
$mform->setDisableShortforms();
$this->add_action_buttons();
}
/**
* Validate the data from the form.
*
* @param array $data form data
* @param array $files form files
* @return array An array of error messages.
*/
public function validation($data, $files) {
$errors = parent::validation($data, $files);
// Ensure backpackapiurl and backpackweburl are valid URLs.
$isobv21 = isset($data['apiversion']) && $data['apiversion'] == OPEN_BADGES_V2P1;
if (!$isobv21) {
if (empty($data['backpackapiurl'])) {
$errors['backpackapiurl'] = get_string('err_required', 'form');
} else if (!preg_match('@^https?://.+@', $data['backpackapiurl'])) {
$errors['backpackapiurl'] = get_string('invalidurl', 'badges');
}
}
if (!empty($data['backpackweburl']) && !preg_match('@^https?://.+@', $data['backpackweburl'])) {
$errors['backpackweburl'] = get_string('invalidurl', 'badges');
}
return $errors;
}
/**
* Return submitted data if properly submitted or returns NULL if validation fails or
* if there is no submitted data.
*
* @return object|void
*/
public function get_data() {
$data = parent::get_data();
if ($data ) {
if ((isset($data->includeauthdetails) && !$data->includeauthdetails)
|| (isset($data->apiversion) && $data->apiversion == 2.1)) {
$data->backpackemail = "";
$data->password = "";
}
if ((isset($data->apiversion) && $data->apiversion == 1)) {
$data->password = "";
}
}
return $data;
}
/**
* Add backpack specific auth details.
*
* @param string|null $email The email addressed provided or null if it's new.
* @param bool $includepassword Include the password field. Defaults to true
* @throws \coding_exception
*/
protected function add_auth_fields(?string $email, bool $includepassword = true) {
$mform = $this->_form;
$emailstring = get_string('email');
$passwordstring = get_string('password');
$showpasswordhelp = false;
if (!isset($this->_customdata['userbackpack'])) {
$emailstring = get_string('defaultissuercontact', 'core_badges');
$passwordstring = get_string('defaultissuerpassword', 'core_badges');
$showpasswordhelp = true;
}
$mform->addElement('text', 'backpackemail', $emailstring);
$mform->setType('backpackemail', PARAM_EMAIL);
$mform->setDefault('backpackemail', $email);
if ($includepassword) {
$mform->addElement('passwordunmask', 'password', $passwordstring);
$mform->setType('password', PARAM_RAW);
if ($showpasswordhelp) {
$mform->addHelpButton('password', 'defaultissuerpassword', 'badges');
}
}
}
}
+94
View File
@@ -0,0 +1,94 @@
<?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 badge message.
*
* @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>
*/
namespace core_badges\form;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/formslib.php');
require_once($CFG->libdir . '/badgeslib.php');
require_once($CFG->libdir . '/filelib.php');
use moodleform;
/**
* Form to edit badge message.
*
* @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
*/
class message extends moodleform {
/**
* Create the form.
*/
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');
if (empty($CFG->allowattachments)) {
$mform->freeze('attachment');
}
$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);
}
}
+75
View File
@@ -0,0 +1,75 @@
<?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 helper library.
*
* @package core
* @subpackage badges
* @copyright 2020 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_badges;
/**
* Badge helper library.
*
* @copyright 2020 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class helper {
/**
* Create a backpack.
*
* @param array $params Parameters.
* @return object
*/
public static function create_fake_backpack(array $params = []) {
global $DB;
$record = (object) array_merge([
'userid' => null,
'email' => 'test@example.com',
'backpackuid' => -1,
'autosync' => 0,
'password' => '',
'externalbackpackid' => 12345,
], $params);
$record->id = $DB->insert_record('badge_backpack', $record);
return $record;
}
/**
* Create a user backpack collection.
*
* @param array $params Parameters.
* @return object
*/
public static function create_fake_backpack_collection(array $params = []) {
global $DB;
$record = (object) array_merge([
'backpackid' => 12345,
'collectionid' => -1,
'entityid' => random_string(20),
], $params);
$record->id = $DB->insert_record('badge_external', $record);
return $record;
}
}
+96
View File
@@ -0,0 +1,96 @@
<?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 to proccess Oauth2 connects for backpack.
*
* @package core_badges
* @subpackage badges
* @copyright 2020 Tung Thai
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Tung Thai <Tung.ThaiDuc@nashtechglobal.com>
*/
namespace core_badges\oauth2;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/authlib.php');
use stdClass;
/**
* Proccess Oauth2 connects to backpack site.
*
* @copyright 2020 Tung Thai
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Tung Thai <Tung.ThaiDuc@nashtechglobal.com>
*/
class auth extends \auth_oauth2\auth {
/**
* To complete data after login.
*
* @param client $client object.
* @param string $redirecturl the url redirect.
*/
public function complete_data(\core_badges\oauth2\client $client, $redirecturl) {
global $DB, $USER;
$userinfo = $client->get_userinfo();
$badgebackpack = new stdClass();
$badgebackpack->userid = $USER->id;
if ($userinfo && isset($userinfo->email)) {
$badgebackpack->email = $userinfo->email;
} else {
$badgebackpack->email = $USER->email;
}
$badgebackpack->externalbackpackid = $client->backpack->id;
$badgebackpack->backpackuid = 0;
$badgebackpack->autosync = 0;
$badgebackpack->password = '';
$record = $DB->get_record('badge_backpack', ['userid' => $USER->id,
'externalbackpackid' => $client->backpack->id]);
if (!$record) {
$DB->insert_record('badge_backpack', $badgebackpack);
} else {
$badgebackpack->id = $record->id;
$DB->update_record('badge_backpack', $badgebackpack);
}
redirect($redirecturl, get_string('backpackconnected', 'badges'), null,
\core\output\notification::NOTIFY_SUCCESS);
}
/**
* Check user has been logged the backpack site.
*
* @param int $externalbackpackid ID of external backpack.
* @param int $userid ID of user.
* @return bool
*/
public static function is_logged_oauth2($externalbackpackid, $userid) {
global $USER;
if (empty($userid)) {
$userid = $USER->id;
}
$persistedtoken = badge_backpack_oauth2::get_record(['externalbackpackid' => $externalbackpackid, 'userid' => $userid]);
if ($persistedtoken) {
return true;
}
return false;
}
}
@@ -0,0 +1,77 @@
<?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 form add/update oauth2 for backpack is connected.
*
* @package core_badges
* @subpackage badges
* @copyright 2020 Tung Thai
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Tung Thai <Tung.ThaiDuc@nashtechglobal.com>
*/
namespace core_badges\oauth2;
defined('MOODLE_INTERNAL') || die();
use core\persistent;
/**
* Class badge_backpack_oauth2 for backpack is connected.
*
* @copyright 2020 Tung Thai
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Tung Thai <Tung.ThaiDuc@nashtechglobal.com>
*/
class badge_backpack_oauth2 extends persistent {
/**
* The table name.
*/
const TABLE = 'badge_backpack_oauth2';
/**
* Return the definition of the properties of this model.
*
* @return array
*/
protected static function define_properties() {
return array(
'userid' => array(
'type' => PARAM_INT,
),
'issuerid' => array(
'type' => PARAM_INT
),
'externalbackpackid' => array(
'type' => PARAM_INT
),
'token' => array(
'type' => PARAM_TEXT
),
'refreshtoken' => array(
'type' => PARAM_TEXT
),
'expires' => array(
'type' => PARAM_INT
),
'scope' => array(
'type' => PARAM_TEXT
),
);
}
}
+351
View File
@@ -0,0 +1,351 @@
<?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/>.
/**
* Configurable OAuth2 client class.
*
* @package core_badges
* @subpackage badges
* @copyright 2020 Tung Thai
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Tung Thai <Tung.ThaiDuc@nashtechglobal.com>
*/
namespace core_badges\oauth2;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/oauthlib.php');
require_once($CFG->libdir . '/filelib.php');
require_once('badge_backpack_oauth2.php');
use moodle_url;
use moodle_exception;
use stdClass;
define('BACKPACK_CHALLENGE_METHOD', 'S256');
define('BACKPACK_CODE_VERIFIER_TIME', 60);
/**
* Configurable OAuth2 client to request authorization and store token. Use the PKCE method to verifier authorization.
*
* @copyright 2020 Tung Thai
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Tung Thai <Tung.ThaiDuc@nashtechglobal.com>
*/
class client extends \core\oauth2\client {
/** @var \core\oauth2\issuer */
private $issuer;
/** @var string $clientid client identifier issued to the client */
private $clientid = '';
/** @var string $clientsecret The client secret. */
private $clientsecret = '';
/** @var moodle_url $returnurl URL to return to after authenticating */
private $returnurl = null;
/** @var string $grantscope */
protected $grantscope = '';
/** @var string $scope */
protected $scope = '';
/** @var bool basicauth */
protected $basicauth = true;
/** @var string|null backpack object */
public $backpack = '';
/**
* client constructor.
*
* @param issuer $issuer oauth2 service.
* @param string $returnurl return url after login
* @param string $additionalscopes the scopes has been granted
* @param null $backpack backpack object.
* @throws \coding_exception error message.
*/
public function __construct(\core\oauth2\issuer $issuer, $returnurl = '', $additionalscopes = '',
$backpack = null) {
$this->issuer = $issuer;
$this->clientid = $issuer->get('clientid');
$this->returnurl = $returnurl;
$this->clientsecret = $issuer->get('clientsecret');
$this->backpack = $backpack;
$this->grantscope = $additionalscopes;
$this->scope = $additionalscopes;
parent::__construct($issuer, $returnurl, $additionalscopes, false);
}
/**
* Get login url.
*
* @return moodle_url
* @throws \coding_exception
* @throws moodle_exception
*/
public function get_login_url() {
$callbackurl = self::callback_url();
$scopes = $this->issuer->get('scopessupported');
// Removed the scopes does not support in authorization.
$excludescopes = ['profile', 'openid'];
$arrascopes = explode(' ', $scopes);
foreach ($excludescopes as $exscope) {
$key = array_search($exscope, $arrascopes);
if (isset($key)) {
unset($arrascopes[$key]);
}
}
$scopes = implode(' ', $arrascopes);
$params = array_merge(
[
'client_id' => $this->clientid,
'response_type' => 'code',
'redirect_uri' => $callbackurl->out(false),
'state' => $this->returnurl->out_as_local_url(false),
'scope' => $scopes,
'code_challenge' => $this->code_challenge(),
'code_challenge_method' => BACKPACK_CHALLENGE_METHOD,
]
);
return new moodle_url($this->auth_url(), $params);
}
/**
* Generate code challenge.
*
* @return string
*/
public function code_challenge() {
$random = bin2hex(openssl_random_pseudo_bytes(43));
$verifier = $this->base64url_encode(pack('H*', $random));
$challenge = $this->base64url_encode(pack('H*', hash('sha256', $verifier)));
$_SESSION['SESSION']->code_verifier = $verifier;
return $challenge;
}
/**
* Get code verifier.
*
* @return bool
*/
public function code_verifier() {
if (isset($_SESSION['SESSION']) && !empty($_SESSION['SESSION']->code_verifier)) {
return $_SESSION['SESSION']->code_verifier;
}
return false;
}
/**
* Generate base64url encode.
*
* @param string $plaintext text to convert.
* @return string
*/
public function base64url_encode($plaintext) {
$base64 = base64_encode($plaintext);
$base64 = trim($base64, "=");
$base64url = strtr($base64, '+/', '-_');
return ($base64url);
}
/**
* Callback url where the request is returned to.
*
* @return moodle_url url of callback
*/
public static function callback_url() {
return new moodle_url('/admin/oauth2callback.php');
}
/**
* Check and refresh token to keep login on backpack site.
*
* @return bool
* @throws \coding_exception
* @throws moodle_exception
*/
public function is_logged_in() {
// Has the token expired?
if (isset($this->accesstoken->expires) && time() >= $this->accesstoken->expires) {
if (isset($this->accesstoken->refreshtoken)) {
return $this->upgrade_token($this->accesstoken->refreshtoken, 'refresh_token');
} else {
throw new moodle_exception('Could not refresh oauth token, please try again.');
}
}
if (isset($this->accesstoken->token) && isset($this->accesstoken->scope)) {
return true;
}
// If we've been passed then authorization code generated by the
// authorization server try and upgrade the token to an access token.
$code = optional_param('oauth2code', null, PARAM_RAW);
// Note - sometimes we may call is_logged_in twice in the same request - we don't want to attempt
// to upgrade the same token twice.
if ($code && $this->upgrade_token($code, 'authorization_code')) {
return true;
}
return false;
}
/**
* Request new token.
*
* @param string $code code verify from Auth site.
* @param string $granttype grant type.
* @return bool
* @throws moodle_exception
*/
public function upgrade_token($code, $granttype = 'authorization_code') {
$callbackurl = self::callback_url();
if ($granttype == 'authorization_code') {
$this->basicauth = true;
$params = array('code' => $code,
'grant_type' => $granttype,
'redirect_uri' => $callbackurl->out(false),
'scope' => $this->get_scopes(),
'code_verifier' => $this->code_verifier()
);
} else if ($granttype == 'refresh_token') {
$this->basicauth = false;
$params = array('refresh_token' => $code,
'grant_type' => $granttype,
'scope' => $this->get_scopes(),
);
}
if ($this->basicauth) {
$idsecret = $this->clientid . ':' . $this->clientsecret;
$this->setHeader('Authorization: Basic ' . base64_encode($idsecret));
} else {
$params['client_id'] = $this->clientid;
$params['client_secret'] = $this->clientsecret;
}
// Requests can either use http GET or POST.
$response = $this->post($this->token_url(), $this->build_post_data($params));
if ($this->info['http_code'] !== 200) {
$debuginfo = !empty($this->error) ? $this->error : $response;
throw new moodle_exception('oauth2refreshtokenerror', 'core_error', '', $this->info['http_code'], $debuginfo);
}
$r = json_decode($response);
if (is_null($r)) {
throw new moodle_exception("Could not decode JSON token response");
}
if (!empty($r->error)) {
throw new moodle_exception($r->error . ' ' . $r->error_description);
}
if (!isset($r->access_token)) {
return false;
}
// Store the token an expiry time.
$accesstoken = new stdClass;
$accesstoken->token = $r->access_token;
if (isset($r->expires_in)) {
// Expires 10 seconds before actual expiry.
$accesstoken->expires = (time() + ($r->expires_in - 10));
}
if (isset($r->refresh_token)) {
$this->refreshtoken = $r->refresh_token;
$accesstoken->refreshtoken = $r->refresh_token;
}
$accesstoken->scope = $r->scope;
// Also add the scopes.
$this->store_token($accesstoken);
return true;
}
/**
* Store a token to verify for send request.
*
* @param null|stdClass $token
*/
protected function store_token($token) {
global $USER;
$this->accesstoken = $token;
// Create or update a DB record with the new token.
$persistedtoken = badge_backpack_oauth2::get_record(['externalbackpackid' => $this->backpack->id, 'userid' => $USER->id]);
if ($token !== null) {
if (!$persistedtoken) {
$persistedtoken = new badge_backpack_oauth2();
$persistedtoken->set('issuerid', $this->backpack->oauth2_issuerid);
$persistedtoken->set('externalbackpackid', $this->backpack->id);
$persistedtoken->set('userid', $USER->id);
} else {
$persistedtoken->set('timemodified', time());
}
// Update values from $token. Don't use from_record because that would skip validation.
$persistedtoken->set('usermodified', $USER->id);
$persistedtoken->set('token', $token->token);
$persistedtoken->set('refreshtoken', $token->refreshtoken);
$persistedtoken->set('expires', $token->expires);
$persistedtoken->set('scope', $token->scope);
$persistedtoken->save();
} else {
if ($persistedtoken) {
$persistedtoken->delete();
}
}
}
/**
* Get token of current user.
*
* @return stdClass|null token object
*/
protected function get_stored_token() {
global $USER;
$token = badge_backpack_oauth2::get_record(['externalbackpackid' => $this->backpack->id, 'userid' => $USER->id]);
if ($token !== false) {
$token = $token->to_record();
return $token;
}
return null;
}
/**
* Get scopes granted.
*
* @return null|string
*/
protected function get_scopes() {
if (!empty($this->grantscope)) {
return $this->grantscope;
}
$token = $this->get_stored_token();
if ($token) {
return $token->scope;
}
return null;
}
}
+254
View File
@@ -0,0 +1,254 @@
<?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/>.
defined('MOODLE_INTERNAL') || die();
use \core_badges\badge;
/**
* Event observer for badges.
*
* @package core_badges
* @copyright 2013 Rajesh Taneja <rajesh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_badges_observer {
/**
* Triggered when 'course_module_completion_updated' event is triggered.
*
* @param \core\event\course_module_completion_updated $event
*/
public static function course_module_criteria_review(\core\event\course_module_completion_updated $event) {
global $DB, $CFG;
if (!empty($CFG->enablebadges)) {
require_once($CFG->dirroot.'/lib/badgeslib.php');
$eventdata = $event->get_record_snapshot('course_modules_completion', $event->objectid);
$userid = $event->relateduserid;
$mod = $event->contextinstanceid;
if ($eventdata->completionstate == COMPLETION_COMPLETE
|| $eventdata->completionstate == COMPLETION_COMPLETE_PASS
|| $eventdata->completionstate == COMPLETION_COMPLETE_FAIL) {
// Need to take into account that there can be more than one badge with the same activity in its criteria.
if ($rs = $DB->get_records('badge_criteria_param', array('name' => 'module_' . $mod, 'value' => $mod))) {
foreach ($rs as $r) {
$bid = $DB->get_field('badge_criteria', 'badgeid', array('id' => $r->critid), MUST_EXIST);
$badge = new badge($bid);
if (!$badge->is_active() || $badge->is_issued($userid)) {
continue;
}
if ($badge->criteria[BADGE_CRITERIA_TYPE_ACTIVITY]->review($userid)) {
$badge->criteria[BADGE_CRITERIA_TYPE_ACTIVITY]->mark_complete($userid);
if ($badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($userid)) {
$badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($userid);
$badge->issue($userid);
}
}
}
}
}
}
}
/**
* Triggered when '\core\event\competency_evidence_created' event is triggered.
*
* @param \core\event\competency_evidence_created $event
*/
public static function competency_criteria_review(\core\event\competency_evidence_created $event) {
global $DB, $CFG;
if (!empty($CFG->enablebadges)) {
require_once($CFG->dirroot.'/lib/badgeslib.php');
if (!get_config('core_competency', 'enabled')) {
return;
}
$ucid = $event->other['usercompetencyid'];
$cid = $event->other['competencyid'];
$userid = $event->relateduserid;
if ($rs = $DB->get_records('badge_criteria_param', array('name' => 'competency_' . $cid, 'value' => $cid))) {
foreach ($rs as $r) {
$crit = $DB->get_record('badge_criteria', array('id' => $r->critid), 'badgeid, criteriatype', MUST_EXIST);
$badge = new badge($crit->badgeid);
// Only site badges are updated from site competencies.
if (!$badge->is_active() || $badge->is_issued($userid)) {
continue;
}
if ($badge->criteria[$crit->criteriatype]->review($userid)) {
$badge->criteria[$crit->criteriatype]->mark_complete($userid);
if ($badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($userid)) {
$badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($userid);
$badge->issue($userid);
}
}
}
}
}
}
/**
* Triggered when 'course_completed' event is triggered.
*
* @param \core\event\course_completed $event
*/
public static function course_criteria_review(\core\event\course_completed $event) {
global $DB, $CFG;
if (!empty($CFG->enablebadges)) {
require_once($CFG->dirroot.'/lib/badgeslib.php');
$userid = $event->relateduserid;
$courseid = $event->courseid;
// Need to take into account that course can be a part of course_completion and courseset_completion criteria.
if ($rs = $DB->get_records('badge_criteria_param', array('name' => 'course_' . $courseid, 'value' => $courseid))) {
foreach ($rs as $r) {
$crit = $DB->get_record('badge_criteria', array('id' => $r->critid), 'badgeid, criteriatype', MUST_EXIST);
$badge = new badge($crit->badgeid);
if (!$badge->is_active() || $badge->is_issued($userid)) {
continue;
}
if ($badge->criteria[$crit->criteriatype]->review($userid)) {
$badge->criteria[$crit->criteriatype]->mark_complete($userid);
if ($badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($userid)) {
$badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($userid);
$badge->issue($userid);
}
}
}
}
}
}
/**
* Triggered when 'badge_awarded' event happens.
*
* @param \core\event\badge_awarded $event event generated when a badge is awarded.
*/
public static function badge_criteria_review(\core\event\badge_awarded $event) {
global $DB, $CFG;
if (!empty($CFG->enablebadges)) {
require_once($CFG->dirroot.'/lib/badgeslib.php');
$userid = $event->relateduserid;
if ($rs = $DB->get_records('badge_criteria', array('criteriatype' => BADGE_CRITERIA_TYPE_BADGE))) {
foreach ($rs as $r) {
$badge = new badge($r->badgeid);
if (!$badge->is_active() || $badge->is_issued($userid)) {
continue;
}
if ($badge->criteria[BADGE_CRITERIA_TYPE_BADGE]->review($userid)) {
$badge->criteria[BADGE_CRITERIA_TYPE_BADGE]->mark_complete($userid);
if ($badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($userid)) {
$badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($userid);
$badge->issue($userid);
}
}
}
}
}
}
/**
* Triggered when 'user_updated' event happens.
*
* @param \core\event\user_updated $event event generated when user profile is updated.
*/
public static function profile_criteria_review(\core\event\user_updated $event) {
global $DB, $CFG;
if (!empty($CFG->enablebadges)) {
require_once($CFG->dirroot.'/lib/badgeslib.php');
$userid = $event->objectid;
if ($rs = $DB->get_records('badge_criteria', array('criteriatype' => BADGE_CRITERIA_TYPE_PROFILE))) {
foreach ($rs as $r) {
$badge = new badge($r->badgeid);
if (!$badge->is_active() || $badge->is_issued($userid)) {
continue;
}
if ($badge->criteria[BADGE_CRITERIA_TYPE_PROFILE]->review($userid)) {
$badge->criteria[BADGE_CRITERIA_TYPE_PROFILE]->mark_complete($userid);
if ($badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($userid)) {
$badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($userid);
$badge->issue($userid);
}
}
}
}
}
}
/**
* Triggered when the 'cohort_member_added' event happens.
*
* @param \core\event\cohort_member_added $event generated when a user is added to a cohort
*/
public static function cohort_criteria_review(\core\event\cohort_member_added $event) {
global $DB, $CFG;
if (!empty($CFG->enablebadges)) {
require_once($CFG->dirroot.'/lib/badgeslib.php');
$cohortid = $event->objectid;
$userid = $event->relateduserid;
// Get relevant badges.
$badgesql = "SELECT badgeid
FROM {badge_criteria_param} cp
JOIN {badge_criteria} c ON cp.critid = c.id
WHERE c.criteriatype = ?
AND cp.name = ?";
$badges = $DB->get_records_sql($badgesql, array(BADGE_CRITERIA_TYPE_COHORT, "cohort_{$cohortid}"));
if (empty($badges)) {
return;
}
foreach ($badges as $b) {
$badge = new badge($b->badgeid);
if (!$badge->is_active()) {
continue;
}
if ($badge->is_issued($userid)) {
continue;
}
if ($badge->criteria[BADGE_CRITERIA_TYPE_COHORT]->review($userid)) {
$badge->criteria[BADGE_CRITERIA_TYPE_COHORT]->mark_complete($userid);
if ($badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($userid)) {
$badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($userid);
$badge->issue($userid);
}
}
}
}
}
}
@@ -0,0 +1,75 @@
<?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/>.
/**
* Issued badge renderable.
*
* @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>
*/
namespace core_badges\output;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/badgeslib.php');
use renderable;
/**
* Link to external resources this badge is aligned with.
*
* @copyright 2018 Tung Thai
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Tung Thai <Tung.ThaiDuc@nashtechglobal.com>
*/
class badge_alignments 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 $alignments = array();
/** @var array list of badges. */
public $currentbadgeid = 0;
/**
* Initializes the list of alignments to display.
*
* @param array $alignments List alignments to render.
* @param int $currentbadgeid ID current badge.
*/
public function __construct($alignments, $currentbadgeid) {
$this->alignments = $alignments;
$this->currentbadgeid = $currentbadgeid;
}
}
@@ -0,0 +1,70 @@
<?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/>.
/**
* Issued badge renderable.
*
* @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>
*/
namespace core_badges\output;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/badgeslib.php');
use renderable;
/**
* Collection of all badges for view.php page
*
* @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
*/
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;
}
}
@@ -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/>.
/**
* Issued badge renderable.
*
* @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>
*/
namespace core_badges\output;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/badgeslib.php');
use renderable;
/**
* Collection of badges used at the index.php page
*
* @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
*/
class badge_management extends badge_collection implements renderable {
}
@@ -0,0 +1,70 @@
<?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/>.
/**
* Issued badge renderable.
*
* @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>
*/
namespace core_badges\output;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/badgeslib.php');
use renderable;
/**
* Badge recipients rendering class
*
* @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
*/
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;
}
}
+76
View File
@@ -0,0 +1,76 @@
<?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/>.
/**
* Collection of all related 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>
*/
namespace core_badges\output;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/badgeslib.php');
use renderable;
/**
* Collection of all related badges.
*
* @copyright 2018 Tung Thai
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Tung Thai <Tung.ThaiDuc@nashtechglobal.com>
*/
class badge_related 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 int the current badge. */
public $currentbadgeid = 0;
/** @var array list of badges. */
public $badges = array();
/**
* Initializes the list of badges to display.
*
* @param array $badges related badges to render.
* @param int $currentbadgeid ID current badge.
*/
public function __construct($badges, $currentbadgeid) {
$this->badges = $badges;
$this->currentbadgeid = $currentbadgeid;
}
}
@@ -0,0 +1,63 @@
<?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/>.
/**
* Collection of use 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>
*/
namespace core_badges\output;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/badgeslib.php');
use renderable;
/**
* Collection of user badges used at the mybadges.php page
*
* @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
*/
class badge_user_collection extends badge_collection implements renderable {
/** @var array backpack settings */
public $backpack = null;
/** @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) {
global $CFG;
parent::__construct($badges);
if (!empty($CFG->badges_allowexternalbackpack)) {
$this->backpack = get_backpack_settings($userid, true);
}
}
}
+180
View File
@@ -0,0 +1,180 @@
<?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/>.
namespace core_badges\output;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/badgeslib.php');
use coding_exception;
use context_course;
use stdClass;
use renderable;
use core_badges\badge;
use moodle_url;
use renderer_base;
/**
* Page to display badge information, such as name, description or criteria. This information is unrelated to assertions.
*
* @package core_badges
* @copyright 2022 Sara Arjona (sara@moodle.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class badgeclass implements renderable {
/** @var badge class */
public $badge;
/** @var badge class */
public $badgeid = 0;
/** @var \context The badge context*/
public $context;
/**
* Initializes the badge to display.
*
* @param int $id Id of the badge to display.
*/
public function __construct(int $id) {
$this->badgeid = $id;
$this->badge = new badge($this->badgeid);
if ($this->badge->status == BADGE_STATUS_INACTIVE) {
// Inactive badges that haven't been published previously can't be displayed.
$this->badge = null;
} else {
$this->context = $this->badge->get_context();
}
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output Renderer base.
* @return stdClass
*/
public function export_for_template(renderer_base $output): stdClass {
global $DB, $SITE;
$data = new stdClass();
if ($this->context instanceof context_course) {
$data->coursefullname = format_string($DB->get_field('course', 'fullname', ['id' => $this->badge->courseid]),
true, ['context' => $this->context]);
} else {
$data->sitefullname = format_string($SITE->fullname, true, ['context' => $this->context]);
}
// Field: Image.
$storage = get_file_storage();
$imagefile = $storage->get_file($this->context->id, 'badges', 'badgeimage', $this->badgeid, '/', 'f3.png');
if ($imagefile) {
$imagedata = base64_encode($imagefile->get_content());
} else {
if (defined('PHPUNIT_TEST') && PHPUNIT_TEST) {
// Unit tests the file might not exist yet.
$imagedata = '';
} else {
throw new coding_exception('Image file does not exist.');
}
}
$data->badgeimage = 'data:image/png;base64,' . $imagedata;
// Fields: Name, description.
$data->badgename = $this->badge->name;
$data->badgedescription = $this->badge->description;
// Field: Criteria.
// This method will return the HTML with the badge criteria.
$data->criteria = $output->print_badge_criteria($this->badge);
// Field: Issuer.
$data->issuedby = format_string($this->badge->issuername, true, ['context' => $this->context]);
if (isset($this->badge->issuercontact) && !empty($this->badge->issuercontact)) {
$data->issuedbyemailobfuscated = obfuscate_mailto($this->badge->issuercontact, $data->issuedby);
}
// Fields: Other details, such as language or version.
$data->hasotherfields = false;
if (!empty($this->badge->language)) {
$data->hasotherfields = true;
$languages = get_string_manager()->get_list_of_languages();
$data->language = $languages[$this->badge->language];
}
if (!empty($this->badge->version)) {
$data->hasotherfields = true;
$data->version = $this->badge->version;
}
if (!empty($this->badge->imageauthorname)) {
$data->hasotherfields = true;
$data->imageauthorname = $this->badge->imageauthorname;
}
if (!empty($this->badge->imageauthoremail)) {
$data->hasotherfields = true;
$data->imageauthoremail = obfuscate_mailto($this->badge->imageauthoremail, $this->badge->imageauthoremail);
}
if (!empty($this->badge->imageauthorurl)) {
$data->hasotherfields = true;
$data->imageauthorurl = $this->badge->imageauthorurl;
}
if (!empty($this->badge->imagecaption)) {
$data->hasotherfields = true;
$data->imagecaption = $this->badge->imagecaption;
}
// Field: Endorsement.
$endorsement = $this->badge->get_endorsement();
if (!empty($endorsement)) {
$data->hasotherfields = true;
$endorsement = $this->badge->get_endorsement();
$endorsement->issueremail = obfuscate_mailto($endorsement->issueremail, $endorsement->issueremail);
$data->endorsement = (array) $endorsement;
}
// Field: Related badges.
$relatedbadges = $this->badge->get_related_badges(true);
if (!empty($relatedbadges)) {
$data->hasotherfields = true;
$data->hasrelatedbadges = true;
$data->relatedbadges = [];
foreach ($relatedbadges as $related) {
if (isloggedin() && !is_guest($this->context)) {
$related->url = (new moodle_url('/badges/overview.php', ['id' => $related->id]))->out(false);
}
$data->relatedbadges[] = (array)$related;
}
}
// Field: Alignments.
$alignments = $this->badge->get_alignments();
if (!empty($alignments)) {
$data->hasotherfields = true;
$data->hasalignments = true;
$data->alignments = [];
foreach ($alignments as $alignment) {
$data->alignments[] = (array)$alignment;
}
}
// Field: Tags.
$tags = \core_tag_tag::get_item_tags('core_badges', 'badge', $this->badgeid);
$taglist = new \core_tag\output\taglist($tags);
$data->badgetag = $taglist->export_for_template($output);
return $data;
}
}
+103
View File
@@ -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/>.
namespace core_badges\output;
use renderable;
use renderer_base;
use moodle_page;
use navigation_node;
use templatable;
/**
* Abstract class for the badges tertiary navigation. The class initialises the page and type class variables.
*
* @package core_badges
* @copyright 2021 Peter Dias
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class base_action_bar implements renderable, templatable {
/** @var moodle_page $page The context we are operating within. */
protected $page;
/** @var int $type The badge type. */
protected $type;
/**
* standard_action_bar constructor.
*
* @param moodle_page $page
* @param int $type
*/
public function __construct(moodle_page $page, int $type) {
$this->type = $type;
$this->page = $page;
}
/**
* The template that this tertiary nav should use.
*
* @return string
*/
abstract public function get_template(): string;
/**
* Gets additional third party navigation nodes for display.
*
* @param renderer_base $output The output
* @return array All that sweet third party navigation action.
*/
public function get_third_party_nav_action(renderer_base $output): array {
$badgenode = $this->page->settingsnav->find('coursebadges', navigation_node::TYPE_CONTAINER);
if (!$badgenode) {
return [];
}
$leftovernodes = [];
foreach ($badgenode->children as $key => $value) {
if (array_search($value->key, $this->expected_items()) === false) {
$leftovernodes[] = $value;
}
}
$result = \core\navigation\views\secondary::create_menu_element($leftovernodes);
if ($result == false) {
return [];
} else {
$data ['thirdpartybutton'] = true;
if (count($result) == 1) {
// Return a button.
$link = key($result);
$text = current($result);
$data['thirdpartynodes'] = ['link' => $link, 'text' => $text];
} else {
// Return a url_select.
$selectobject = new \url_select($result, $this->page->url, get_string('othernavigation', 'badges'));
$data['thirdpartynodes'] = $selectobject->export_for_template($output);
$data['thirdpartybutton'] = false;
}
}
return $data;
}
/**
* Expected navigation node keys for badges.
*
* @return array default badge navigation node keys.
*/
protected function expected_items(): array {
return ['coursebadges', 'newbadge'];
}
}
@@ -0,0 +1,87 @@
<?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/>.
/**
* Manage enabled backpacks for the site.
*
* @package core_badges
* @copyright 2019 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_badges\output;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/badgeslib.php');
use core_badges\external\backpack_exporter;
/**
* Manage enabled backpacks renderable.
*
* @package core_badges
* @copyright 2019 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class external_backpacks_page implements \renderable {
/** @var \moodle_url Badges backpacks URL. */
protected $url;
/** @var array List the backpacks at site level. */
protected $backpacks = [];
/**
* Constructor.
* @param \moodle_url $url
*/
public function __construct(\moodle_url $url) {
$this->url = $url;
$this->backpacks = badges_get_site_backpacks();
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output Renderer base.
* @return stdClass
*/
public function export_for_template(\renderer_base $output) {
global $PAGE;
$rownumber = 0;
$rowcount = count($this->backpacks);
$data = new \stdClass();
$data->baseurl = $this->url;
$data->backpacks = array();
$data->sesskey = sesskey();
foreach ($this->backpacks as $backpack) {
$exporter = new backpack_exporter($backpack);
$backpack = $exporter->export($output);
$backpack->cantest = ($backpack->apiversion == OPEN_BADGES_V2);
$backpack->canmoveup = $rownumber > 0;
$backpack->canmovedown = $rownumber < $rowcount - 1;
$data->backpacks[] = $backpack;
$rownumber++;
}
return $data;
}
}
@@ -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/>.
/**
* List of enabled backpacks for the site.
*
* @package core_badges
* @copyright 2019 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_badges\output;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/tablelib.php');
require_once($CFG->libdir . '/badgeslib.php');
use html_writer;
use moodle_url;
use table_sql;
/**
* Backpacks table class.
*
* @package core_badges
* @copyright 2019 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class external_backpacks_table extends table_sql {
/**
* Sets up the table.
*/
public function __construct() {
parent::__construct('backpacks');
$context = \context_system::instance();
// This object should not be used without the right permissions.
require_capability('moodle/badges:manageglobalsettings', $context);
// Define columns in the table.
$this->define_table_columns();
// Define configs.
$this->define_table_configs();
}
/**
* Setup the headers for the table.
*/
protected function define_table_columns() {
$cols = [
'backpackweburl' => get_string('backpackurl', 'core_badges'),
'sortorder' => '',
];
$this->define_columns(array_keys($cols));
$this->define_headers(array_values($cols));
}
/**
* Define table configs.
*/
protected function define_table_configs() {
$this->collapsible(false);
$this->sortable(false);
$this->pageable(false);
}
}
+187
View File
@@ -0,0 +1,187 @@
<?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 badge renderable.
*
* @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>
*/
namespace core_badges\output;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/badgeslib.php');
use renderable;
use renderer_base;
use stdClass;
/**
* An external badges for external.php page
*
* @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
*/
class external_badge implements renderable {
/** @var stdClass Issued badge */
public $issued;
/** @var int User ID */
public $recipient;
/** @var bool Validation of external badge */
public $valid = true;
/**
* Initializes the badge to display
*
* @param stdClass $badge External badge information.
* @param int $recipient User id.
*/
public function __construct($badge, $recipient) {
global $DB;
// At this point a user has connected a backpack. So, we are going to get
// their backpack email rather than their account email.
$userfieldsapi = \core_user\fields::for_name();
$namefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
$user = $DB->get_record_sql("SELECT {$namefields}, b.email
FROM {user} u INNER JOIN {badge_backpack} b ON u.id = b.userid
WHERE b.userid = :userid", array('userid' => $recipient), IGNORE_MISSING);
$this->issued = $badge;
$this->recipient = $user;
// Check if recipient is valid.
// There is no way to be 100% sure that a badge belongs to a user.
// Backpack does not return any recipient information.
// All we can do is compare that backpack email hashed using salt
// provided in the assertion matches a badge recipient from the assertion.
if ($user) {
if (isset($badge->assertion->recipient->identity)) {
$badge->assertion->salt = $badge->assertion->recipient->salt;
$badge->assertion->recipient = $badge->assertion->recipient->identity;
}
// Open Badges V2 does not even include a recipient.
if (!isset($badge->assertion->recipient)) {
$this->valid = false;
} else if (validate_email($badge->assertion->recipient) && $badge->assertion->recipient == $user->email) {
// If we have email, compare emails.
$this->valid = true;
} else if ($badge->assertion->recipient == 'sha256$' . hash('sha256', $user->email)) {
// If recipient is hashed, but no salt, compare hashes without salt.
$this->valid = true;
} else if ($badge->assertion->recipient == 'sha256$' . hash('sha256', $user->email . $badge->assertion->salt)) {
// If recipient is hashed, compare hashes.
$this->valid = true;
} else {
// Otherwise, we cannot be sure that this user is a recipient.
$this->valid = false;
}
} else {
$this->valid = false;
}
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output Renderer base.
* @return stdClass
*/
public function export_for_template(renderer_base $output): stdClass {
$data = new stdClass();
$now = time();
if (isset($this->issued->assertion->expires)) {
if (!is_numeric($this->issued->assertion->expires)) {
$this->issued->assertion->expires = strtotime($this->issued->assertion->expires);
}
$expiration = $this->issued->assertion->expires;
} else {
$expiration = $now + 86400;
}
// Field: Image.
if (isset($this->issued->imageUrl)) {
$this->issued->image = $this->issued->imageUrl;
}
$data->badgeimage = $this->issued->image;
if (is_object($data->badgeimage)) {
if (!empty($data->badgeimage->author)) {
$data->hasotherfields = true;
$data->imageauthorname = $data->badgeimage->author;
}
if (!empty($data->badgeimage->caption)) {
$data->hasotherfields = true;
$data->imagecaption = $data->badgeimage->caption;
}
$data->badgeimage = $data->badgeimage->id;
}
// Field: Expiration date.
if (isset($this->issued->assertion->expires)) {
if ($expiration < $now) {
$data->expireddate = $this->issued->assertion->expires;
$data->expireddateformatted = userdate(
$this->issued->assertion->expires,
get_string('strftimedatetime', 'langconfig')
);
} else {
$data->expiredate = $this->issued->assertion->expires;
}
}
// Fields: Name, description, issuedOn.
$data->badgename = $this->issued->assertion->badge->name;
$data->badgedescription = $this->issued->assertion->badge->description;
if (isset($this->issued->assertion->issued_on)) {
if (!is_numeric($this->issued->assertion->issued_on)) {
$this->issued->assertion->issued_on = strtotime($this->issued->assertion->issued_on);
}
$data->badgeissuedon = $this->issued->assertion->issued_on;
}
// Field: Recipient (the badge was awarded to this person).
$data->recipientname = fullname($this->recipient);
if (!$this->valid) {
$data->recipientnotification = new stdClass();
$data->recipientnotification->message = get_string('recipientvalidationproblem', 'badges');
}
// Field: Criteria.
if (isset($this->issued->assertion->badgeclass->criteria->narrative)) {
$data->criteria = $this->issued->assertion->badgeclass->criteria->narrative;
}
// Field: Issuer.
$data->issuedby = $this->issued->issuer->name;
if (isset($this->issued->issuer->contact) && !empty($this->issued->issuer->contact)) {
$data->issuedbyemailobfuscated = obfuscate_mailto($this->issued->issuer->contact, $data->issuedby);
}
// Field: Hosted URL.
if (isset($this->issued->hostedUrl) && !empty($this->issued->hostedUrl)) {
$data->hostedurl = $this->issued->hostedUrl;
}
return $data;
}
}
+252
View File
@@ -0,0 +1,252 @@
<?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/>.
/**
* Issued badge renderable.
*
* @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>
*/
namespace core_badges\output;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/badgeslib.php');
use context_course;
use context_system;
use stdClass;
use renderable;
use core_badges\badge;
use moodle_url;
use renderer_base;
/**
* An issued badges for badge.php page
*
* @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
*/
class issued_badge implements renderable {
/** @var issued badge */
public $issued;
/** @var badge recipient */
public $recipient;
/** @var badge class */
public $badgeclass;
/** @var badge visibility to others */
public $visible = 0;
/** @var badge class */
public $badgeid = 0;
/** @var unique hash identifying the issued badge */
public $hash;
/**
* Initializes the badge to display
*
* @param string $hash Issued badge hash
*/
public function __construct($hash) {
global $DB;
$this->hash = $hash;
$assertion = new \core_badges_assertion($hash, badges_open_badges_backpack_api());
$this->issued = $assertion->get_badge_assertion();
if (!is_numeric($this->issued['issuedOn'])) {
$this->issued['issuedOn'] = strtotime($this->issued['issuedOn']);
}
$this->badgeclass = $assertion->get_badge_class();
$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) {
// Get a recipient from database.
$userfieldsapi = \core_user\fields::for_name();
$namefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
$user = $DB->get_record_sql("SELECT u.id, $namefields, u.deleted, u.email
FROM {user} u WHERE u.id = :userid", array('userid' => $rec->userid));
$this->recipient = $user;
$this->visible = $rec->visible;
$this->badgeid = $rec->badgeid;
}
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output Renderer base.
* @return stdClass
*/
public function export_for_template(renderer_base $output): stdClass {
global $CFG, $DB, $SITE, $USER;
$now = time();
if (isset($this->issued['expires'])) {
if (!is_numeric($this->issued['expires'])) {
$this->issued['expires'] = strtotime($this->issued['expires']);
}
$expiration = $this->issued['expires'];
} else {
$expiration = $now + 86400;
}
$context = null;
$data = new stdClass();
$badge = new badge($this->badgeid);
if ($badge->type == BADGE_TYPE_COURSE && isset($badge->courseid)) {
$context = context_course::instance($badge->courseid);
$data->coursefullname = format_string($DB->get_field('course', 'fullname', ['id' => $badge->courseid]),
true, ['context' => $context]);
} else {
$context = context_system::instance();
$data->sitefullname = format_string($SITE->fullname, true, ['context' => $context]);
}
// Field: Image.
$data->badgeimage = is_array($this->badgeclass['image']) ? $this->badgeclass['image']['id'] : $this->badgeclass['image'];
// Field: Expiration date.
if (isset($this->issued['expires'])) {
if ($expiration < $now) {
$data->expireddate = $this->issued['expires'];
$data->expireddateformatted = userdate($this->issued['expires'], get_string('strftimedatetime', 'langconfig'));
} else {
$data->expiredate = $this->issued['expires'];
}
}
// Fields: Name, description, issuedOn.
$data->badgename = $badge->name;
$data->badgedescription = $badge->description;
$data->badgeissuedon = $this->issued['issuedOn'];
// Field: Recipient (the badge was awarded to this person).
if ($this->recipient->deleted) {
$strdata = new stdClass();
$strdata->user = fullname($this->recipient);
$strdata->site = format_string($SITE->fullname, true, ['context' => context_system::instance()]);
$data->recipientname = get_string('error:userdeleted', 'badges', $strdata);
} else {
$data->recipientname = fullname($this->recipient);
}
// Field: Criteria.
// This method will return the HTML with the badge criteria.
$data->criteria = $output->print_badge_criteria($badge);
// Field: Issuer.
$data->issuedby = format_string($badge->issuername, true, ['context' => $context]);
if (isset($badge->issuercontact) && !empty($badge->issuercontact)) {
$data->issuedbyemailobfuscated = obfuscate_mailto($badge->issuercontact, $data->issuedby);
}
// Fields: Other details, such as language or version.
$data->hasotherfields = false;
if (!empty($badge->language)) {
$data->hasotherfields = true;
$languages = get_string_manager()->get_list_of_languages();
$data->language = $languages[$badge->language];
}
if (!empty($badge->version)) {
$data->hasotherfields = true;
$data->version = $badge->version;
}
if (!empty($badge->imageauthorname)) {
$data->hasotherfields = true;
$data->imageauthorname = $badge->imageauthorname;
}
if (!empty($badge->imageauthoremail)) {
$data->hasotherfields = true;
$data->imageauthoremail = obfuscate_mailto($badge->imageauthoremail, $badge->imageauthoremail);
}
if (!empty($badge->imageauthorurl)) {
$data->hasotherfields = true;
$data->imageauthorurl = $badge->imageauthorurl;
}
if (!empty($badge->imagecaption)) {
$data->hasotherfields = true;
$data->imagecaption = $badge->imagecaption;
}
// Field: Endorsement.
$endorsement = $badge->get_endorsement();
if (!empty($endorsement)) {
$data->hasotherfields = true;
$endorsement = $badge->get_endorsement();
$endorsement->issueremail = obfuscate_mailto($endorsement->issueremail, $endorsement->issueremail);
$data->endorsement = (array) $endorsement;
}
// Field: Related badges.
$relatedbadges = $badge->get_related_badges(true);
if (!empty($relatedbadges)) {
$data->hasotherfields = true;
$data->hasrelatedbadges = true;
$data->relatedbadges = [];
foreach ($relatedbadges as $related) {
if (isloggedin() && ($context instanceof context_course && !is_guest($context))) {
$related->url = (new moodle_url('/badges/overview.php', ['id' => $related->id]))->out(false);
}
$data->relatedbadges[] = (array)$related;
}
}
// Field: Alignments.
$alignments = $badge->get_alignments();
if (!empty($alignments)) {
$data->hasotherfields = true;
$data->hasalignments = true;
$data->alignments = [];
foreach ($alignments as $alignment) {
$data->alignments[] = (array)$alignment;
}
}
// Buttons to display.
if ($USER->id == $this->recipient->id && !empty($CFG->enablebadges)) {
$data->downloadurl = (new moodle_url('/badges/badge.php', ['hash' => $this->hash, 'bake' => true]))->out(false);
if (!empty($CFG->badges_allowexternalbackpack) && ($expiration > $now)
&& $userbackpack = badges_get_user_backpack($USER->id)) {
if (badges_open_badges_backpack_api($userbackpack->id) == OPEN_BADGES_V2P1) {
$addtobackpackurl = new moodle_url('/badges/backpack-export.php', ['hash' => $this->hash]);
} else {
$addtobackpackurl = new moodle_url('/badges/backpack-add.php', ['hash' => $this->hash]);
}
$data->addtobackpackurl = $addtobackpackurl->out(false);
}
}
// Field: Tags.
$tags = \core_tag_tag::get_item_tags('core_badges', 'badge', $this->badgeid);
$taglist = new \core_tag\output\taglist($tags);
$data->badgetag = $taglist->export_for_template($output);
return $data;
}
}
@@ -0,0 +1,173 @@
<?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/>.
namespace core_badges\output;
use core_badges\badge;
use moodle_url;
use renderer_base;
use single_button;
use moodle_page;
use url_select;
/**
* Class manage_badge_action_bar - Display the action bar
*
* @package core_badges
* @copyright 2021 Peter Dias
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class manage_badge_action_bar extends base_action_bar {
/** @var badge $badge The badge we are managing. */
protected $badge;
/**
* manage_badge_action_bar constructor
*
* @param badge $badge The badge we are viewing
* @param moodle_page $page The page object
*/
public function __construct(badge $badge, moodle_page $page) {
parent::__construct($page, $badge->type);
$this->badge = $badge;
}
/**
* The template that this tertiary nav should use.
*
* @return string
*/
public function get_template(): string {
return 'core_badges/manage_badge';
}
/**
* Export the action bar
*
* @param renderer_base $output
* @return array
*/
public function export_for_template(renderer_base $output): array {
$elements = [];
$params = ['type' => $this->type];
if ($this->page->context->contextlevel == CONTEXT_COURSE) {
$params['id'] = $this->page->context->instanceid;
}
$elements['button'] = new single_button(new moodle_url('/badges/index.php', $params), get_string('back'), 'get');
$elements['urlselect'] = new url_select($this->generate_badge_navigation(), $this->page->url->out(false), null);
foreach ($elements as $key => $element) {
$elements[$key] = $element->export_for_template($output);
}
$additional = $this->get_third_party_nav_action($output);
$elements += $additional ?: [];
return $elements;
}
/**
* Returns a multi dimensional array of the links that should be displayed when creating a badge.
* The keys of the array feed into the text shown to the user and content of each element contain the following:
* - url URL for the option
* - additionalparams Additional params to feed into the url
* - capability The capabilities to check that governs visibility
* @return array
*/
protected function get_badge_administration_mapping_construct(): array {
return [
'boverview' => [
'url' => '/badges/overview.php',
'capability' => ''
],
'bdetails' => [
'url' => '/badges/edit.php',
'additionalparams' => ['action' => 'badge'],
'capability' => 'moodle/badges:configuredetails'
],
'bcriteria' => [
'url' => '/badges/criteria.php',
'capability' => 'moodle/badges:configurecriteria'
],
'bmessage' => [
'url' => '/badges/edit.php',
'additionalparams' => ['action' => 'message'],
'capability' => 'moodle/badges:configuremessages'
],
'bawards' => [
'url' => '/badges/recipients.php',
'capability' => 'moodle/badges:viewawarded'
],
'bendorsement' => [
'url' => '/badges/endorsement.php',
'capability' => 'moodle/badges:configuredetails'
],
'brelated' => [
'url' => '/badges/related.php',
'capability' => 'moodle/badges:configuredetails'
],
'balignment' => [
'url' => '/badges/alignment.php',
'capability' => 'moodle/badges:configuredetails'
],
];
}
/**
* Generate the options to be displayed when editing a badge. This feeds into a URL select which will be displayed
* in the tertiary navigation.
*
* @return array
*/
protected function generate_badge_navigation(): array {
global $DB;
$params = ['id' => $this->badge->id];
$options = [];
$construct = $this->get_badge_administration_mapping_construct();
foreach ($construct as $stringidentifier => $checks) {
if ($checks['capability'] && !has_capability($checks['capability'], $this->page->context)) {
continue;
}
$sql = '';
switch ($stringidentifier) {
case 'bawards':
$sql = "SELECT COUNT(b.userid)
FROM {badge_issued} b
INNER JOIN {user} u ON b.userid = u.id
WHERE b.badgeid = :badgeid AND u.deleted = 0";
break;
case 'brelated':
$sql = "SELECT COUNT(br.badgeid)
FROM {badge_related} br
WHERE (br.badgeid = :badgeid OR br.relatedbadgeid = :badgeid2)";
break;
case 'balignment':
$sql = "SELECT COUNT(bc.id)
FROM {badge_alignment} bc
WHERE bc.badgeid = :badgeid";
break;
}
$content = null;
if ($sql) {
$content = $DB->count_records_sql($sql, ['badgeid' => $this->badge->id, 'badgeid2' => $this->badge->id]);
}
$url = new moodle_url($checks['url'], $params + ($checks['additionalparams'] ?? []));
$options[get_string($stringidentifier, 'core_badges', $content)] = $url->out(false);
}
return array_flip($options);
}
}
@@ -0,0 +1,61 @@
<?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/>.
namespace core_badges\output;
use moodle_url;
use renderer_base;
use single_button;
/**
* Class recipients_action_bar - Display the action bar
*
* @package core_badges
* @copyright 2021 Peter Dias
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class recipients_action_bar extends manage_badge_action_bar {
/**
* The template that this tertiary nav should use.
*
* @return string
*/
public function get_template(): string {
return 'core_badges/award_badge';
}
/**
* Export the action bar
*
* @param renderer_base $output
* @return array
*/
public function export_for_template(renderer_base $output): array {
$elements = parent::export_for_template($output);
// Add button for badge manual award.
if ($this->badge->has_manual_award_criteria()
&& has_capability('moodle/badges:awardbadge', $this->page->context) && $this->badge->is_active()) {
$url = new moodle_url('/badges/award.php', ['id' => $this->badge->id]);
$button = new single_button($url, get_string('award', 'badges'), 'post', single_button::BUTTON_PRIMARY);
$elements['awardbutton'] = $button->export_for_template($output);
}
$thirdpartynav = $this->get_third_party_nav_action($output);
$elements += $thirdpartynav ?: [];
return $elements;
}
}
@@ -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/>.
namespace core_badges\output;
use moodle_page;
use moodle_url;
use renderer_base;
use single_button;
/**
* Class standard_action_bar - Display the action bar
*
* @package core_badges
* @copyright 2021 Peter Dias
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class standard_action_bar extends base_action_bar {
/** @var bool $showmanage Whether or not to show the manage badges button. */
protected $showmanage;
/** @var bool $showaddbadge Whether or not to show the add badges button. */
protected $showaddbadge;
/** @var moodle_url $backurl BackURL to be used when the back button is required. */
protected $backurl;
/**
* standard_action_bar constructor
*
* @param moodle_page $page The page object
* @param int $type The type of badge we are operating with
* @param bool $showmanage Whether or not to show the manage badges button
* @param bool $showaddbadge Whether or not to show the add badges button
* @param moodle_url|null $backurl The backurl to be used
*/
public function __construct(moodle_page $page, int $type, bool $showmanage = true,
$showaddbadge = true, ?moodle_url $backurl = null) {
parent::__construct($page, $type);
$this->showmanage = $showmanage;
$this->showaddbadge = $showaddbadge;
$this->backurl = $backurl;
}
/**
* The template that this tertiary nav should use.
*
* @return string
*/
public function get_template(): string {
return 'core_badges/manage_badges';
}
/**
* Export the action bar
*
* @param renderer_base $output
* @return array The buttons to be rendered
*/
public function export_for_template(renderer_base $output): array {
$buttons = [];
if ($this->backurl) {
$buttons[] = new single_button($this->backurl, get_string('back'), 'get');
}
$params = ['type' => $this->type];
if ($this->page->context->contextlevel == CONTEXT_COURSE) {
$params['id'] = $this->page->context->instanceid;
}
if ($this->showmanage) {
$buttons[] = new single_button(new moodle_url('/badges/index.php', $params),
get_string('managebadges', 'core_badges'), 'get');
}
if ($this->showaddbadge && has_capability('moodle/badges:createbadge', $this->page->context)) {
$buttons[] = new single_button(new moodle_url('/badges/newbadge.php', $params),
get_string('newbadge', 'core_badges'), 'post', single_button::BUTTON_PRIMARY);
}
foreach ($buttons as $key => $button) {
$buttons[$key] = $button->export_for_template($output);
}
$data = ['buttons' => $buttons];
$additional = $this->get_third_party_nav_action($output);
$data += $additional ?: [];
return $data;
}
}
+700
View File
@@ -0,0 +1,700 @@
<?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/>.
/**
* Data provider.
*
* @package core_badges
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_badges\privacy;
defined('MOODLE_INTERNAL') || die();
use badge;
use context;
use context_course;
use context_helper;
use context_system;
use context_user;
use core_text;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\transform;
use core_privacy\local\request\writer;
use core_privacy\local\request\userlist;
use core_privacy\local\request\approved_userlist;
require_once($CFG->libdir . '/badgeslib.php');
/**
* Data provider class.
*
* @package core_badges
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\core_userlist_provider,
\core_privacy\local\request\subsystem\provider {
/**
* Returns metadata.
*
* @param collection $collection The initialised collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $collection): collection {
$collection->add_database_table('badge', [
'usercreated' => 'privacy:metadata:badge:usercreated',
'usermodified' => 'privacy:metadata:badge:usermodified',
'timecreated' => 'privacy:metadata:badge:timecreated',
'timemodified' => 'privacy:metadata:badge:timemodified',
], 'privacy:metadata:badge');
$collection->add_database_table('badge_issued', [
'userid' => 'privacy:metadata:issued:userid',
'dateissued' => 'privacy:metadata:issued:dateissued',
'dateexpire' => 'privacy:metadata:issued:dateexpire',
], 'privacy:metadata:issued');
$collection->add_database_table('badge_criteria_met', [
'userid' => 'privacy:metadata:criteriamet:userid',
'datemet' => 'privacy:metadata:criteriamet:datemet',
], 'privacy:metadata:criteriamet');
$collection->add_database_table('badge_manual_award', [
'recipientid' => 'privacy:metadata:manualaward:recipientid',
'issuerid' => 'privacy:metadata:manualaward:issuerid',
'issuerrole' => 'privacy:metadata:manualaward:issuerrole',
'datemet' => 'privacy:metadata:manualaward:datemet',
], 'privacy:metadata:manualaward');
$collection->add_database_table('badge_backpack', [
'userid' => 'privacy:metadata:backpack:userid',
'email' => 'privacy:metadata:backpack:email',
'externalbackpackid' => 'privacy:metadata:backpack:externalbackpackid',
'backpackuid' => 'privacy:metadata:backpack:backpackuid',
// The columns autosync and password are not used.
], 'privacy:metadata:backpack');
$collection->add_external_location_link('backpacks', [
'name' => 'privacy:metadata:external:backpacks:badge',
'description' => 'privacy:metadata:external:backpacks:description',
'image' => 'privacy:metadata:external:backpacks:image',
'url' => 'privacy:metadata:external:backpacks:url',
'issuer' => 'privacy:metadata:external:backpacks:issuer',
], 'privacy:metadata:external:backpacks');
$collection->add_database_table('badge_backpack_oauth2', [
'userid' => 'privacy:metadata:backpackoauth2:userid',
'usermodified' => 'privacy:metadata:backpackoauth2:usermodified',
'token' => 'privacy:metadata:backpackoauth2:token',
'issuerid' => 'privacy:metadata:backpackoauth2:issuerid',
'scope' => 'privacy:metadata:backpackoauth2:scope',
], 'privacy:metadata:backpackoauth2');
return $collection;
}
/**
* Get the list of contexts that contain user information for the specified user.
*
* @param int $userid The user to search.
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
*/
public static function get_contexts_for_userid(int $userid): \core_privacy\local\request\contextlist {
$contextlist = new \core_privacy\local\request\contextlist();
// Find the modifications we made on badges (course & system).
$sql = "
SELECT ctx.id
FROM {badge} b
JOIN {context} ctx
ON (b.type = :typecourse AND b.courseid = ctx.instanceid AND ctx.contextlevel = :courselevel)
OR (b.type = :typesite AND ctx.id = :syscontextid)
WHERE b.usermodified = :userid1
OR b.usercreated = :userid2";
$params = [
'courselevel' => CONTEXT_COURSE,
'syscontextid' => SYSCONTEXTID,
'typecourse' => BADGE_TYPE_COURSE,
'typesite' => BADGE_TYPE_SITE,
'userid1' => $userid,
'userid2' => $userid,
];
$contextlist->add_from_sql($sql, $params);
// Find where we've manually awarded a badge (recipient user context).
$sql = "
SELECT ctx.id
FROM {badge_manual_award} bma
JOIN {context} ctx
ON ctx.instanceid = bma.recipientid
AND ctx.contextlevel = :userlevel
WHERE bma.issuerid = :userid";
$params = [
'userlevel' => CONTEXT_USER,
'userid' => $userid,
];
$contextlist->add_from_sql($sql, $params);
// Now find where there is real user data (user context).
$sql = "
SELECT ctx.id
FROM {context} ctx
LEFT JOIN {badge_manual_award} bma
ON bma.recipientid = ctx.instanceid
LEFT JOIN {badge_issued} bi
ON bi.userid = ctx.instanceid
LEFT JOIN {badge_criteria_met} bcm
ON bcm.userid = ctx.instanceid
LEFT JOIN {badge_backpack} bb
ON bb.userid = ctx.instanceid
WHERE ctx.contextlevel = :userlevel
AND ctx.instanceid = :userid
AND (bma.id IS NOT NULL
OR bi.id IS NOT NULL
OR bcm.id IS NOT NULL
OR bb.id IS NOT NULL)";
$params = [
'userlevel' => CONTEXT_USER,
'userid' => $userid,
];
$contextlist->add_from_sql($sql, $params);
return $contextlist;
}
/**
* Get the list of users within a specific context.
*
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
*/
public static function get_users_in_context(userlist $userlist) {
$context = $userlist->get_context();
$allowedcontexts = [
CONTEXT_COURSE,
CONTEXT_SYSTEM,
CONTEXT_USER
];
if (!in_array($context->contextlevel, $allowedcontexts)) {
return;
}
if ($context->contextlevel == CONTEXT_COURSE || $context->contextlevel == CONTEXT_SYSTEM) {
// Find the modifications we made on badges (course & system).
if ($context->contextlevel == CONTEXT_COURSE) {
$extrawhere = 'AND b.courseid = :courseid';
$params = [
'badgetype' => BADGE_TYPE_COURSE,
'courseid' => $context->instanceid
];
} else {
$extrawhere = '';
$params = ['badgetype' => BADGE_TYPE_SITE];
}
$sql = "SELECT b.usermodified, b.usercreated
FROM {badge} b
WHERE b.type = :badgetype
$extrawhere";
$userlist->add_from_sql('usermodified', $sql, $params);
$userlist->add_from_sql('usercreated', $sql, $params);
}
if ($context->contextlevel == CONTEXT_USER) {
// Find where we've manually awarded a badge (recipient user context).
$params = [
'instanceid' => $context->instanceid
];
$sql = "SELECT issuerid, recipientid
FROM {badge_manual_award}
WHERE recipientid = :instanceid";
$userlist->add_from_sql('issuerid', $sql, $params);
$userlist->add_from_sql('recipientid', $sql, $params);
$sql = "SELECT userid
FROM {badge_issued}
WHERE userid = :instanceid";
$userlist->add_from_sql('userid', $sql, $params);
$sql = "SELECT userid
FROM {badge_criteria_met}
WHERE userid = :instanceid";
$userlist->add_from_sql('userid', $sql, $params);
$sql = "SELECT userid
FROM {badge_backpack}
WHERE userid = :instanceid";
$userlist->add_from_sql('userid', $sql, $params);
}
}
/**
* Export all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
public static function export_user_data(approved_contextlist $contextlist) {
global $DB;
$userid = $contextlist->get_user()->id;
$contexts = array_reduce($contextlist->get_contexts(), function($carry, $context) {
$level = $context->contextlevel;
if ($level == CONTEXT_USER || $level == CONTEXT_COURSE) {
$carry[$level][] = $context->instanceid;
} else if ($level == CONTEXT_SYSTEM) {
$carry[$level] = SYSCONTEXTID;
}
return $carry;
}, [
CONTEXT_COURSE => [],
CONTEXT_USER => [],
CONTEXT_SYSTEM => null,
]);
$path = [get_string('badges', 'core_badges')];
$ctxfields = context_helper::get_preload_record_columns_sql('ctx');
// Export the badges we've created or modified.
if (!empty($contexts[CONTEXT_SYSTEM]) || !empty($contexts[CONTEXT_COURSE])) {
$sqls = [];
$params = [];
if (!empty($contexts[CONTEXT_SYSTEM])) {
$sqls[] = "b.type = :typesite";
$params['typesite'] = BADGE_TYPE_SITE;
}
if (!empty($contexts[CONTEXT_COURSE])) {
list($insql, $inparams) = $DB->get_in_or_equal($contexts[CONTEXT_COURSE], SQL_PARAMS_NAMED);
$sqls[] = "(b.type = :typecourse AND b.courseid $insql)";
$params = array_merge($params, ['typecourse' => BADGE_TYPE_COURSE], $inparams);
}
$sqlwhere = '(' . implode(' OR ', $sqls) . ')';
$sql = "
SELECT b.*, COALESCE(b.courseid, 0) AS normalisedcourseid
FROM {badge} b
WHERE (b.usermodified = :userid1 OR b.usercreated = :userid2)
AND $sqlwhere
ORDER BY b.courseid, b.id";
$params = array_merge($params, ['userid1' => $userid, 'userid2' => $userid]);
$recordset = $DB->get_recordset_sql($sql, $params);
static::recordset_loop_and_export($recordset, 'normalisedcourseid', [], function($carry, $record) use ($userid) {
$carry[] = [
'name' => $record->name,
'created_on' => transform::datetime($record->timecreated),
'created_by_you' => transform::yesno($record->usercreated == $userid),
'modified_on' => transform::datetime($record->timemodified),
'modified_by_you' => transform::yesno($record->usermodified == $userid),
];
return $carry;
}, function($courseid, $data) use ($path) {
$context = $courseid ? context_course::instance($courseid) : context_system::instance();
writer::with_context($context)->export_data($path, (object) ['badges' => $data]);
});
}
// Export the badges we've manually awarded.
if (!empty($contexts[CONTEXT_USER])) {
list($insql, $inparams) = $DB->get_in_or_equal($contexts[CONTEXT_USER], SQL_PARAMS_NAMED);
$sql = "
SELECT bma.id, bma.recipientid, bma.datemet, b.name, b.courseid,
r.id AS roleid,
r.name AS rolename,
r.shortname AS roleshortname,
r.archetype AS rolearchetype,
$ctxfields
FROM {badge_manual_award} bma
JOIN {badge} b
ON b.id = bma.badgeid
JOIN {role} r
ON r.id = bma.issuerrole
JOIN {context} ctx
ON (COALESCE(b.courseid, 0) > 0 AND ctx.instanceid = b.courseid AND ctx.contextlevel = :courselevel)
OR (COALESCE(b.courseid, 0) = 0 AND ctx.id = :syscontextid)
WHERE bma.recipientid $insql
AND bma.issuerid = :userid
ORDER BY bma.recipientid, bma.id";
$params = array_merge($inparams, [
'courselevel' => CONTEXT_COURSE,
'syscontextid' => SYSCONTEXTID,
'userid' => $userid
]);
$recordset = $DB->get_recordset_sql($sql, $params);
static::recordset_loop_and_export($recordset, 'recipientid', [], function($carry, $record) use ($userid) {
// The only reason we fetch the context and role is to format the name of the role, which could be
// different to the standard name if the badge was created in a course.
context_helper::preload_from_record($record);
$context = $record->courseid ? context_course::instance($record->courseid) : context_system::instance();
$role = (object) [
'id' => $record->roleid,
'name' => $record->rolename,
'shortname' => $record->roleshortname,
'archetype' => $record->rolearchetype,
// Mock those two fields as they do not matter.
'sortorder' => 0,
'description' => ''
];
$carry[] = [
'name' => $record->name,
'issued_by_you' => transform::yesno(true),
'issued_on' => transform::datetime($record->datemet),
'issuer_role' => role_get_name($role, $context),
];
return $carry;
}, function($userid, $data) use ($path) {
$context = context_user::instance($userid);
writer::with_context($context)->export_related_data($path, 'manual_awards', (object) ['badges' => $data]);
});
}
// Export our data.
if (in_array($userid, $contexts[CONTEXT_USER])) {
// Export the badges.
$uniqueid = $DB->sql_concat_join("'-'", ['b.id', 'COALESCE(bc.id, 0)', 'COALESCE(bi.id, 0)',
'COALESCE(bma.id, 0)', 'COALESCE(bcm.id, 0)', 'COALESCE(brb.id, 0)', 'COALESCE(ba.id, 0)']);
$sql = "
SELECT $uniqueid AS uniqueid, b.id,
bi.id AS biid, bi.dateissued, bi.dateexpire, bi.uniquehash,
bma.id AS bmaid, bma.datemet, bma.issuerid,
bcm.id AS bcmid,
c.fullname AS coursename,
be.id AS beid,
be.issuername AS beissuername,
be.issuerurl AS beissuerurl,
be.issueremail AS beissueremail,
be.claimid AS beclaimid,
be.claimcomment AS beclaimcomment,
be.dateissued AS bedateissued,
brb.id as rbid,
brb.badgeid as rbbadgeid,
brb.relatedbadgeid as rbrelatedbadgeid,
ba.id as baid,
ba.targetname as batargetname,
ba.targeturl as batargeturl,
ba.targetdescription as batargetdescription,
ba.targetframework as batargetframework,
ba.targetcode as batargetcode,
$ctxfields
FROM {badge} b
LEFT JOIN {badge_issued} bi
ON bi.badgeid = b.id
AND bi.userid = :userid1
LEFT JOIN {badge_related} brb
ON ( b.id = brb.badgeid OR b.id = brb.relatedbadgeid )
LEFT JOIN {badge_alignment} ba
ON ( b.id = ba.badgeid )
LEFT JOIN {badge_endorsement} be
ON be.badgeid = b.id
LEFT JOIN {badge_manual_award} bma
ON bma.badgeid = b.id
AND bma.recipientid = :userid2
LEFT JOIN {badge_criteria} bc
ON bc.badgeid = b.id
LEFT JOIN {badge_criteria_met} bcm
ON bcm.critid = bc.id
AND bcm.userid = :userid3
LEFT JOIN {course} c
ON c.id = b.courseid
AND b.type = :typecourse
LEFT JOIN {context} ctx
ON ctx.instanceid = c.id
AND ctx.contextlevel = :courselevel
WHERE bi.id IS NOT NULL
OR bma.id IS NOT NULL
OR bcm.id IS NOT NULL
ORDER BY b.id";
$params = [
'userid1' => $userid,
'userid2' => $userid,
'userid3' => $userid,
'courselevel' => CONTEXT_COURSE,
'typecourse' => BADGE_TYPE_COURSE,
];
$recordset = $DB->get_recordset_sql($sql, $params);
static::recordset_loop_and_export($recordset, 'id', null, function($carry, $record) use ($userid) {
$badge = new badge($record->id);
// Export details of the badge.
if ($carry === null) {
$carry = [
'name' => $badge->name,
'version' => $badge->version,
'language' => $badge->language,
'imageauthorname' => $badge->imageauthorname,
'imageauthoremail' => $badge->imageauthoremail,
'imageauthorurl' => $badge->imageauthorurl,
'imagecaption' => $badge->imagecaption,
'issued' => null,
'manual_award' => null,
'criteria_met' => [],
'endorsement' => null,
];
if ($badge->type == BADGE_TYPE_COURSE) {
context_helper::preload_from_record($record);
$carry['course'] = format_string($record->coursename, true, ['context' => $badge->get_context()]);
}
if (!empty($record->beid)) {
$carry['endorsement'] = [
'issuername' => $record->beissuername,
'issuerurl' => $record->beissuerurl,
'issueremail' => $record->beissueremail,
'claimid' => $record->beclaimid,
'claimcomment' => $record->beclaimcomment,
'dateissued' => $record->bedateissued ? transform::datetime($record->bedateissued) : null
];
}
if (!empty($record->biid)) {
$carry['issued'] = [
'issued_on' => transform::datetime($record->dateissued),
'expires_on' => $record->dateexpire ? transform::datetime($record->dateexpire) : null,
'unique_hash' => $record->uniquehash,
];
}
if (!empty($record->bmaid)) {
$carry['manual_award'] = [
'awarded_on' => transform::datetime($record->datemet),
'issuer' => transform::user($record->issuerid)
];
}
}
if (!empty($record->rbid)) {
if (empty($carry['related_badge'])) {
$carry['related_badge'] = [];
}
$rbid = $record->rbbadgeid;
if ($rbid == $record->id) {
$rbid = $record->rbrelatedbadgeid;
}
$exists = false;
foreach ($carry['related_badge'] as $related) {
if ($related['badgeid'] == $rbid) {
$exists = true;
break;
}
}
if (!$exists) {
$relatedbadge = new badge($rbid);
$carry['related_badge'][] = [
'badgeid' => $rbid,
'badgename' => $relatedbadge->name
];
}
}
if (!empty($record->baid)) {
if (empty($carry['alignment'])) {
$carry['alignment'] = [];
}
$exists = false;
$newalignment = [
'targetname' => $record->batargetname,
'targeturl' => $record->batargeturl,
'targetdescription' => $record->batargetdescription,
'targetframework' => $record->batargetframework,
'targetcode' => $record->batargetcode,
];
foreach ($carry['alignment'] as $alignment) {
if ($alignment == $newalignment) {
$exists = true;
break;
}
}
if (!$exists) {
$carry['alignment'][] = $newalignment;
}
}
// Export the details of the criteria met.
// We only do that once, when we find that a least one criteria was met.
// This is heavily based on the logic present in core_badges_renderer::render_issued_badge.
if (!empty($record->bcmid) && empty($carry['criteria_met'])) {
$agg = $badge->get_aggregation_methods();
$evidenceids = array_map(function($record) {
return $record->critid;
}, $badge->get_criteria_completions($userid));
$criteria = $badge->criteria;
unset($criteria[BADGE_CRITERIA_TYPE_OVERALL]);
$items = [];
foreach ($criteria as $type => $c) {
if (in_array($c->id, $evidenceids)) {
$details = $c->get_details(true);
if (count($c->params) == 1) {
$items[] = get_string('criteria_descr_single_' . $type , 'core_badges') . ' ' . $details;
} else {
$items[] = get_string('criteria_descr_' . $type , 'core_badges',
core_text::strtoupper($agg[$badge->get_aggregation_method($type)])) . ' ' . $details;
}
}
}
$carry['criteria_met'] = $items;
}
return $carry;
}, function($badgeid, $data) use ($path, $userid) {
$path = array_merge($path, ["{$data['name']} ({$badgeid})"]);
$writer = writer::with_context(context_user::instance($userid));
$writer->export_data($path, (object) $data);
$writer->export_area_files($path, 'badges', 'userbadge', $badgeid);
});
// Export the backpacks.
$data = [];
$recordset = $DB->get_recordset_select('badge_backpack', 'userid = :userid', ['userid' => $userid]);
foreach ($recordset as $record) {
$data[] = [
'email' => $record->email,
'externalbackpackid' => $record->externalbackpackid,
'uid' => $record->backpackuid
];
}
$recordset->close();
if (!empty($data)) {
writer::with_context(context_user::instance($userid))->export_related_data($path, 'backpacks',
(object) ['backpacks' => $data]);
}
}
}
/**
* Delete all data for all users in the specified context.
*
* @param context $context The specific context to delete data for.
*/
public static function delete_data_for_all_users_in_context(context $context) {
// We cannot delete the course or system data as it is needed by the system.
if ($context->contextlevel != CONTEXT_USER) {
return;
}
// Delete all the user data.
static::delete_user_data($context->instanceid);
}
/**
* Delete multiple users within a single context.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*/
public static function delete_data_for_users(approved_userlist $userlist) {
$context = $userlist->get_context();
if (!in_array($context->instanceid, $userlist->get_userids())) {
return;
}
if ($context->contextlevel == CONTEXT_USER) {
// We can only delete our own data in the user context, nothing in course or system.
static::delete_user_data($context->instanceid);
}
}
/**
* Delete all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
$userid = $contextlist->get_user()->id;
foreach ($contextlist->get_contexts() as $context) {
if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $userid) {
// We can only delete our own data in the user context, nothing in course or system.
static::delete_user_data($userid);
break;
}
}
}
/**
* Delete all the data for a user.
*
* @param int $userid The user ID.
* @return void
*/
protected static function delete_user_data($userid) {
global $DB;
// Delete the stuff.
$DB->delete_records('badge_manual_award', ['recipientid' => $userid]);
$DB->delete_records('badge_criteria_met', ['userid' => $userid]);
$DB->delete_records('badge_issued', ['userid' => $userid]);
// Delete the backpacks and related stuff.
$backpackids = $DB->get_fieldset_select('badge_backpack', 'id', 'userid = :userid', ['userid' => $userid]);
if (!empty($backpackids)) {
list($insql, $inparams) = $DB->get_in_or_equal($backpackids, SQL_PARAMS_NAMED);
$DB->delete_records_select('badge_external', "backpackid $insql", $inparams);
$DB->delete_records_select('badge_backpack', "id $insql", $inparams);
}
}
/**
* Loop and export from a recordset.
*
* @param \moodle_recordset $recordset The recordset.
* @param string $splitkey The record key to determine when to export.
* @param mixed $initial The initial data to reduce from.
* @param callable $reducer The function to return the dataset, receives current dataset, and the current record.
* @param callable $export The function to export the dataset, receives the last value from $splitkey and the dataset.
* @return void
*/
protected static function recordset_loop_and_export(\moodle_recordset $recordset, $splitkey, $initial,
callable $reducer, callable $export) {
$data = $initial;
$lastid = null;
foreach ($recordset as $record) {
if ($lastid !== null && $record->{$splitkey} != $lastid) {
$export($lastid, $data);
$data = $initial;
}
$data = $reducer($data, $record);
$lastid = $record->{$splitkey};
}
$recordset->close();
if ($lastid !== null) {
$export($lastid, $data);
}
}
}
@@ -0,0 +1,154 @@
<?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/>.
declare(strict_types=1);
namespace core_badges\reportbuilder\datasource;
use lang_string;
use core_reportbuilder\datasource;
use core_reportbuilder\local\entities\{course, user};
use core_badges\reportbuilder\local\entities\{badge, badge_issued};
use core_tag\reportbuilder\local\entities\tag;
/**
* Badges datasource
*
* @package core_badges
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class badges extends datasource {
/**
* Return user friendly name of the report source
*
* @return string
*/
public static function get_name(): string {
return get_string('badges', 'core_badges');
}
/**
* Initialise report
*/
protected function initialise(): void {
$badgeentity = new badge();
$badgealias = $badgeentity->get_table_alias('badge');
$this->set_main_table('badge', $badgealias);
$this->add_entity($badgeentity);
// Join the tag entity.
$tagentity = (new tag())
->set_table_alias('tag', $badgeentity->get_table_alias('tag'))
->set_entity_title(new lang_string('badgetags', 'core_badges'));
$this->add_entity($tagentity
->add_joins($badgeentity->get_tag_joins()));
// Join the badge issued entity to the badge entity.
$badgeissuedentity = new badge_issued();
$badgeissuedalias = $badgeissuedentity->get_table_alias('badge_issued');
$this->add_entity($badgeissuedentity
->add_join("LEFT JOIN {badge_issued} {$badgeissuedalias}
ON {$badgeissuedalias}.badgeid = {$badgealias}.id")
);
// Join the user entity to the badge issued entity.
$userentity = new user();
$useralias = $userentity->get_table_alias('user');
$this->add_entity($userentity
->add_joins($badgeissuedentity->get_joins())
->add_join("LEFT JOIN {user} {$useralias}
ON {$useralias}.id = {$badgeissuedalias}.userid")
->set_entity_title(new lang_string('recipient', 'core_badges'))
);
// Join the course entity to the badge entity, coalescing courseid with the siteid for site badges.
$courseentity = new course();
$coursealias = $courseentity->get_table_alias('course');
$this->add_entity($courseentity
->add_join("LEFT JOIN {course} {$coursealias}
ON {$coursealias}.id = COALESCE({$badgealias}.courseid, 1)")
);
// Add report elements from each of the entities we added to the report.
$this->add_all_from_entity($badgeentity->get_entity_name());
// Add specific tag entity elements.
$this->add_columns_from_entity($tagentity->get_entity_name(), ['name', 'namewithlink']);
$this->add_filter($tagentity->get_filter('name'));
$this->add_condition($tagentity->get_condition('name'));
$this->add_all_from_entity($badgeissuedentity->get_entity_name());
$this->add_all_from_entity($userentity->get_entity_name());
$this->add_all_from_entity($courseentity->get_entity_name());
}
/**
* Return the columns that will be added to the report upon creation
*
* @return string[]
*/
public function get_default_columns(): array {
return [
'badge:name',
'badge:description',
'user:fullname',
'badge_issued:issued',
];
}
/**
* Return the filters that will be added to the report upon creation
*
* @return string[]
*/
public function get_default_filters(): array {
return [
'badge:name',
'user:fullname',
'badge_issued:issued',
];
}
/**
* Return the conditions that will be added to the report upon creation
*
* @return string[]
*/
public function get_default_conditions(): array {
return [
'badge:type',
'badge:name',
];
}
/**
* Return the default sorting that will be added to the report once it is created
*
* @return array|int[]
*/
public function get_default_column_sorting(): array {
return [
'badge:name' => SORT_ASC,
'user:fullname' => SORT_ASC,
'badge_issued:issued' => SORT_ASC,
];
}
}
@@ -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/>.
declare(strict_types=1);
namespace core_badges\reportbuilder\datasource;
use lang_string;
use core_reportbuilder\datasource;
use core_reportbuilder\local\entities\{course, user};
use core_reportbuilder\local\helpers\database;
use core_badges\reportbuilder\local\entities\{badge, badge_issued};
use core_cohort\reportbuilder\local\entities\cohort;
use core_tag\reportbuilder\local\entities\tag;
/**
* User badges datasource
*
* @package core_badges
* @copyright 2023 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class users extends datasource {
/**
* Return user friendly name of the report source
*
* @return string
*/
public static function get_name(): string {
return get_string('userbadges', 'core_badges');
}
/**
* Initialise report
*/
protected function initialise(): void {
global $CFG;
$userentity = new user();
$useralias = $userentity->get_table_alias('user');
$this->set_main_table('user', $useralias);
$this->add_entity($userentity);
$paramguest = database::generate_param_name();
$this->add_base_condition_sql("{$useralias}.id != :{$paramguest} AND {$useralias}.deleted = 0", [
$paramguest => $CFG->siteguest,
]);
// Join the badge issued entity to the user entity.
$badgeissuedentity = new badge_issued();
$badgeissuedalias = $badgeissuedentity->get_table_alias('badge_issued');
$this->add_entity($badgeissuedentity
->add_join("LEFT JOIN {badge_issued} {$badgeissuedalias} ON {$badgeissuedalias}.userid = {$useralias}.id"));
$badgeentity = new badge();
$badgealias = $badgeentity->get_table_alias('badge');
$this->add_entity($badgeentity
->add_joins($badgeissuedentity->get_joins())
->add_join("LEFT JOIN {badge} {$badgealias} ON {$badgealias}.id = {$badgeissuedalias}.badgeid"));
// Join the tag entity.
$tagentity = (new tag())
->set_table_alias('tag', $badgeentity->get_table_alias('tag'))
->set_entity_title(new lang_string('badgetags', 'core_badges'));
$this->add_entity($tagentity
->add_joins($badgeentity->get_joins())
->add_joins($badgeentity->get_tag_joins()));
// Join the course entity to the badge entity, coalescing courseid with the siteid for site badges.
$courseentity = new course();
$coursealias = $courseentity->get_table_alias('course');
$this->add_entity($courseentity
->add_joins($badgeentity->get_joins())
->add_join("LEFT JOIN {course} {$coursealias} ON {$coursealias}.id =
CASE WHEN {$badgealias}.id IS NULL THEN 0 ELSE COALESCE({$badgealias}.courseid, 1) END"));
// Join the cohort entity.
$cohortentity = new cohort();
$cohortalias = $cohortentity->get_table_alias('cohort');
$cohortmemberalias = database::generate_alias();
$this->add_entity($cohortentity->add_joins([
"LEFT JOIN {cohort_members} {$cohortmemberalias} ON {$cohortmemberalias}.userid = {$useralias}.id",
"LEFT JOIN {cohort} {$cohortalias} ON {$cohortalias}.id = {$cohortmemberalias}.cohortid",
]));
// Add report elements from each of the entities we added to the report.
$this->add_all_from_entity($userentity->get_entity_name());
$this->add_all_from_entity($badgeissuedentity->get_entity_name());
$this->add_all_from_entity($badgeentity->get_entity_name());
$this->add_all_from_entity($tagentity->get_entity_name(), ['name', 'namewithlink'], ['name'], ['name']);
$this->add_all_from_entity($courseentity->get_entity_name());
$this->add_all_from_entity($cohortentity->get_entity_name(), ['name', 'idnumber', 'description', 'customfield*'],
['cohortselect', 'name', 'idnumber', 'customfield*'], ['cohortselect', 'name', 'idnumber', 'customfield*']);
}
/**
* Return the columns that will be added to the report upon creation
*
* @return string[]
*/
public function get_default_columns(): array {
return [
'user:fullname',
'badge:name',
'badge:description',
'badge_issued:issued',
];
}
/**
* Return the column sorting that will be added to the report upon creation
*
* @return int[]
*/
public function get_default_column_sorting(): array {
return [
'user:fullname' => SORT_ASC,
'badge:name' => SORT_ASC,
'badge_issued:issued' => SORT_ASC,
];
}
/**
* Return the filters that will be added to the report upon creation
*
* @return string[]
*/
public function get_default_filters(): array {
return [
'user:fullname',
'badge:name',
'badge_issued:issued',
];
}
/**
* Return the conditions that will be added to the report upon creation
*
* @return string[]
*/
public function get_default_conditions(): array {
return [
'badge:type',
'badge:name',
];
}
}
@@ -0,0 +1,377 @@
<?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/>.
declare(strict_types=1);
namespace core_badges\reportbuilder\local\entities;
use context_course;
use context_helper;
use context_system;
use html_writer;
use lang_string;
use moodle_url;
use stdClass;
use core_reportbuilder\local\entities\base;
use core_reportbuilder\local\filters\{date, select, text};
use core_reportbuilder\local\helpers\database;
use core_reportbuilder\local\report\{column, filter};
defined('MOODLE_INTERNAL') or die;
global $CFG;
require_once("{$CFG->libdir}/badgeslib.php");
/**
* Badge entity
*
* @package core_badges
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class badge extends base {
/**
* Database tables that this entity uses
*
* @return string[]
*/
protected function get_default_tables(): array {
return [
'badge',
'context',
'tag_instance',
'tag',
];
}
/**
* The default title for this entity
*
* @return lang_string
*/
protected function get_default_entity_title(): lang_string {
return new lang_string('badgedetails', 'core_badges');
}
/**
* Initialise the entity
*
* @return base
*/
public function initialise(): base {
$columns = $this->get_all_columns();
foreach ($columns as $column) {
$this->add_column($column);
}
// All the filters defined by the entity can also be used as conditions.
$filters = $this->get_all_filters();
foreach ($filters as $filter) {
$this
->add_filter($filter)
->add_condition($filter);
}
return $this;
}
/**
* Returns list of all available columns
*
* @return column[]
*/
protected function get_all_columns(): array {
global $DB;
$badgealias = $this->get_table_alias('badge');
$contextalias = $this->get_table_alias('context');
// Name.
$columns[] = (new column(
'name',
new lang_string('name'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_field("{$badgealias}.name")
->set_is_sortable(true);
// Name with link.
$columns[] = (new column(
'namewithlink',
new lang_string('namewithlink', 'core_badges'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_fields("{$badgealias}.name, {$badgealias}.id")
->set_is_sortable(true)
->add_callback(static function(?string $value, stdClass $row): string {
if (!$row->id) {
return '';
}
$url = new moodle_url('/badges/overview.php', ['id' => $row->id]);
return html_writer::link($url, $row->name);
});
// Description (note, this column contains plaintext so requires no post-processing).
$descriptionfieldsql = "{$badgealias}.description";
if ($DB->get_dbfamily() === 'oracle') {
$descriptionfieldsql = $DB->sql_order_by_text($descriptionfieldsql, 1024);
}
$columns[] = (new column(
'description',
new lang_string('description', 'core_badges'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_LONGTEXT)
->add_field($descriptionfieldsql, 'description');
// Criteria.
$columns[] = (new column(
'criteria',
new lang_string('bcriteria', 'core_badges'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_field("{$badgealias}.id")
->set_disabled_aggregation_all()
->add_callback(static function($badgeid): string {
global $PAGE;
if (!$badgeid) {
return '';
}
$badge = new \core_badges\badge($badgeid);
$renderer = $PAGE->get_renderer('core_badges');
return $renderer->print_badge_criteria($badge, 'short');
});
// Image.
$columns[] = (new column(
'image',
new lang_string('badgeimage', 'core_badges'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->add_join("LEFT JOIN {context} {$contextalias}
ON {$contextalias}.contextlevel = " . CONTEXT_COURSE . "
AND {$contextalias}.instanceid = {$badgealias}.courseid")
->set_type(column::TYPE_INTEGER)
->add_fields("{$badgealias}.id, {$badgealias}.type, {$badgealias}.courseid")
->add_field($DB->sql_cast_to_char("{$badgealias}.imagecaption"), 'imagecaption')
->add_fields(context_helper::get_preload_record_columns_sql($contextalias))
->set_disabled_aggregation_all()
->add_callback(static function(?int $badgeid, stdClass $badge): string {
if (!$badgeid) {
return '';
}
if ($badge->type == BADGE_TYPE_SITE) {
$context = context_system::instance();
} else {
context_helper::preload_from_record($badge);
$context = context_course::instance($badge->courseid);
}
$badgeimage = moodle_url::make_pluginfile_url($context->id, 'badges', 'badgeimage', $badgeid, '/', 'f2');
return html_writer::img($badgeimage, $badge->imagecaption);
});
// Language.
$columns[] = (new column(
'language',
new lang_string('language'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_field("{$badgealias}.language")
->set_is_sortable(true)
->add_callback(static function($language): string {
$languages = get_string_manager()->get_list_of_languages();
return $languages[$language] ?? $language ?? '';
});
// Version.
$columns[] = (new column(
'version',
new lang_string('version', 'core_badges'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_field("{$badgealias}.version")
->set_is_sortable(true);
// Status.
$columns[] = (new column(
'status',
new lang_string('status', 'core_badges'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_field("{$badgealias}.status")
->set_is_sortable(true)
->add_callback(static function($status): string {
if ($status === null) {
return '';
}
return get_string("badgestatus_{$status}", 'core_badges');
});
// Expiry date/period.
$columns[] = (new column(
'expiry',
new lang_string('expirydate', 'core_badges'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TIMESTAMP)
->add_fields("{$badgealias}.expiredate, {$badgealias}.expireperiod, {$badgealias}.id")
->set_is_sortable(true, ["{$badgealias}.expiredate", "{$badgealias}.expireperiod"])
->set_disabled_aggregation_all()
->add_callback(static function(?int $expiredate, stdClass $badge): string {
if (!$badge->id) {
return '';
} else if ($expiredate) {
return userdate($expiredate);
} else if ($badge->expireperiod) {
return format_time($badge->expireperiod);
} else {
return get_string('never', 'core_badges');
}
});
// Image author details.
foreach (['imageauthorname', 'imageauthoremail', 'imageauthorurl'] as $imageauthorfield) {
$columns[] = (new column(
$imageauthorfield,
new lang_string($imageauthorfield, 'core_badges'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_field("{$badgealias}.{$imageauthorfield}")
->set_is_sortable(true);
}
return $columns;
}
/**
* Return list of all available filters
*
* @return filter[]
*/
protected function get_all_filters(): array {
global $DB;
$badgealias = $this->get_table_alias('badge');
// Name.
$filters[] = (new filter(
text::class,
'name',
new lang_string('name'),
$this->get_entity_name(),
"{$badgealias}.name"
))
->add_joins($this->get_joins());
// Version.
$filters[] = (new filter(
text::class,
'version',
new lang_string('version', 'core_badges'),
$this->get_entity_name(),
"{$badgealias}.version"
))
->add_joins($this->get_joins());
// Status.
$filters[] = (new filter(
select::class,
'status',
new lang_string('status', 'core_badges'),
$this->get_entity_name(),
"{$badgealias}.status"
))
->add_joins($this->get_joins())
->set_options([
BADGE_STATUS_INACTIVE => new lang_string('badgestatus_0', 'core_badges'),
BADGE_STATUS_ACTIVE => new lang_string('badgestatus_1', 'core_badges'),
BADGE_STATUS_INACTIVE_LOCKED => new lang_string('badgestatus_2', 'core_badges'),
BADGE_STATUS_ACTIVE_LOCKED => new lang_string('badgestatus_3', 'core_badges'),
BADGE_STATUS_ARCHIVED => new lang_string('badgestatus_4', 'core_badges'),
]);
// Expiry date/period.
[$parammaxint, $paramtime] = database::generate_param_names(2);
$filters[] = (new filter(
date::class,
'expiry',
new lang_string('expirydate', 'core_badges'),
$this->get_entity_name(),
"CASE WHEN {$badgealias}.expiredate IS NULL AND {$badgealias}.expireperiod IS NULL
THEN " . $DB->sql_cast_char2int(":{$parammaxint}") . "
ELSE COALESCE({$badgealias}.expiredate, {$badgealias}.expireperiod + :{$paramtime})
END",
[$parammaxint => 2147483647, $paramtime => time()]
))
->add_joins($this->get_joins())
->set_limited_operators([
date::DATE_ANY,
date::DATE_RANGE,
date::DATE_LAST,
date::DATE_CURRENT,
date::DATE_NEXT,
date::DATE_PAST,
date::DATE_FUTURE,
]);
// Type.
$filters[] = (new filter(
select::class,
'type',
new lang_string('type', 'core_badges'),
$this->get_entity_name(),
"{$badgealias}.type"
))
->add_joins($this->get_joins())
->set_options([
BADGE_TYPE_SITE => new lang_string('site'),
BADGE_TYPE_COURSE => new lang_string('course'),
]);
return $filters;
}
/**
* Return joins necessary for retrieving tags
*
* @return string[]
*/
public function get_tag_joins(): array {
return $this->get_tag_joins_for_entity('core_badges', 'badge', $this->get_table_alias('badge') . '.id');
}
}
@@ -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/>.
declare(strict_types=1);
namespace core_badges\reportbuilder\local\entities;
use lang_string;
use core_reportbuilder\local\entities\base;
use core_reportbuilder\local\filters\{boolean_select, date};
use core_reportbuilder\local\helpers\format;
use core_reportbuilder\local\report\{column, filter};
/**
* Badge issued entity
*
* @package core_badges
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class badge_issued extends base {
/**
* Database tables that this entity uses
*
* @return string[]
*/
protected function get_default_tables(): array {
return [
'badge_issued',
];
}
/**
* The default title for this entity
*
* @return lang_string
*/
protected function get_default_entity_title(): lang_string {
return new lang_string('badgeissued', 'core_badges');
}
/**
* Initialise the entity
*
* @return base
*/
public function initialise(): base {
$columns = $this->get_all_columns();
foreach ($columns as $column) {
$this->add_column($column);
}
// All the filters defined by the entity can also be used as conditions.
$filters = $this->get_all_filters();
foreach ($filters as $filter) {
$this
->add_filter($filter)
->add_condition($filter);
}
return $this;
}
/**
* Returns list of all available columns
*
* @return column[]
*/
protected function get_all_columns(): array {
$badgeissuedalias = $this->get_table_alias('badge_issued');
// Date issued.
$columns[] = (new column(
'issued',
new lang_string('dateawarded', 'core_badges'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TIMESTAMP)
->add_field("{$badgeissuedalias}.dateissued")
->set_is_sortable(true)
->add_callback([format::class, 'userdate']);
// Date expires.
$columns[] = (new column(
'expire',
new lang_string('expirydate', 'core_badges'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TIMESTAMP)
->add_field("{$badgeissuedalias}.dateexpire")
->set_is_sortable(true)
->add_callback([format::class, 'userdate']);
// Visible.
$columns[] = (new column(
'visible',
new lang_string('visible', 'core_badges'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_BOOLEAN)
->add_fields("{$badgeissuedalias}.visible")
->add_callback([format::class, 'boolean_as_text']);
return $columns;
}
/**
* Return list of all available filters
*
* @return filter[]
*/
protected function get_all_filters(): array {
$badgealias = $this->get_table_alias('badge_issued');
// Date issued.
$filters[] = (new filter(
date::class,
'issued',
new lang_string('dateawarded', 'core_badges'),
$this->get_entity_name(),
"{$badgealias}.dateissued"
))
->add_joins($this->get_joins());
// Date expires.
$filters[] = (new filter(
date::class,
'expires',
new lang_string('expirydate', 'core_badges'),
$this->get_entity_name(),
"{$badgealias}.dateexpire"
))
->add_joins($this->get_joins());
// Visible.
$filters[] = (new filter(
boolean_select::class,
'visible',
new lang_string('visible', 'core_badges'),
$this->get_entity_name(),
"{$badgealias}.visible"
))
->add_joins($this->get_joins());
return $filters;
}
}
@@ -0,0 +1,319 @@
<?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/>.
declare(strict_types=1);
namespace core_badges\reportbuilder\local\systemreports;
use core\context\{course, system};
use core_badges\reportbuilder\local\entities\badge;
use core_reportbuilder\local\helpers\database;
use core_reportbuilder\local\report\{action, column};
use core_reportbuilder\system_report;
use html_writer;
use lang_string;
use moodle_url;
use pix_icon;
use stdClass;
defined('MOODLE_INTERNAL') || die;
global $CFG;
require_once("{$CFG->libdir}/badgeslib.php");
/**
* Badges system report class implementation
*
* @package core_badges
* @copyright 2023 David Carrillo <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class badges extends system_report {
/** @var int $badgeid The ID of the current badge row */
private int $badgeid;
/**
* Initialise report, we need to set the main table, load our entities and set columns/filters
*/
protected function initialise(): void {
// Our main entity, it contains all of the column definitions that we need.
$badgeentity = new badge();
$entityalias = $badgeentity->get_table_alias('badge');
$this->set_main_table('badge', $entityalias);
$this->add_entity($badgeentity);
$paramtype = database::generate_param_name();
$context = $this->get_context();
if ($context instanceof system) {
$type = BADGE_TYPE_SITE;
$this->add_base_condition_sql("{$entityalias}.type = :$paramtype", [$paramtype => $type]);
} else {
$type = BADGE_TYPE_COURSE;
$paramcourseid = database::generate_param_name();
$this->add_base_condition_sql("{$entityalias}.type = :$paramtype AND {$entityalias}.courseid = :$paramcourseid",
[$paramtype => $type, $paramcourseid => $context->instanceid]);
}
// Any columns required by actions should be defined here to ensure they're always available.
$this->add_base_fields("{$entityalias}.id, {$entityalias}.type, {$entityalias}.courseid, {$entityalias}.status");
// Now we can call our helper methods to add the content we want to include in the report.
$this->add_columns($badgeentity);
$this->add_filters();
$this->add_actions();
$this->set_initial_sort_column('badge:namewithlink', SORT_ASC);
$this->set_default_no_results_notice(new lang_string('nomatchingbadges', 'core_badges'));
// Set if report can be downloaded.
$this->set_downloadable(false);
}
/**
* Validates access to view this report
*
* @return bool
*/
protected function can_view(): bool {
return has_any_capability([
'moodle/badges:viewawarded',
'moodle/badges:createbadge',
'moodle/badges:awardbadge',
'moodle/badges:configurecriteria',
'moodle/badges:configuremessages',
'moodle/badges:configuredetails',
'moodle/badges:deletebadge'], $this->get_context());
}
/**
* Adds the columns we want to display in the report
*
* They are provided by the entities we previously added in the {@see initialise} method, referencing each by their
* unique identifier. If custom columns are needed just for this report, they can be defined here.
*
* @param badge $badgeentity
*/
public function add_columns(badge $badgeentity): void {
$columns = [
'badge:image',
'badge:namewithlink',
'badge:version',
'badge:status',
'badge:criteria',
];
$this->add_columns_from_entities($columns);
// Issued badges column.
$tempbadgealias = database::generate_alias();
$badgeentityalias = $badgeentity->get_table_alias('badge');
$this->add_column((new column(
'issued',
new lang_string('awards', 'core_badges'),
$badgeentity->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_INTEGER)
->add_field("(SELECT COUNT({$tempbadgealias}.userid)
FROM {badge_issued} {$tempbadgealias}
INNER JOIN {user} u
ON {$tempbadgealias}.userid = u.id
WHERE {$tempbadgealias}.badgeid = {$badgeentityalias}.id AND u.deleted = 0)", 'issued')
->set_is_sortable(true)
->set_callback(function(int $count): string {
if (!has_capability('moodle/badges:viewawarded', $this->get_context())) {
return (string) $count;
}
return html_writer::link(new moodle_url('/badges/recipients.php', ['id' => $this->badgeid]), $count);
}));
// Remove title from image column.
$this->get_column('badge:image')->set_title(null);
// Change title from namewithlink column.
$this->get_column('badge:namewithlink')->set_title(new lang_string('name'));
}
/**
* Adds the filters we want to display in the report
*
* They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their
* unique identifier
*/
protected function add_filters(): void {
$this->add_filters_from_entities([
'badge:name',
'badge:version',
'badge:status',
'badge:expiry',
]);
}
/**
* Add the system report actions. An extra column will be appended to each row, containing all actions added here
*
* Note the use of ":id" placeholder which will be substituted according to actual values in the row
*/
protected function add_actions(): void {
// Activate badge.
$this->add_action((new action(
new moodle_url('/badges/action.php', [
'id' => ':id',
'activate' => true,
'return' => ':return',
]),
new pix_icon('t/show', '', 'core'),
[],
false,
new lang_string('activate', 'badges')
))->add_callback(static function(stdclass $row): bool {
$badge = new \core_badges\badge($row->id);
// Populate the return URL.
$row->return = (new moodle_url('/badges/index.php',
['type' => $badge->type, 'id' => (int) $badge->courseid]))->out_as_local_url(false);
return has_capability('moodle/badges:configuredetails', $badge->get_context()) &&
$badge->has_criteria() &&
($row->status == BADGE_STATUS_INACTIVE || $row->status == BADGE_STATUS_INACTIVE_LOCKED);
}));
// Deactivate badge.
$this->add_action((new action(
new moodle_url('/badges/index.php', [
'lock' => ':id',
'sesskey' => sesskey(),
'type' => ':type',
'id' => ':courseid',
]),
new pix_icon('t/hide', '', 'core'),
[],
false,
new lang_string('deactivate', 'badges')
))->add_callback(static function(stdclass $row): bool {
$badge = new \core_badges\badge($row->id);
return has_capability('moodle/badges:configuredetails', $badge->get_context()) &&
$badge->has_criteria() &&
$row->status != BADGE_STATUS_INACTIVE && $row->status != BADGE_STATUS_INACTIVE_LOCKED;
}));
// Award badge manually.
$this->add_action((new action(
new moodle_url('/badges/award.php', [
'id' => ':id',
]),
new pix_icon('t/award', '', 'core'),
[],
false,
new lang_string('award', 'badges')
))->add_callback(static function(stdclass $row): bool {
$badge = new \core_badges\badge($row->id);
return has_capability('moodle/badges:awardbadge', $badge->get_context()) &&
$badge->has_manual_award_criteria() &&
$badge->is_active();
}));
// Edit action.
$this->add_action((new action(
new moodle_url('/badges/edit.php', [
'id' => ':id',
'action' => 'badge',
]),
new pix_icon('t/edit', '', 'core'),
[],
false,
new lang_string('edit', 'core')
))->add_callback(static function(stdclass $row): bool {
$context = self::get_badge_context((int)$row->type, (int)$row->courseid);
return has_capability('moodle/badges:configuredetails', $context);
}));
// Duplicate action.
$this->add_action((new action(
new moodle_url('/badges/action.php', [
'id' => ':id',
'copy' => 1,
'sesskey' => sesskey(),
]),
new pix_icon('t/copy', '', 'core'),
[],
false,
new lang_string('copy', 'badges')
))->add_callback(static function(stdclass $row): bool {
$context = self::get_badge_context((int)$row->type, (int)$row->courseid);
return has_capability('moodle/badges:createbadge', $context);
}));
// Delete action.
$this->add_action((new action(
new moodle_url('/badges/index.php', [
'delete' => ':id',
'type' => ':type',
'id' => ':courseid',
]),
new pix_icon('t/delete', '', 'core'),
['class' => 'text-danger'],
false,
new lang_string('delete', 'core')
))->add_callback(static function(stdclass $row): bool {
$context = self::get_badge_context((int)$row->type, (int)$row->courseid);
return has_capability('moodle/badges:deletebadge', $context);
}));
}
/**
* Return badge context based on type and courseid
*
* @param int $type
* @param int $courseid
* @return \core\context
* @throws \coding_exception
*/
private static function get_badge_context(int $type, int $courseid): \core\context {
switch ($type) {
case BADGE_TYPE_SITE:
return system::instance();
case BADGE_TYPE_COURSE:
return course::instance($courseid);
default:
throw new \coding_exception('Wrong context');
}
}
/**
* Store the ID of the badge within each row
*
* @param stdClass $row
*/
public function row_callback(stdClass $row): void {
$this->badgeid = (int) $row->id;
}
/**
* CSS classes to add to the row
*
* @param stdClass $row
* @return string
*/
public function get_row_class(stdClass $row): string {
return ($row->status == BADGE_STATUS_INACTIVE_LOCKED || $row->status == BADGE_STATUS_INACTIVE) ? 'text-muted' : '';
}
}
@@ -0,0 +1,138 @@
<?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/>.
declare(strict_types=1);
namespace core_badges\reportbuilder\local\systemreports;
use core_badges\reportbuilder\local\entities\badge;
use core_badges\reportbuilder\local\entities\badge_issued;
use core_reportbuilder\system_report;
use lang_string;
use moodle_url;
use pix_icon;
use stdClass;
defined('MOODLE_INTERNAL') || die;
global $CFG;
require_once("{$CFG->libdir}/badgeslib.php");
/**
* Course badges system report class implementation
*
* @package core_badges
* @copyright 2023 David Carrillo <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_badges extends system_report {
/**
* Initialise report, we need to set the main table, load our entities and set columns/filters
*/
protected function initialise(): void {
global $USER;
// Our main entity, it contains all of the column definitions that we need.
$badgeentity = new badge();
$entityalias = $badgeentity->get_table_alias('badge');
$this->set_main_table('badge', $entityalias);
$this->add_entity($badgeentity);
$type = $this->get_parameter('type', 0, PARAM_INT);
$courseid = $this->get_parameter('courseid', 0, PARAM_INT);
$this->add_base_condition_simple('type', $type);
$this->add_base_condition_simple('courseid', $courseid);
$this->add_base_condition_sql("({$entityalias}.status = " . BADGE_STATUS_ACTIVE .
" OR {$entityalias}.status = " . BADGE_STATUS_ACTIVE_LOCKED . ")");
$badgeissuedentity = new badge_issued();
$badgeissuedalias = $badgeissuedentity->get_table_alias('badge_issued');
$this->add_entity($badgeissuedentity
->add_join("LEFT JOIN {badge_issued} {$badgeissuedalias}
ON {$entityalias}.id = {$badgeissuedalias}.badgeid AND {$badgeissuedalias}.userid = ".$USER->id)
);
$this->add_base_fields("{$badgeissuedalias}.uniquehash");
// Now we can call our helper methods to add the content we want to include in the report.
$this->add_columns();
$this->add_filters();
$this->set_initial_sort_column('badge:name', SORT_ASC);
$this->set_default_no_results_notice(new lang_string('nomatchingbadges', 'core_badges'));
// Set if report can be downloaded.
$this->set_downloadable(false);
}
/**
* Validates access to view this report
*
* @return bool
*/
protected function can_view(): bool {
return has_capability('moodle/badges:viewbadges', $this->get_context());
}
/**
* Adds the columns we want to display in the report
*
* They are provided by the entities we previously added in the {@see initialise} method, referencing each by their
* unique identifier. If custom columns are needed just for this report, they can be defined here.
*/
protected function add_columns(): void {
$badgeissuedalias = $this->get_entity('badge_issued')->get_table_alias('badge_issued');
$this->add_columns_from_entities([
'badge:image',
'badge:name',
'badge:description',
'badge:criteria',
'badge_issued:issued',
]);
$this->get_column('badge_issued:issued')
->set_title(new lang_string('awardedtoyou', 'core_badges'))
->add_fields("{$badgeissuedalias}.uniquehash")
->set_callback(static function(?int $value, stdClass $row) {
global $OUTPUT;
if (!$value) {
return '';
}
$format = get_string('strftimedatefullshort', 'core_langconfig');
$date = $value ? userdate($value, $format) : '';
$badgeurl = new moodle_url('/badges/badge.php', ['hash' => $row->uniquehash]);
$icon = new pix_icon('i/valid', get_string('dateearned', 'badges', $date));
return $OUTPUT->action_icon($badgeurl, $icon, null, null, true);
});
}
/**
* Adds the filters we want to display in the report
*
* They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their
* unique identifier
*/
protected function add_filters(): void {
$this->add_filters_from_entities([
'badge:name',
'badge_issued:issued',
]);
}
}
@@ -0,0 +1,123 @@
<?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/>.
declare(strict_types=1);
namespace core_badges\reportbuilder\local\systemreports;
use core_badges\reportbuilder\local\entities\badge_issued;
use core_reportbuilder\local\report\action;
use core_reportbuilder\system_report;
use lang_string;
use moodle_url;
use pix_icon;
/**
* Badge recipients system report class implementation
*
* @package core_badges
* @copyright 2023 David Carrillo <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class recipients extends system_report {
/**
* Initialise report, we need to set the main table, load our entities and set columns/filters
*/
protected function initialise(): void {
// Our main entity, it contains all of the column definitions that we need.
$badgeissuedentity = new badge_issued();
$entityalias = $badgeissuedentity->get_table_alias('badge_issued');
$this->set_main_table('badge_issued', $entityalias);
$this->add_entity($badgeissuedentity);
$userentity = new \core_reportbuilder\local\entities\user();
$entityuseralias = $userentity->get_table_alias('user');
$this->add_entity($userentity
->add_joins($userentity->get_joins())
->add_join("JOIN {user} {$entityuseralias}
ON {$entityuseralias}.id = {$entityalias}.userid")
);
$this->add_base_condition_simple('badgeid', $this->get_parameter('badgeid', 0, PARAM_INT));
$this->add_base_fields("{$entityalias}.uniquehash");
// Now we can call our helper methods to add the content we want to include in the report.
$this->add_columns();
$this->add_filters();
$this->add_actions();
$this->set_initial_sort_column('badge_issued:issued', SORT_DESC);
$this->set_default_no_results_notice(new lang_string('nomatchingawards', 'core_badges'));
// Set if report can be downloaded.
$this->set_downloadable(false);
}
/**
* Validates access to view this report
*
* @return bool
*/
protected function can_view(): bool {
return has_capability('moodle/badges:viewawarded', $this->get_context());
}
/**
* Adds the columns we want to display in the report
*
* They are provided by the entities we previously added in the {@see initialise} method, referencing each by their
* unique identifier. If custom columns are needed just for this report, they can be defined here.
*/
protected function add_columns(): void {
$this->add_columns_from_entities([
'user:fullnamewithlink',
'badge_issued:issued',
]);
}
/**
* Adds the filters we want to display in the report
*
* They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their
* unique identifier
*/
protected function add_filters(): void {
$this->add_filters_from_entities([
'user:fullname',
'badge_issued:issued',
]);
}
/**
* Add the system report actions. An extra column will be appended to each row, containing all actions added here
*
* Note the use of ":uniquehash" placeholder which will be substituted according to actual values in the row
*/
protected function add_actions(): void {
$this->add_action((new action(
new moodle_url('/badges/badge.php', [
'hash' => ':uniquehash',
]),
new pix_icon('i/search', '', 'core'),
[],
false,
new lang_string('viewbadge', 'badges')
)));
}
}