Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Open Badges Issuer Gadget for Google Sites - More info
CryptoJS v3.0.2
(c) 2009-2012 by Jeff Mott. All rights reserved.
var CryptoJS=CryptoJS||function(i,p){var f={},q=f.lib={},j=q.Base=function(){function a(){}return{extend:function(h){a.prototype=this;var d=new a;h&&d.mixIn(h);d.$super=this;return d},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var d in a)a.hasOwnProperty(d)&&(this[d]=a[d]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.$super.extend(this)}}}(),k=q.WordArray=j.extend({init:function(a,h){a=
this.words=a||[];this.sigBytes=h!=p?h:4*a.length},toString:function(a){return(a||m).stringify(this)},concat:function(a){var h=this.words,d=a.words,c=this.sigBytes,a=a.sigBytes;this.clamp();if(c%4)for(var b=0;b<a;b++)h[c+b>>>2]|=(d[b>>>2]>>>24-8*(b%4)&255)<<24-8*((c+b)%4);else if(65535<d.length)for(b=0;b<a;b+=4)h[c+b>>>2]=d[b>>>2];else h.push.apply(h,d);this.sigBytes+=a;return this},clamp:function(){var a=this.words,b=this.sigBytes;a[b>>>2]&=4294967295<<32-8*(b%4);a.length=i.ceil(b/4)},clone:function(){var a=;a.words=this.words.slice(0);return a},random:function(a){for(var b=[],d=0;d<a;d+=4)b.push(4294967296*i.random()|0);return k.create(b,a)}}),r=f.enc={},m=r.Hex={stringify:function(a){for(var b=a.words,a=a.sigBytes,d=[],c=0;c<a;c++){var e=b[c>>>2]>>>24-8*(c%4)&255;d.push((e>>>4).toString(16));d.push((e&15).toString(16))}return d.join("")},parse:function(a){for(var b=a.length,d=[],c=0;c<b;c+=2)d[c>>>3]|=parseInt(a.substr(c,2),16)<<24-4*(c%8);return k.create(d,b/2)}},s=r.Latin1={stringify:function(a){for(var b=
a.words,a=a.sigBytes,d=[],c=0;c<a;c++)d.push(String.fromCharCode(b[c>>>2]>>>24-8*(c%4)&255));return d.join("")},parse:function(a){for(var b=a.length,d=[],c=0;c<b;c++)d[c>>>2]|=(a.charCodeAt(c)&255)<<24-8*(c%4);return k.create(d,b)}},g=r.Utf8={stringify:function(a){try{return decodeURIComponent(escape(s.stringify(a)))}catch(b){throw Error("Malformed UTF-8 data");}},parse:function(a){return s.parse(unescape(encodeURIComponent(a)))}},b=q.BufferedBlockAlgorithm=j.extend({reset:function(){this._data=k.create();
this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=g.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var b=this._data,d=b.words,c=b.sigBytes,e=this.blockSize,f=c/(4*e),f=a?i.ceil(f):i.max((f|0)-this._minBufferSize,0),a=f*e,c=i.min(4*a,c);if(a){for(var g=0;g<a;g+=e)this._doProcessBlock(d,g);g=d.splice(0,a);b.sigBytes-=c}return k.create(g,c)},clone:function(){var;a._data=this._data.clone();return a},_minBufferSize:0});q.Hasher=b.extend({init:function(){this.reset()},
reset:function(){;this._doReset()},update:function(a){this._append(a);this._process();return this},finalize:function(a){a&&this._append(a);this._doFinalize();return this._hash},clone:function(){var;a._hash=this._hash.clone();return a},blockSize:16,_createHelper:function(a){return function(b,d){return a.create(d).finalize(b)}},_createHmacHelper:function(a){return function(b,d){return e.HMAC.create(a,d).finalize(b)}}});var e=f.algo={};return f}(Math);
(function(i){var p=CryptoJS,f=p.lib,q=f.WordArray,f=f.Hasher,j=p.algo,k=[],r=[];(function(){function f(a){for(var b=i.sqrt(a),d=2;d<=b;d++)if(!(a%d))return!1;return!0}function g(a){return 4294967296*(a-(a|0))|0}for(var b=2,e=0;64>e;)f(b)&&(8>e&&(k[e]=g(i.pow(b,0.5))),r[e]=g(i.pow(b,1/3)),e++),b++})();var m=[],j=j.SHA256=f.extend({_doReset:function(){this._hash=q.create(k.slice(0))},_doProcessBlock:function(f,g){for(var b=this._hash.words,e=b[0],a=b[1],h=b[2],d=b[3],c=b[4],i=b[5],j=b[6],k=b[7],l=0;64>
l;l++){if(16>l)m[l]=f[g+l]|0;else{var n=m[l-15],o=m[l-2];m[l]=((n<<25|n>>>7)^(n<<14|n>>>18)^n>>>3)+m[l-7]+((o<<15|o>>>17)^(o<<13|o>>>19)^o>>>10)+m[l-16]}n=k+((c<<26|c>>>6)^(c<<21|c>>>11)^(c<<7|c>>>25))+(c&i^~c&j)+r[l]+m[l];o=((e<<30|e>>>2)^(e<<19|e>>>13)^(e<<10|e>>>22))+(e&a^e&h^a&h);k=j;j=i;i=c;c=d+n|0;d=h;h=a;a=e;e=n+o|0}b[0]=b[0]+e|0;b[1]=b[1]+a|0;b[2]=b[2]+h|0;b[3]=b[3]+d|0;b[4]=b[4]+c|0;b[5]=b[5]+i|0;b[6]=b[6]+j|0;b[7]=b[7]+k|0},_doFinalize:function(){var f=this._data,g=f.words,b=8*this._nDataBytes,
<?xml version="1.0" encoding="UTF-8" ?>
<ModulePrefs title="Open Badges Issuer Gadget" width="800" height="700" author="Martin Hawksey"
description="This gadget allows you to integrate the the Mozilla Open Badges Issuer API into Google Sites. To use insert into a Google site and set a base url for your hosted assertions. To allow users to collect their badges direct them to the web address of the Site page containing the gadget adding ?claim_code={insert the rest of their assertion url} to the url"
author_email="" scrolling="true"
title_url="" author_affiliation=""
<UserPref name="script_url" display_name="Base Url" datatype="string" default_value="" required="true"/>
<Content type="html">
<![CDATA[ <script src=""></script>
<script type="text/javascript">
var code = []; // initialize assesor array
var prefs = new gadgets.Prefs();
// Modification of
function getQueryString() {
var ref = document.referrer;
var qs= ref.split('?');
var result = {}, queryString = qs[1],
re = /([^&=]+)=([^&]*)/g, m;
while (m = re.exec(queryString)) {
result[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
return result;
function init() {
var base_url = prefs.getString("script_url");
var codes = getQueryString().claim_code;
code = codes.split(",");
if (codes != "") {
for ( var i = 0; i < code .length; i++ ) {
code[i] = base_url + code[i];
// -->
<p id="msg">No claim code</p>
<p id="collectBadgeWrapper"><a href='javascript:void(0);' onclick='OpenBadges.issue(code, function(errors, successes)
{ });' id="collectBadge" style="display:none">Claim your badge</a>
</p> ]]>
// initialising some of these as blank to to keep human readable structure
var badgeTemplate = {
"recipient": "",
"salt": "",
"evidence": "",
"issued_on": "",
"badge": {
"version": "0.1.0",
"name": "",
"image": "",
"description": "",
"criteria": "",
"issuer": {
"origin": "",
"name": "Martin Hawksey",
"org": "CETIS",
"contact": ""
function onFormSumbit(e) {
// next three lines used to get last row number (might fail on simulatious form submits but will do for thsi project)
var doc = SpreadsheetApp.getActiveSpreadsheet();
var sheet = doc.getSheetByName("DATA");
var lastrow = sheet.getLastRow();
var correct = false; // initialise the response to the question as incorrect
// next 4 lines read the form values submitted
var timestamp = e.values[0];
var email = e.values[1];
var name = e.values[2];
var resp = e.values[3];
var claim_code = []; // initialise an array of claim_codes
var baseUrl = ""; //this is where my site is hosted
var claim_code_base = "row=" + lastrow; // our claim code is going to carry two pieces of info row number and type of badge
claim_code.push(Utilities.base64Encode(claim_code_base + "&type=user")); //everyone gets a user badge so we push this to our claim_code array encoding the value to disguise
// next 4 lines tests if user got the answer right and if so adds a claim_code
if (resp == ScriptProperties.getProperty("answer")){ // answer hidden as script property
claim_code.push(Utilities.base64Encode(claim_code_base + "&type=gold"));
correct = true;
var url = baseUrl+"?claim_code=" + claim_code.join(","); // build the url to be emailed to person. If multiple badges the claim_code is comma seperated
// compose text for the email
var emailText = "Hi "+name+",\nThanks for trying the Open Badges Issuer Gadget. To claim you badges visit \n\n" + url;
if (correct) {
emailText += "\n\n [As you got the question right you can claim the Issuer Gadget User and Gold badges]";
} else {
emailText += "\n\n [Unfortunately you didn't attempt the optional question or got it wrong but you can still claim you Issuer Gadget User badge]";
// using the MailApp function of Apps Script to send the email to the person
MailApp.sendEmail(email, "Claim your Open Badges Issuer Gadget Badges", emailText);
// this function builds the Assertion JSON blob for each badge
// the code is Published > Deployed as web app. To see an example render visit:
// An issue with this is currently Google prevent cross domain access to the json file via ajax (which the issuer api uses) so the response is currently proxied using php
// <?php
// $binfo = json_decode(file_get_contents(''.$_GET['claim_code']));
// header('Content-Type: application/json');
// echo json_encode($binfo);
// ?>
// try at
function doGet(e){
// The flow here is get the data from the spreadsheet and create the assertion blob
// Other ways of doing this use native NoSQL style ScriptDb (part of Apps Script), pre baking assertion
// then serving up directly in doGet
// to access spreadsheet data we need to get by id (stored by runing setup)
var doc = SpreadsheetApp.openById(ScriptProperties.getProperty('active'));
var sheet = doc.getSheetByName("DATA");
var claim_encode = Utilities.base64Decode(e.parameter.claim_code); // decode claim_code
var claim_code = bin2String(claim_encode); // need to convert byte array to string
var data = getQueryString("?"+claim_code); // extract row and type passed in claim_code
// if question was answered correctly prepare gold badge
if (data.type == "gold"){
var select ="*"; // used as part of evidence
var name = "Open Badges Issuer Gadget Gold";
var image = "";
var description = "Awared for understanding urls required to use use the Open Badges Issuer Gadget emebed in a Google Site.";
} else {
var select ="A,B"; // used as part of evidence
var name = "Open Badges Issuer Gadget User";
var image = "";
var description = "Awarded for trying the Open Badges Issuer Gadget.";
// get some data recorded in the spreadsheet
var timestamp = sheet.getRange(data.row, 1).getValue();
var email = sheet.getRange(data.row, 2).getValue();
// publishing form response in another spreadsheet (using IMPORTRANGE) which lets us query
var evidence_base = "";
// fill in the badge assertion details
badgeTemplate.salt = "G00g1e";
badgeTemplate.recipient = hashEmailAddress(email, badgeTemplate.salt);
badgeTemplate.evidence = evidence_base + "&tq="+encodeURIComponent("select "+select+" where A = datetime '"+Utilities.formatDate(timestamp, "GMT", "yyyy-MM-dd HH:mm:ss")+"'");
badgeTemplate.issued_on = Utilities.formatDate(timestamp, "GMT", "yyyy-MM-dd"); = name;
badgeTemplate.badge.image = image;
badgeTemplate.badge.description = description;
badgeTemplate.badge.criteria = "";
// now that Assertion is complete next lines publish it to the web
var output = ContentService.createTextOutput();
return output;
function setup(){
ScriptProperties.setProperty('active', SpreadsheetApp.getActiveSpreadsheet().getId());
// Based on
function hashEmailAddress(email, salt) {
var hash = CryptoJS.SHA256(email+salt);
return 'sha256$'+ hash;
// Modification of
function getQueryString(ref) {
var qs= ref.split('?');
var result = {}, queryString = qs[1],
re = /([^&=]+)=([^&]*)/g, m;
while (m = re.exec(queryString)) {
result[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
return result;
// From
function bin2String(array) {
return String.fromCharCode.apply(String, array);
Copy link

libraronin commented Sep 23, 2013

I would like to use your badging gadget, but does this script still work with the new way forms and responses are handled in Google Drive now?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment