Merge branch 'master' into highlighter
@ -1,9 +1,9 @@
|
||||
So far, these people have contributed:
|
||||
--------------------------------------
|
||||
|
||||
[Bananeweizen](https://github.com/Bananeweizen),
|
||||
[blakjakau](https://github.com/blakjakau),
|
||||
[boombuler](https://github.com/boombuler),
|
||||
[breunigs](https://github.com/breunigs),
|
||||
[ccjon](https://github.com/ccjon),
|
||||
[cmrn](https://github.com/cmrn),
|
||||
[epf](https://github.com/epf),
|
||||
|
16
build.py
@ -93,7 +93,7 @@ def loaderMD(var):
|
||||
payload = {'text': file, 'mode': 'markdown'}
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
req = urllib2.Request(url, json.dumps(payload).encode('utf8'), headers)
|
||||
md = urllib2.urlopen(req).read().decode('utf8').replace('\n', '').replace('\'', '\\\'')
|
||||
md = urllib2.urlopen(req).read().decode('utf8').replace('\n', '\\n').replace('\'', '\\\'')
|
||||
files[fn] = {}
|
||||
files[fn][filemd5] = md
|
||||
db['files'] = files
|
||||
@ -205,12 +205,24 @@ if buildMobile:
|
||||
if buildMobile not in ['debug','release']:
|
||||
raise Exception("Error: buildMobile must be 'debug' or 'release'")
|
||||
|
||||
# first, copy the IITC script into the mobile folder. create the folder if needed
|
||||
# compile the user location script
|
||||
fn = "user-location.user.js"
|
||||
script = readfile("mobile/" + fn)
|
||||
downloadUrl = distUrlBase and distUrlBase + '/' + fn.replace("\\","/") or 'none'
|
||||
updateUrl = distUrlBase and downloadUrl.replace('.user.js', '.meta.js') or 'none'
|
||||
script = doReplacements(script, downloadUrl=downloadUrl, updateUrl=updateUrl)
|
||||
|
||||
metafn = fn.replace('.user.js', '.meta.js')
|
||||
saveScriptAndMeta(script, os.path.join(outDir,fn), os.path.join(outDir,metafn))
|
||||
|
||||
# copy the IITC script into the mobile folder. create the folder if needed
|
||||
try:
|
||||
os.makedirs("mobile/assets")
|
||||
except:
|
||||
pass
|
||||
shutil.copy(os.path.join(outDir,"total-conversion-build.user.js"), "mobile/assets/iitc.js")
|
||||
# copy the user location script into the mobile folder.
|
||||
shutil.copy(os.path.join(outDir,"user-location.user.js"), "mobile/assets/user-location.user.js")
|
||||
|
||||
# also copy plugins
|
||||
try:
|
||||
|
33
code/boot.js
@ -83,6 +83,26 @@ window.setupLayerChooserSelectOne = function() {
|
||||
});
|
||||
}
|
||||
|
||||
// Setup the function to record the on/off status of overlay layerGroups
|
||||
window.setupLayerChooserStatusRecorder = function() {
|
||||
// Record already added layerGroups
|
||||
$.each(window.layerChooser._layers, function(ind, chooserEntry) {
|
||||
if(!chooserEntry.overlay) return true;
|
||||
var display = window.map.hasLayer(chooserEntry.layer);
|
||||
window.updateDisplayedLayerGroup(chooserEntry.name, display);
|
||||
});
|
||||
|
||||
// Record layerGroups change
|
||||
window.map.on('layeradd layerremove', function(e) {
|
||||
var id = L.stamp(e.layer);
|
||||
var layerGroup = this._layers[id];
|
||||
if (layerGroup && layerGroup.overlay) {
|
||||
var display = (e.type === 'layeradd');
|
||||
window.updateDisplayedLayerGroup(layerGroup.name, display);
|
||||
}
|
||||
}, window.layerChooser);
|
||||
}
|
||||
|
||||
window.setupStyles = function() {
|
||||
$('head').append('<style>' +
|
||||
[ '#largepreview.enl img { border:2px solid '+COLORS[TEAM_ENL]+'; } ',
|
||||
@ -206,14 +226,16 @@ window.setupMap = function() {
|
||||
|
||||
// update map hooks
|
||||
map.on('movestart zoomstart', window.requests.abort);
|
||||
map.on('moveend zoomend', function() { window.startRefreshTimeout(500) });
|
||||
|
||||
// run once on init
|
||||
window.requestData();
|
||||
window.startRefreshTimeout();
|
||||
map.on('moveend zoomend', function() { console.log('map moveend'); window.startRefreshTimeout(ON_MOVE_REFRESH*1000) });
|
||||
|
||||
window.addResumeFunction(window.requestData);
|
||||
window.requests.addRefreshFunction(window.requestData);
|
||||
|
||||
// start the refresh process with a small timeout, so the first data request happens quickly
|
||||
// (the code originally called the request function directly, and triggered a normal delay for the nxt refresh.
|
||||
// however, the moveend/zoomend gets triggered on map load, causing a duplicate refresh. this helps prevent that
|
||||
window.startRefreshTimeout(ON_MOVE_REFRESH*1000);
|
||||
|
||||
};
|
||||
|
||||
// renders player details into the website. Since the player info is
|
||||
@ -370,6 +392,7 @@ function boot() {
|
||||
window.chat.setup();
|
||||
window.setupQRLoadLib();
|
||||
window.setupLayerChooserSelectOne();
|
||||
window.setupLayerChooserStatusRecorder();
|
||||
window.setupBackButton();
|
||||
// read here ONCE, so the URL is only evaluated one time after the
|
||||
// necessary data has been loaded.
|
||||
|
@ -1,96 +1,227 @@
|
||||
|
||||
// REDEEMING /////////////////////////////////////////////////////////
|
||||
|
||||
window.handleRedeemResponse = function(data, textStatus, jqXHR) {
|
||||
if(data.error) {
|
||||
var error = '';
|
||||
if(data.error === 'ALREADY_REDEEMED') {
|
||||
error = 'The passcode has already been redeemed.';
|
||||
} else if(data.error === 'ALREADY_REDEEMED_BY_PLAYER') {
|
||||
error = 'You have already redeemed this passcode.';
|
||||
} else if(data.error === 'INVALID_PASSCODE') {
|
||||
error = 'This passcode is invalid.';
|
||||
} else {
|
||||
error = 'There was a problem redeeming the passcode. Try again?';
|
||||
/* Resource type names mapped to actual names and abbreviations.
|
||||
* Add more here if necessary.
|
||||
*/
|
||||
window.REDEEM_RESOURCES = {
|
||||
RES_SHIELD: {long: 'Portal Shield', short: 'SH'},
|
||||
EMITTER_A: {long: 'Resonator', short: 'R'},
|
||||
EMP_BURSTER: {long: 'XMP Burster', short: 'X'},
|
||||
POWER_CUBE: {long: 'Power Cube', short: 'C'}
|
||||
};
|
||||
|
||||
/* Redemption errors. Very self-explanatory.
|
||||
*/
|
||||
window.REDEEM_ERRORS = {
|
||||
ALREADY_REDEEMED: 'The passcode has already been redeemed.',
|
||||
ALREADY_REDEEMED_BY_PLAYER : 'You have already redeemed this passcode.',
|
||||
INVALID_PASSCODE: 'This passcode is invalid.'
|
||||
};
|
||||
|
||||
/* These are HTTP status codes returned by the redemption API.
|
||||
* TODO: Move to another file? Use more generally across IITC?
|
||||
*/
|
||||
window.REDEEM_STATUSES = {
|
||||
429: 'You have been rate-limited by the server. Wait a bit and try again.',
|
||||
500: 'Internal server error'
|
||||
};
|
||||
|
||||
/* Encouragement for people who got it in.
|
||||
* Just for fun.
|
||||
*/
|
||||
window.REDEEM_ENCOURAGEMENT = [
|
||||
"Passcode accepted!",
|
||||
"Access granted.",
|
||||
"Asset transfer in progress.",
|
||||
"Well done, Agent.",
|
||||
"Make the " + {'RESISTANCE' : 'Resistance', 'ALIENS' : 'Enlightened'}[PLAYER.team] + " proud!"
|
||||
];
|
||||
|
||||
/* Redemption "handlers" handle decoding and formatting for rewards.
|
||||
*
|
||||
* Redemption "decoders" are used for returning the primary attribute (key) from
|
||||
* different types of items. Pretty self-explanatory.
|
||||
*
|
||||
* Redemption "formatters" are used for formatting specific types of password rewards.
|
||||
* Right now, Ingress has resourceWithLevels (leveled resources) and modResource (mods).
|
||||
* Resources with levels have levels, and mods have rarity. Format them appropriately.
|
||||
*/
|
||||
window.REDEEM_HANDLERS = {
|
||||
'resourceWithLevels' : {
|
||||
decode: function(type, resource) {return resource.level;},
|
||||
format: function(acquired, level) {
|
||||
var prefix = '<span style="color: ' + (window.COLORS_LVL[level] || 'white') + ';">';
|
||||
var suffix = '</span>';
|
||||
return {
|
||||
table: '<td>' + prefix + 'L' + level + suffix + '</td><td>' + acquired.name.long + ' [' + acquired.count + ']</td>',
|
||||
html: acquired.count + '×' + acquired.name.short + prefix + level + suffix,
|
||||
plain: acquired.count + '@' + acquired.name.short + level
|
||||
};
|
||||
}
|
||||
alert('<strong>' + data.error + '</strong>\n' + error);
|
||||
} else if(data.result) {
|
||||
var tblResult = $('<table class="redeem-result" />');
|
||||
tblResult.append($('<tr><th colspan="2">Passcode accepted!</th></tr>'));
|
||||
|
||||
if(data.result.apAward)
|
||||
tblResult.append($('<tr><td>+</td><td>' + data.result.apAward + 'AP</td></tr>'));
|
||||
if(data.result.xmAward)
|
||||
tblResult.append($('<tr><td>+</td><td>' + data.result.xmAward + 'XM</td></tr>'));
|
||||
|
||||
var resonators = {};
|
||||
var bursts = {};
|
||||
var shields = {};
|
||||
var cubes = {};
|
||||
|
||||
for(var i in data.result.inventoryAward) {
|
||||
var acquired = data.result.inventoryAward[i][2];
|
||||
if(acquired.modResource) {
|
||||
if(acquired.modResource.resourceType === 'RES_SHIELD') {
|
||||
var rarity = acquired.modResource.rarity.split('_').map(function (i) {return i[0]}).join('');
|
||||
if(!shields[rarity]) shields[rarity] = 0;
|
||||
shields[rarity] += 1;
|
||||
}
|
||||
} else if(acquired.resourceWithLevels) {
|
||||
if(acquired.resourceWithLevels.resourceType === 'EMITTER_A') {
|
||||
var level = acquired.resourceWithLevels.level
|
||||
if(!resonators[level]) resonators[level] = 0;
|
||||
resonators[level] += 1;
|
||||
} else if(acquired.resourceWithLevels.resourceType === 'EMP_BURSTER') {
|
||||
var level = acquired.resourceWithLevels.level
|
||||
if(!bursts[level]) bursts[level] = 0;
|
||||
bursts[level] += 1;
|
||||
} else if(acquired.resourceWithLevels.resourceType === 'POWER_CUBE') {
|
||||
var level = acquired.resourceWithLevels.level
|
||||
if(!cubes[level]) cubes[level] = 0;
|
||||
cubes[level] += 1;
|
||||
}
|
||||
}
|
||||
},
|
||||
'modResource' : {
|
||||
decode: function(type, resource) {return resource.rarity;},
|
||||
format: function(acquired, rarity) {
|
||||
var prefix = '<span style="color: ' + (window.COLORS_MOD[rarity] || 'white') + ';">';
|
||||
var suffix = '</span>';
|
||||
var abbreviation = rarity.split('_').map(function (i) {return i[0];}).join('');
|
||||
return {
|
||||
table: '<td>' + prefix + abbreviation + suffix + '</td><td>' + acquired.name.long + ' [' + acquired.count + ']</td>',
|
||||
html: acquired.count + '×' + prefix + abbreviation + suffix,
|
||||
plain: acquired.count + '@' + abbreviation
|
||||
};
|
||||
}
|
||||
},
|
||||
'default' : {
|
||||
decode: function(type, resource) {return 'UNKNOWN';},
|
||||
format: function(acquired, group) {
|
||||
return {
|
||||
table: '<td>+</td><td>' + acquired.name.long + ' [' + acquired.count + ']</td>',
|
||||
html: acquired.count + '×' + acquired.name.short,
|
||||
plain: acquired.count + '@' + acquired.name.short
|
||||
};
|
||||
}
|
||||
|
||||
$.each(resonators, function(lvl, count) {
|
||||
var text = 'Resonator';
|
||||
if(count >= 2) text += ' ('+count+')';
|
||||
tblResult.append($('<tr ><td style="color: ' +window.COLORS_LVL[lvl]+ ';">L' +lvl+ '</td><td>' + text + '</td></tr>'));
|
||||
});
|
||||
$.each(bursts, function(lvl, count) {
|
||||
var text = 'Xmp Burster';
|
||||
if(count >= 2) text += ' ('+count+')';
|
||||
tblResult.append($('<tr ><td style="color: ' +window.COLORS_LVL[lvl]+ ';">L' +lvl+ '</td><td>' + text + '</td></tr>'));
|
||||
});
|
||||
$.each(cubes, function(lvl, count) {
|
||||
var text = 'Power Cube';
|
||||
if(count >= 2) text += ' ('+count+')';
|
||||
tblResult.append($('<tr ><td style="color: ' +window.COLORS_LVL[lvl]+ ';">L' +lvl+ '</td><td>' + text + '</td></tr>'));
|
||||
});
|
||||
$.each(shields, function(lvl, count) {
|
||||
var text = 'Portal Shield';
|
||||
if(count >= 2) text += ' ('+count+')';
|
||||
tblResult.append($('<tr><td>'+lvl+'</td><td>'+text+'</td></tr>'));
|
||||
});
|
||||
alert(tblResult, true);
|
||||
}
|
||||
};
|
||||
|
||||
/* Redemption "hints" hint at what an unknown resource might be from its object properties.
|
||||
*/
|
||||
window.REDEEM_HINTS = {
|
||||
level: 'resourceWithLevels',
|
||||
rarity: 'modResource'
|
||||
};
|
||||
|
||||
window.handleRedeemResponse = function(data, textStatus, jqXHR) {
|
||||
var passcode = this.passcode, to_alert, to_log;
|
||||
|
||||
if(data.error) {
|
||||
to_alert = '<strong>' + data.error + '</strong><br />' + (window.REDEEM_ERRORS[data.error] || 'There was a problem redeeming the passcode. Try again?');
|
||||
to_log = '[ERROR] ' + data.error;
|
||||
} else if(data.result) {
|
||||
var encouragement = window.REDEEM_ENCOURAGEMENT[Math.floor(Math.random() * window.REDEEM_ENCOURAGEMENT.length)];
|
||||
var payload = {};
|
||||
var inferred = [];
|
||||
var results = {
|
||||
'table' : ['<th colspan="2" style="text-align: left;"><strong>' + encouragement + '</strong></th>'],
|
||||
'html' : [],
|
||||
'plain' : []
|
||||
};
|
||||
|
||||
// Track frequencies and levels of items
|
||||
$.each(data.result.inventoryAward, function (award_idx, award) {
|
||||
var acquired = award[2], handler, type, key, name;
|
||||
|
||||
// The "what the heck is this item" heuristic
|
||||
$.each(acquired, function (taxonomy, resource) {
|
||||
if('resourceType' in resource) {
|
||||
if(taxonomy in window.REDEEM_HANDLERS) {
|
||||
// Cool. We know how to directly handle this item.
|
||||
handler = {
|
||||
functions: window.REDEEM_HANDLERS[taxonomy],
|
||||
taxonomy: taxonomy,
|
||||
processed_as: taxonomy
|
||||
};
|
||||
} else {
|
||||
// Let's see if we can get a hint for how we should handle this.
|
||||
$.each(resource, function (resource_key, resource_value) {
|
||||
if(resource_key in window.REDEEM_HINTS) {
|
||||
// We're not sure what this item is, but we can process it like another item
|
||||
handler = {
|
||||
functions: (window.REDEEM_HANDLERS[window.REDEEM_HINTS[resource_key]] || window.REDEEM_HANDLERS['default']),
|
||||
taxonomy: taxonomy,
|
||||
processed_as: window.REDEEM_HINTS[resource_key]
|
||||
};
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Fall back to the default handler if necessary
|
||||
handler = handler || {
|
||||
functions: window.REDEEM_HANDLERS['default'],
|
||||
taxonomy: taxonomy,
|
||||
processed_as: 'default'
|
||||
};
|
||||
}
|
||||
|
||||
// Collect the data that we know
|
||||
type = resource.resourceType;
|
||||
key = handler.functions.decode(type, resource);
|
||||
name = window.REDEEM_RESOURCES[type] || {long: type, short: type[0]};
|
||||
|
||||
// Decide if we inferred this resource
|
||||
if(!(type in window.REDEEM_RESOURCES) || handler.taxonomy !== handler.processed_as) {
|
||||
name.long += '*';
|
||||
name.short += '*';
|
||||
inferred.push({type: type, key: key, handler: handler});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Update frequencies
|
||||
payload[type] = payload[type] || {};
|
||||
payload[type][key] = payload[type][key] || {};
|
||||
payload[type][key].handler = payload[type][key].handler || handler;
|
||||
payload[type][key].type = payload[type][key].type || type;
|
||||
payload[type][key].name = payload[type][key].name || name;
|
||||
payload[type][key].count = payload[type][key].count || 0;
|
||||
payload[type][key].count += 1;
|
||||
});
|
||||
|
||||
// Get AP and XM.
|
||||
$.each([{label: 'AP', award: parseInt(data.result.apAward)}, {label: 'XM', award: parseInt(data.result.xmAward)}], function(idx, val) {
|
||||
if(val.award > 0) {
|
||||
results.table.push('<td>+</td><td>' + digits(val.award) + ' ' + val.label + '</td>');
|
||||
results.html.push(val.award + ' ' + val.label);
|
||||
results.plain.push(val.award + ' ' + val.label);
|
||||
}
|
||||
});
|
||||
|
||||
// Build the formatted results alphabetically
|
||||
$.each(Object.keys(payload).sort(), function(type_idx, type) {
|
||||
$.each(Object.keys(payload[type]).sort(), function(key_idx, key) {
|
||||
var acquired = payload[type][key];
|
||||
$.each(acquired.handler.functions.format(acquired, key), function(format, string) {
|
||||
results[format].push(string);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Let the user know if we had to guess
|
||||
if (inferred.length > 0) {
|
||||
results.table.push('<td style="font-family: monospace;">*</td><td style="font-family: monospace;">Guessed (check console)</td>');
|
||||
$.each(inferred, function (idx, val) {
|
||||
console.log(passcode +
|
||||
' => [INFERRED] ' + val.type + ':' + val.key + ' :: ' +
|
||||
val.handler.taxonomy + ' =~ ' + val.handler.processed_as);
|
||||
});
|
||||
}
|
||||
|
||||
// Add table footers
|
||||
results.table.push('<td style="font-family: monospace;">></td><td><a href="javascript:alert(\'' +
|
||||
escape('<span style="font-family: monospace;"><strong>' + encouragement + '</strong><br />' + results.html.join('/') + '</span>') +
|
||||
'\', true);" style="font-family: monospace;">[plaintext]</a>');
|
||||
|
||||
// Display formatted versions in a table, plaintext, and the console log
|
||||
to_alert = '<table class="redeem-result">' + results.table.map(function(a) {return '<tr>' + a + '</tr>';}).join("\n") + '</table>';
|
||||
to_log = '[SUCCESS] ' + results.plain.join('/');
|
||||
}
|
||||
|
||||
alert(to_alert, true);
|
||||
console.log(passcode + ' => ' + to_log);
|
||||
}
|
||||
|
||||
window.setupRedeem = function() {
|
||||
$("#redeem").keypress(function(e) {
|
||||
if((e.keyCode ? e.keyCode : e.which) != 13) return;
|
||||
if((e.keyCode ? e.keyCode : e.which) !== 13) return;
|
||||
var data = {passcode: $(this).val()};
|
||||
|
||||
window.postAjax('redeemReward', data, window.handleRedeemResponse,
|
||||
function(response) {
|
||||
var extra = '';
|
||||
if(response && response.status) {
|
||||
if(response.status === 429) {
|
||||
extra = 'You have been rate-limited by the server. Wait a bit and try again.';
|
||||
} else {
|
||||
extra = 'The server indicated an error.';
|
||||
}
|
||||
extra += '\nResponse: HTTP <a href="http://httpstatus.es/' + response.status + '" alt="HTTP ' + response.status + '">' + response.status + '</a>.';
|
||||
if(response.status) {
|
||||
extra = (window.REDEEM_STATUSES[response.status] || 'The server indicated an error.') + ' (HTTP ' + response.status + ')';
|
||||
} else {
|
||||
extra = 'No status code was returned.';
|
||||
}
|
||||
|
@ -8,6 +8,10 @@ window.failedRequestCount = 0;
|
||||
|
||||
window.requests = function() {}
|
||||
|
||||
//time of last refresh
|
||||
window.requests._lastRefreshTime = 0;
|
||||
window.requests._quickRefreshPending = false;
|
||||
|
||||
window.requests.add = function(ajax) {
|
||||
window.activeRequests.push(ajax);
|
||||
renderUpdateStatus();
|
||||
@ -38,17 +42,19 @@ window.renderUpdateStatus = function() {
|
||||
if(mapRunsUserAction)
|
||||
t += 'paused during interaction';
|
||||
else if(isIdle())
|
||||
t += '<span style="color:red">Idle, not updating.</span>';
|
||||
t += '<span style="color:#888">Idle, not updating.</span>';
|
||||
else if(window.activeRequests.length > 0)
|
||||
t += window.activeRequests.length + ' requests running.';
|
||||
else if(window.requests._quickRefreshPending)
|
||||
t += 'refreshing...';
|
||||
else
|
||||
t += 'Up to date.';
|
||||
|
||||
if(renderLimitReached())
|
||||
t += ' <span style="color:red" class="help" title="Can only render so much before it gets unbearably slow. Not all entities are shown. Zoom in or increase the limit (search for MAX_DRAWN_*).">RENDER LIMIT</span> '
|
||||
t += ' <span style="color:#f66" class="help" title="Can only render so much before it gets unbearably slow. Not all entities are shown. Zoom in or increase the limit (search for MAX_DRAWN_*).">RENDER LIMIT</span> '
|
||||
|
||||
if(window.failedRequestCount > 0)
|
||||
t += ' <span style="color:red">' + window.failedRequestCount + ' failed</span>.'
|
||||
t += ' <span style="color:#f66">' + window.failedRequestCount + ' failed</span>.'
|
||||
|
||||
t += '<br/>(';
|
||||
var minlvl = getMinPortalLevel();
|
||||
@ -78,19 +84,28 @@ window.startRefreshTimeout = function(override) {
|
||||
if(refreshTimeout) clearTimeout(refreshTimeout);
|
||||
var t = 0;
|
||||
if(override) {
|
||||
window.requests._quickRefreshPending = true;
|
||||
t = override;
|
||||
//ensure override can't cause too fast a refresh if repeatedly used (e.g. lots of scrolling/zooming)
|
||||
timeSinceLastRefresh = new Date().getTime()-window.requests._lastRefreshTime;
|
||||
if(timeSinceLastRefresh < 0) timeSinceLastRefresh = 0; //in case of clock adjustments
|
||||
if(timeSinceLastRefresh < MINIMUM_OVERRIDE_REFRESH*1000)
|
||||
t = (MINIMUM_OVERRIDE_REFRESH*1000-timeSinceLastRefresh);
|
||||
} else {
|
||||
window.requests._quickRefreshPending = false;
|
||||
t = REFRESH*1000;
|
||||
var adj = ZOOM_LEVEL_ADJ * (18 - window.map.getZoom());
|
||||
if(adj > 0) t += adj*1000;
|
||||
}
|
||||
var next = new Date(new Date().getTime() + t).toLocaleTimeString();
|
||||
console.log('planned refresh: ' + next);
|
||||
console.log('planned refresh in ' + (t/1000) + ' seconds, at ' + next);
|
||||
refreshTimeout = setTimeout(window.requests._callOnRefreshFunctions, t);
|
||||
renderUpdateStatus();
|
||||
}
|
||||
|
||||
window.requests._onRefreshFunctions = [];
|
||||
window.requests._callOnRefreshFunctions = function() {
|
||||
console.log('running refresh at ' + new Date().toLocaleTimeString());
|
||||
startRefreshTimeout();
|
||||
|
||||
if(isIdle()) {
|
||||
@ -101,6 +116,9 @@ window.requests._callOnRefreshFunctions = function() {
|
||||
|
||||
console.log('refreshing');
|
||||
|
||||
//store the timestamp of this refresh
|
||||
window.requests._lastRefreshTime = new Date().getTime();
|
||||
|
||||
$.each(window.requests._onRefreshFunctions, function(ind, f) {
|
||||
f();
|
||||
});
|
||||
@ -121,4 +139,4 @@ window.requests.isLastRequest = function(action) {
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
window.aboutIITC = function(){
|
||||
var v = '@@BUILDNAME@@-@@BUILDDATE@@';
|
||||
var attrib = '@@INCLUDEMD:ATTRIBUTION.md@@';
|
||||
var contrib = '@@INCLUDEMD:CONTRIBS.md@@'
|
||||
var a = ''
|
||||
+ ' <div><b>About IITC</b></div> '
|
||||
+ ' <div>Ingress Intel Total Conversion</div> '
|
||||
@ -23,7 +24,9 @@ window.aboutIITC = function(){
|
||||
+ ' <hr>'
|
||||
+ ' <div>Version: ' + v + '</div>'
|
||||
+ ' <hr>'
|
||||
+ ' <div>' + attrib + '</div>';
|
||||
+ ' <div>' + attrib + '</div>'
|
||||
+ ' <hr>'
|
||||
+ ' <div>' + contrib + '</div>';
|
||||
alert(a, true, function() {$('.ui-dialog').removeClass('ui-dialog-aboutIITC');});
|
||||
$('.ui-dialog').addClass('ui-dialog-aboutIITC');
|
||||
}
|
||||
@ -82,13 +85,14 @@ window.digits = function(d) {
|
||||
// able arguments: http://api.jquery.com/jQuery.ajax/
|
||||
// error: see above. Additionally it is logged if the request failed.
|
||||
window.postAjax = function(action, data, success, error) {
|
||||
data = JSON.stringify($.extend({method: 'dashboard.'+action}, data));
|
||||
var post_data = JSON.stringify($.extend({method: 'dashboard.'+action}, data));
|
||||
var remove = function(data, textStatus, jqXHR) { window.requests.remove(jqXHR); };
|
||||
var errCnt = function(jqXHR) { window.failedRequestCount++; window.requests.remove(jqXHR); };
|
||||
var result = $.ajax({
|
||||
url: '/rpc/dashboard.'+action,
|
||||
type: 'POST',
|
||||
data: data,
|
||||
data: post_data,
|
||||
context: data,
|
||||
dataType: 'json',
|
||||
success: [remove, success],
|
||||
error: error ? [errCnt, error] : errCnt,
|
||||
@ -346,3 +350,25 @@ window.convertTextToTableMagic = function(text) {
|
||||
window.calcTriArea = function(p) {
|
||||
return Math.abs((p[0].lat*(p[1].lng-p[2].lng)+p[1].lat*(p[2].lng-p[0].lng)+p[2].lat*(p[0].lng-p[1].lng))/2);
|
||||
}
|
||||
|
||||
// Update layerGroups display status to window.overlayStatus and cookie 'ingress.intelmap.layergroupdisplayed'
|
||||
window.updateDisplayedLayerGroup = function(name, display) {
|
||||
overlayStatus[name] = display;
|
||||
writeCookie('ingress.intelmap.layergroupdisplayed', JSON.stringify(overlayStatus));
|
||||
}
|
||||
|
||||
// Read layerGroup status from window.overlayStatus if it was added to map,
|
||||
// read from cookie if it has not added to map yet.
|
||||
// return true if both overlayStatus and cookie didn't have the record
|
||||
window.isLayerGroupDisplayed = function(name) {
|
||||
if(typeof(overlayStatus[name]) !== 'undefined') return overlayStatus[name];
|
||||
|
||||
var layersJSON = readCookie('ingress.intelmap.layergroupdisplayed');
|
||||
if(!layersJSON) return true;
|
||||
|
||||
var layers = JSON.parse(layersJSON);
|
||||
// keep latest overlayStatus
|
||||
overlayStatus = $.extend(layers, overlayStatus);
|
||||
if(typeof(overlayStatus[name]) === 'undefined') return true;
|
||||
return overlayStatus[name];
|
||||
}
|
||||
|
10
main.js
@ -1,7 +1,7 @@
|
||||
// ==UserScript==
|
||||
// @id ingress-intel-total-conversion@jonatkins
|
||||
// @name IITC: Ingress intel map total conversion
|
||||
// @version 0.11.2.@@DATETIMEVERSION@@
|
||||
// @version 0.11.3.@@DATETIMEVERSION@@
|
||||
// @namespace https://github.com/jonatkins/ingress-intel-total-conversion
|
||||
// @updateURL @@UPDATEURL@@
|
||||
// @downloadURL @@DOWNLOADURL@@
|
||||
@ -112,8 +112,10 @@ function wrapper() {
|
||||
L_PREFER_CANVAS = false;
|
||||
|
||||
// CONFIG OPTIONS ////////////////////////////////////////////////////
|
||||
window.REFRESH = 30; // refresh view every 30s (base time)
|
||||
window.REFRESH = 60; // refresh view every 60s (base time)
|
||||
window.ZOOM_LEVEL_ADJ = 5; // add 5 seconds per zoom level
|
||||
window.ON_MOVE_REFRESH = 0.8; //refresh time to use after a movement event
|
||||
window.MINIMUM_OVERRIDE_REFRESH = 5; //limit on refresh time since previous refresh, limiting repeated move refresh rate
|
||||
window.REFRESH_GAME_SCORE = 5*60; // refresh game score every 5 minutes
|
||||
window.MAX_IDLE_TIME = 4; // stop updating map after 4min idling
|
||||
window.PRECACHE_PLAYER_NAMES_ZOOM = 17; // zoom level to start pre-resolving player names
|
||||
@ -232,6 +234,10 @@ window.links = {};
|
||||
window.fields = {};
|
||||
window.resonators = {};
|
||||
|
||||
// contain current status(on/off) of overlay layerGroups.
|
||||
// But you should use isLayerGroupDisplayed(name) to check the status
|
||||
window.overlayStatus = {};
|
||||
|
||||
// plugin framework. Plugins may load earlier than iitc, so don’t
|
||||
// overwrite data
|
||||
if(typeof window.plugin !== 'function') window.plugin = function() {};
|
||||
|
1
mobile/.gitignore
vendored
@ -7,4 +7,5 @@ libs/
|
||||
proguard-project.txt
|
||||
local.properties
|
||||
assets/iitc.js
|
||||
assets/user-location.user.js
|
||||
assets/plugins/
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.cradle.iitc_mobile"
|
||||
android:versionCode="12"
|
||||
android:versionName="0.3.1" >
|
||||
android:versionCode="13"
|
||||
android:versionName="0.3.2" >
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="14"
|
||||
@ -14,7 +14,7 @@
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/iitc_icon"
|
||||
android:icon="@drawable/ic_iitcm"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme" >
|
||||
<activity
|
||||
|
BIN
mobile/res/drawable-hdpi/ic_iitcm.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 2.8 KiB |
BIN
mobile/res/drawable-ldpi/ic_iitcm.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 1.5 KiB |
BIN
mobile/res/drawable-mdpi/ic_iitcm.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 1.9 KiB |
BIN
mobile/res/drawable-xhdpi/ic_iitcm.png
Normal file
After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 3.8 KiB |
BIN
mobile/res/drawable-xxhdpi/ic_iitcm.png
Normal file
After Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 5.6 KiB |
@ -18,7 +18,7 @@
|
||||
<string name="pref_about_msg">
|
||||
<![CDATA[<big><b>Ingress Intel Total Conversion Mobile</b></big><br><br>
|
||||
<b>by <a href="https://github.com/leCradle">cradle</a></b><br><br>
|
||||
<b>Icon by <a href="https://twitter.com/machtwerk">machtwerk</a></b><br><br>
|
||||
<b>Icon by <a href="http://www.ludolab.net">Giuseppe Lucido</a></b><br><br>
|
||||
IITC Mobile is an optimized mobile browser for the
|
||||
Ingress Intel Total Conversion (IITC) userscript by <a href="https://github.com/breunigs"><b>breunigs</b></a>.
|
||||
After Niantic asked the main developer to shut down his github project, development
|
||||
@ -36,6 +36,8 @@
|
||||
<string name="pref_plugins_title">Available plugins</string>
|
||||
<string name="pref_force_desktop">Force desktop mode</string>
|
||||
<string name="pref_force_desktop_sum">Nice for tablets, looks awful on smartphones</string>
|
||||
<string name="pref_user_loc">Display user location</string>
|
||||
<string name="pref_user_loc_sum">Show users position on map</string>
|
||||
<string name="pref_developer_options">Developer options</string>
|
||||
<string name="pref_select_iitc">IITC source</string>
|
||||
|
||||
|
@ -17,6 +17,11 @@
|
||||
android:title="@string/pref_force_desktop"
|
||||
android:summary="@string/pref_force_desktop_sum"
|
||||
android:defaultValue="false" />
|
||||
<CheckBoxPreference
|
||||
android:key="pref_user_loc"
|
||||
android:title="@string/pref_user_loc"
|
||||
android:summary="@string/pref_user_loc_sum"
|
||||
android:defaultValue="false" />
|
||||
<MultiSelectListPreference
|
||||
android:key="pref_plugins"
|
||||
android:title="@string/pref_plugins"
|
||||
|
@ -4,6 +4,9 @@ import java.io.IOException;
|
||||
|
||||
import com.cradle.iitc_mobile.R;
|
||||
|
||||
import android.location.Location;
|
||||
import android.location.LocationListener;
|
||||
import android.location.LocationManager;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.Uri;
|
||||
@ -20,6 +23,7 @@ import android.content.res.Configuration;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class IITC_Mobile extends Activity {
|
||||
@ -29,6 +33,10 @@ public class IITC_Mobile extends Activity {
|
||||
private boolean desktop = false;
|
||||
private OnSharedPreferenceChangeListener listener;
|
||||
private String intel_url = "https://www.ingress.com/intel";
|
||||
private boolean user_loc = false;
|
||||
private LocationManager loc_mngr = null;
|
||||
private LocationListener loc_listener = null;
|
||||
private boolean keyboad_open = false;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@ -45,11 +53,53 @@ public class IITC_Mobile extends Activity {
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
if (key.equals("pref_force_desktop"))
|
||||
desktop = sharedPreferences.getBoolean("pref_force_desktop", false);
|
||||
if (key.equals("pref_user_loc")) {
|
||||
user_loc = sharedPreferences.getBoolean("pref_user_loc", false);
|
||||
}
|
||||
IITC_Mobile.this.loadUrl(intel_url);
|
||||
}
|
||||
};
|
||||
sharedPref.registerOnSharedPreferenceChangeListener(listener);
|
||||
|
||||
// we need this one to prevent location updates to javascript when keyboard is open
|
||||
// it closes on updates
|
||||
iitc_view.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
if ((iitc_view.getRootView().getHeight() - iitc_view.getHeight()) >
|
||||
iitc_view.getRootView().getHeight()/3) {
|
||||
Log.d("iitcm", "Open Keyboard...");
|
||||
IITC_Mobile.this.keyboad_open = true;
|
||||
} else {
|
||||
Log.d("iitcm", "Close Keyboard...");
|
||||
IITC_Mobile.this.keyboad_open = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
// Acquire a reference to the system Location Manager
|
||||
loc_mngr = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
|
||||
|
||||
// Define a listener that responds to location updates
|
||||
loc_listener = new LocationListener() {
|
||||
public void onLocationChanged(Location location) {
|
||||
// Called when a new location is found by the network location provider.
|
||||
drawMarker(location);
|
||||
}
|
||||
|
||||
public void onStatusChanged(String provider, int status, Bundle extras) {}
|
||||
|
||||
public void onProviderEnabled(String provider) {}
|
||||
|
||||
public void onProviderDisabled(String provider) {}
|
||||
};
|
||||
|
||||
user_loc = sharedPref.getBoolean("pref_user_loc", false);
|
||||
if (user_loc == true) {
|
||||
// Register the listener with the Location Manager to receive location updates
|
||||
loc_mngr.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, loc_listener);
|
||||
loc_mngr.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, loc_listener);
|
||||
}
|
||||
|
||||
// load new iitc web view with ingress intel page
|
||||
Intent intent = getIntent();
|
||||
String action = intent.getAction();
|
||||
@ -78,6 +128,12 @@ public class IITC_Mobile extends Activity {
|
||||
Log.d("iitcm", "resuming...setting reset idleTimer");
|
||||
iitc_view.loadUrl("javascript: window.idleTime = 0");
|
||||
iitc_view.loadUrl("javascript: window.renderUpdateStatus()");
|
||||
|
||||
if (user_loc == true) {
|
||||
// Register the listener with the Location Manager to receive location updates
|
||||
loc_mngr.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, loc_listener);
|
||||
loc_mngr.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, loc_listener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -107,6 +163,10 @@ public class IITC_Mobile extends Activity {
|
||||
}
|
||||
}
|
||||
Log.d("iitcm", "stopping iitcm");
|
||||
|
||||
if (user_loc == true)
|
||||
loc_mngr.removeUpdates(loc_listener);
|
||||
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@ -185,6 +245,7 @@ public class IITC_Mobile extends Activity {
|
||||
}
|
||||
}
|
||||
|
||||
// vp=f enables desktop mode...vp=m is the defaul mobile view
|
||||
private String addUrlParam(String url) {
|
||||
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
this.desktop = sharedPref.getBoolean("pref_force_desktop", false);
|
||||
@ -195,6 +256,8 @@ public class IITC_Mobile extends Activity {
|
||||
return (url + "?vp=m");
|
||||
}
|
||||
|
||||
// inject the iitc-script and load the intel url
|
||||
// plugins are injected onPageFinished
|
||||
public void loadUrl(String url) {
|
||||
url = addUrlParam(url);
|
||||
Log.d("iitcm", "injecting js...");
|
||||
@ -202,4 +265,17 @@ public class IITC_Mobile extends Activity {
|
||||
Log.d("iitcm", "loading url: " + url);
|
||||
iitc_view.loadUrl(url);
|
||||
}
|
||||
}
|
||||
|
||||
// update the user location marker on the map
|
||||
public void drawMarker(Location loc) {
|
||||
// throw away all positions with accuracy > 100 meters
|
||||
// should avoid gps glitches
|
||||
if (loc.getAccuracy() < 100) {
|
||||
if (keyboad_open == false) {
|
||||
iitc_view.loadUrl("javascript: " +
|
||||
"window.plugin.userLocation.updateLocation( " +
|
||||
loc.getLatitude() + ", " + loc.getLongitude() + ");");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -124,6 +124,25 @@ public class IITC_WebViewClient extends WebViewClient {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// inject the user location script if enabled in settings
|
||||
if (sharedPref.getBoolean("pref_user_loc", false))
|
||||
enableTracking(view);
|
||||
}
|
||||
|
||||
public void enableTracking(WebView view) {
|
||||
Log.d("iitcm", "enable tracking...");
|
||||
AssetManager am = context.getAssets();
|
||||
Scanner s = null;
|
||||
String src = "";
|
||||
try {
|
||||
s = new Scanner(am.open("user-location.user.js")).useDelimiter("\\A");
|
||||
} catch (IOException e2) {
|
||||
// TODO Auto-generated catch block
|
||||
e2.printStackTrace();
|
||||
}
|
||||
if (s != null) src = s.hasNext() ? s.next() : "";
|
||||
view.loadUrl("javascript:" + src);
|
||||
}
|
||||
|
||||
// Check every external resource if it’s okay to load it and maybe replace it
|
||||
@ -153,7 +172,7 @@ public class IITC_WebViewClient extends WebViewClient {
|
||||
// start non-ingress-intel-urls in another app...
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
if (url.contains("ingress.com")) {
|
||||
if (url.contains("ingress.com") || url.contains("appengine.google.com")) {
|
||||
// reload iitc if a poslink is clicked inside the app
|
||||
if (url.contains("intel?ll=") || (url.contains("latE6") && url.contains("lngE6"))) {
|
||||
Log.d("iitcm", "should be an internal clicked position link...reload script for: " + url);
|
||||
|
69
mobile/user-location.user.js
Normal file
@ -0,0 +1,69 @@
|
||||
// ==UserScript==
|
||||
// @id iitc-plugin-user-location@cradle
|
||||
// @name IITC plugin: User Location
|
||||
// @version 0.1.0.@@DATETIMEVERSION@@
|
||||
// @namespace https://github.com/jonatkins/ingress-intel-total-conversion
|
||||
// @updateURL @@UPDATEURL@@
|
||||
// @downloadURL @@DOWNLOADURL@@
|
||||
// @description [@@BUILDNAME@@-@@BUILDDATE@@] Show user location marker on map
|
||||
// @include https://www.ingress.com/intel*
|
||||
// @include http://www.ingress.com/intel*
|
||||
// @match https://www.ingress.com/intel*
|
||||
// @match http://www.ingress.com/intel*
|
||||
// ==/UserScript==
|
||||
|
||||
function wrapper() {
|
||||
// ensure plugin framework is there, even if iitc is not yet loaded
|
||||
if(typeof window.plugin !== 'function') window.plugin = function() {};
|
||||
|
||||
|
||||
// PLUGIN START ////////////////////////////////////////////////////////
|
||||
|
||||
window.plugin.userLocation = function() {};
|
||||
|
||||
window.plugin.userLocation.marker = {};
|
||||
window.plugin.userLocation.locationLayer = new L.LayerGroup();
|
||||
|
||||
window.plugin.userLocation.setup = function() {
|
||||
|
||||
var iconImage = '@@INCLUDEIMAGE:images/marker-icon.png@@';
|
||||
var iconRetImage = '@@INCLUDEIMAGE:images/marker-icon_2x.png@@';
|
||||
|
||||
plugin.userLocation.icon = L.Icon.Default.extend({options: {
|
||||
iconUrl: iconImage,
|
||||
iconRetinaUrl: iconRetImage
|
||||
}});
|
||||
|
||||
var marker = L.marker(window.map.getCenter(), {
|
||||
title: "User Location",
|
||||
icon: new plugin.userLocation.icon()
|
||||
});
|
||||
plugin.userLocation.marker = marker;
|
||||
marker.addTo(window.map);
|
||||
// jQueryUI doesn’t automatically notice the new markers
|
||||
window.setupTooltips($(marker._icon));
|
||||
};
|
||||
|
||||
window.plugin.userLocation.updateLocation = function(lat, lng) {
|
||||
var latlng = new L.LatLng(lat, lng);
|
||||
window.plugin.userLocation.marker.setLatLng(latlng);
|
||||
}
|
||||
|
||||
var setup = window.plugin.userLocation.setup;
|
||||
|
||||
// PLUGIN END //////////////////////////////////////////////////////////
|
||||
|
||||
if(window.iitcLoaded && typeof setup === 'function') {
|
||||
setup();
|
||||
} else {
|
||||
if(window.bootPlugins)
|
||||
window.bootPlugins.push(setup);
|
||||
else
|
||||
window.bootPlugins = [setup];
|
||||
}
|
||||
} // wrapper end
|
||||
// inject code into site context
|
||||
var script = document.createElement('script');
|
||||
script.appendChild(document.createTextNode('('+ wrapper +')();'));
|
||||
(document.body || document.head || document.documentElement).appendChild(script);
|
||||
|
@ -1,7 +1,7 @@
|
||||
// ==UserScript==
|
||||
// @id iitc-plugin-keys-on-map@xelio
|
||||
// @name IITC plugin: Keys on map
|
||||
// @version 0.1.0.@@DATETIMEVERSION@@
|
||||
// @version 0.2.0.@@DATETIMEVERSION@@
|
||||
// @namespace https://github.com/jonatkins/ingress-intel-total-conversion
|
||||
// @updateURL @@UPDATEURL@@
|
||||
// @downloadURL @@DOWNLOADURL@@
|
||||
@ -90,7 +90,7 @@ window.plugin.keysOnMap.disableMessage = function() {
|
||||
}
|
||||
}
|
||||
|
||||
var setup = function() {
|
||||
window.plugin.keysOnMap.setupCSS = function() {
|
||||
$("<style>")
|
||||
.prop("type", "text/css")
|
||||
.html(".plugin-keys-on-map-key {\
|
||||
@ -103,9 +103,18 @@ var setup = function() {
|
||||
-webkit-text-size-adjust:none;\
|
||||
}")
|
||||
.appendTo("head");
|
||||
}
|
||||
|
||||
window.plugin.keysOnMap.setupLayer = function() {
|
||||
window.layerChooser.addOverlay(window.plugin.keysOnMap.keyLayerGroup, 'Keys');
|
||||
map.addLayer(window.plugin.keysOnMap.keyLayerGroup);
|
||||
if(isLayerGroupDisplayed('Keys'))
|
||||
map.addLayer(window.plugin.keysOnMap.keyLayerGroup);
|
||||
}
|
||||
|
||||
var setup = function() {
|
||||
|
||||
window.plugin.keysOnMap.setupCSS();
|
||||
window.plugin.keysOnMap.setupLayer();
|
||||
|
||||
// Avoid error if this plugin load first
|
||||
if($.inArray('pluginKeysUpdateKey', window.VALID_HOOKS) < 0)
|
||||
|
@ -1,7 +1,7 @@
|
||||
// ==UserScript==
|
||||
// @id iitc-plugin-player-tracker@breunigs
|
||||
// @name IITC Plugin: Player tracker
|
||||
// @version 0.9.2.@@DATETIMEVERSION@@
|
||||
// @version 0.9.3.@@DATETIMEVERSION@@
|
||||
// @namespace https://github.com/jonatkins/ingress-intel-total-conversion
|
||||
// @updateURL @@UPDATEURL@@
|
||||
// @downloadURL @@DOWNLOADURL@@
|
||||
@ -49,6 +49,14 @@ window.plugin.playerTracker.setup = function() {
|
||||
plugin.playerTracker.drawnTraces = new L.LayerGroup();
|
||||
window.layerChooser.addOverlay(plugin.playerTracker.drawnTraces, 'Player Tracker');
|
||||
map.addLayer(plugin.playerTracker.drawnTraces);
|
||||
map.on('layeradd',function(obj) {
|
||||
if(obj.layer === plugin.playerTracker.drawnTraces)
|
||||
{
|
||||
obj.layer.eachLayer(function(marker) {
|
||||
if(marker._icon) window.setupTooltips($(marker._icon));
|
||||
});
|
||||
}
|
||||
});
|
||||
plugin.playerTracker.oms = new OverlappingMarkerSpiderfier(map);
|
||||
plugin.playerTracker.oms.legColors = {'usual': '#FFFF00', 'highlighted': '#FF0000'};
|
||||
plugin.playerTracker.oms.legWeight = 3.5;
|
||||
|
100
plugins/portal-level-numbers.user.js
Normal file
@ -0,0 +1,100 @@
|
||||
// ==UserScript==
|
||||
// @id iitc-plugin-portal-level-numbers@rongou
|
||||
// @name IITC plugin: Portal Level Numbers
|
||||
// @version 0.1.0.@@DATETIMEVERSION@@
|
||||
// @namespace https://github.com/jonatkins/ingress-intel-total-conversion
|
||||
// @updateURL @@UPDATEURL@@
|
||||
// @downloadURL @@DOWNLOADURL@@
|
||||
// @description [@@BUILDNAME@@-@@BUILDDATE@@] Show portal level numbers on map.
|
||||
// @include https://www.ingress.com/intel*
|
||||
// @include http://www.ingress.com/intel*
|
||||
// @match https://www.ingress.com/intel*
|
||||
// @match http://www.ingress.com/intel*
|
||||
// ==/UserScript==
|
||||
|
||||
function wrapper() {
|
||||
// ensure plugin framework is there, even if iitc is not yet loaded
|
||||
if(typeof window.plugin !== 'function') window.plugin = function() {};
|
||||
|
||||
|
||||
// PLUGIN START ////////////////////////////////////////////////////////
|
||||
|
||||
// use own namespace for plugin
|
||||
window.plugin.portalLevelNumbers = function() {};
|
||||
|
||||
window.plugin.portalLevelNumbers.levelLayers = {};
|
||||
window.plugin.portalLevelNumbers.levelLayerGroup = new L.LayerGroup();
|
||||
|
||||
// Use portal add and remove event to control render of portal level numbers
|
||||
window.plugin.portalLevelNumbers.portalAdded = function(data) {
|
||||
data.portal.on('add', function() {
|
||||
plugin.portalLevelNumbers.renderLevel(this.options.guid, this.getLatLng());
|
||||
});
|
||||
|
||||
data.portal.on('remove', function() {
|
||||
plugin.portalLevelNumbers.removeLevel(this.options.guid);
|
||||
});
|
||||
}
|
||||
|
||||
window.plugin.portalLevelNumbers.renderLevel = function(guid,latLng) {
|
||||
plugin.portalLevelNumbers.removeLevel(guid);
|
||||
|
||||
var d = window.portals[guid].options.details;
|
||||
var levelNumber = Math.floor(window.getPortalLevel(d));
|
||||
var level = L.marker(latLng, {
|
||||
icon: L.divIcon({
|
||||
className: 'plugin-portal-level-numbers',
|
||||
iconAnchor: [6,7],
|
||||
iconSize: [12,10],
|
||||
html: levelNumber
|
||||
}),
|
||||
guid: guid
|
||||
});
|
||||
|
||||
plugin.portalLevelNumbers.levelLayers[guid] = level;
|
||||
level.addTo(plugin.portalLevelNumbers.levelLayerGroup);
|
||||
}
|
||||
|
||||
window.plugin.portalLevelNumbers.removeLevel = function(guid) {
|
||||
var previousLayer = plugin.portalLevelNumbers.levelLayers[guid];
|
||||
if(previousLayer) {
|
||||
plugin.portalLevelNumbers.levelLayerGroup.removeLayer(previousLayer);
|
||||
delete plugin.portalLevelNumbers.levelLayers[guid];
|
||||
}
|
||||
}
|
||||
|
||||
var setup = function() {
|
||||
$("<style>")
|
||||
.prop("type", "text/css")
|
||||
.html(".plugin-portal-level-numbers {\
|
||||
font-size: 10px;\
|
||||
color: #FFFFBB;\
|
||||
font-family: monospace;\
|
||||
text-align: center;\
|
||||
text-shadow: 0 0 0.5em black, 0 0 0.5em black, 0 0 0.5em black;\
|
||||
pointer-events: none;\
|
||||
-webkit-text-size-adjust:none;\
|
||||
}")
|
||||
.appendTo("head");
|
||||
|
||||
window.layerChooser.addOverlay(window.plugin.portalLevelNumbers.levelLayerGroup, 'Portal Levels');
|
||||
map.addLayer(window.plugin.portalLevelNumbers.levelLayerGroup);
|
||||
|
||||
window.addHook('portalAdded', window.plugin.portalLevelNumbers.portalAdded);
|
||||
}
|
||||
|
||||
// PLUGIN END //////////////////////////////////////////////////////////
|
||||
|
||||
if(window.iitcLoaded && typeof setup === 'function') {
|
||||
setup();
|
||||
} else {
|
||||
if(window.bootPlugins)
|
||||
window.bootPlugins.push(setup);
|
||||
else
|
||||
window.bootPlugins = [setup];
|
||||
}
|
||||
} // wrapper end
|
||||
// inject code into site context
|
||||
var script = document.createElement('script');
|
||||
script.appendChild(document.createTextNode('('+ wrapper +')();'));
|
||||
(document.body || document.head || document.documentElement).appendChild(script);
|
BIN
website/apple-touch-icon-precomposed.png
Normal file
After Width: | Height: | Size: 18 KiB |
11
website/assets/html5shiv/html5shiv-printshiv.js
Normal file
@ -0,0 +1,11 @@
|
||||
/*
|
||||
HTML5 Shiv v3.6.2 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
|
||||
*/
|
||||
(function(j,f){function s(a,b){var c=a.createElement("p"),m=a.getElementsByTagName("head")[0]||a.documentElement;c.innerHTML="x<style>"+b+"</style>";return m.insertBefore(c.lastChild,m.firstChild)}function o(){var a=d.elements;return"string"==typeof a?a.split(" "):a}function n(a){var b=t[a[u]];b||(b={},p++,a[u]=p,t[p]=b);return b}function v(a,b,c){b||(b=f);if(e)return b.createElement(a);c||(c=n(b));b=c.cache[a]?c.cache[a].cloneNode():y.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);
|
||||
return b.canHaveChildren&&!z.test(a)?c.frag.appendChild(b):b}function A(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag();a.createElement=function(c){return!d.shivMethods?b.createElem(c):v(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+o().join().replace(/\w+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(d,b.frag)}
|
||||
function w(a){a||(a=f);var b=n(a);if(d.shivCSS&&!q&&!b.hasCSS)b.hasCSS=!!s(a,"article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}");e||A(a,b);return a}function B(a){for(var b,c=a.attributes,m=c.length,f=a.ownerDocument.createElement(l+":"+a.nodeName);m--;)b=c[m],b.specified&&f.setAttribute(b.nodeName,b.nodeValue);f.style.cssText=a.style.cssText;return f}function x(a){function b(){clearTimeout(d._removeSheetTimer);c&&c.removeNode(!0);
|
||||
c=null}var c,f,d=n(a),e=a.namespaces,j=a.parentWindow;if(!C||a.printShived)return a;"undefined"==typeof e[l]&&e.add(l);j.attachEvent("onbeforeprint",function(){b();var g,i,d;d=a.styleSheets;for(var e=[],h=d.length,k=Array(h);h--;)k[h]=d[h];for(;d=k.pop();)if(!d.disabled&&D.test(d.media)){try{g=d.imports,i=g.length}catch(j){i=0}for(h=0;h<i;h++)k.push(g[h]);try{e.push(d.cssText)}catch(n){}}g=e.reverse().join("").split("{");i=g.length;h=RegExp("(^|[\\s,>+~])("+o().join("|")+")(?=[[\\s,>+~#.:]|$)","gi");
|
||||
for(k="$1"+l+"\\:$2";i--;)e=g[i]=g[i].split("}"),e[e.length-1]=e[e.length-1].replace(h,k),g[i]=e.join("}");e=g.join("{");i=a.getElementsByTagName("*");h=i.length;k=RegExp("^(?:"+o().join("|")+")$","i");for(d=[];h--;)g=i[h],k.test(g.nodeName)&&d.push(g.applyElement(B(g)));f=d;c=s(a,e)});j.attachEvent("onafterprint",function(){for(var a=f,c=a.length;c--;)a[c].removeNode();clearTimeout(d._removeSheetTimer);d._removeSheetTimer=setTimeout(b,500)});a.printShived=!0;return a}var r=j.html5||{},z=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,
|
||||
y=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q,u="_html5shiv",p=0,t={},e;(function(){try{var a=f.createElement("a");a.innerHTML="<xyz></xyz>";q="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode||"undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}e=b}catch(d){e=q=!0}})();var d={elements:r.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup main mark meter nav output progress section summary time video",
|
||||
version:"3.6.2",shivCSS:!1!==r.shivCSS,supportsUnknownElements:e,shivMethods:!1!==r.shivMethods,type:"default",shivDocument:w,createElement:v,createDocumentFragment:function(a,b){a||(a=f);if(e)return a.createDocumentFragment();for(var b=b||n(a),c=b.frag.cloneNode(),d=0,j=o(),l=j.length;d<l;d++)c.createElement(j[d]);return c}};j.html5=d;w(f);var D=/^$|\b(?:all|print)\b/,l="html5shiv",C=!e&&function(){var a=f.documentElement;return!("undefined"==typeof f.namespaces||"undefined"==typeof f.parentWindow||
|
||||
"undefined"==typeof a.applyElement||"undefined"==typeof a.removeNode||"undefined"==typeof j.attachEvent)}();d.type+=" print";d.shivPrint=x;x(f)})(this,document);
|
8
website/assets/html5shiv/html5shiv.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/*
|
||||
HTML5 Shiv v3.6.2 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
|
||||
*/
|
||||
(function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag();
|
||||
a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/\w+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x<style>article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}</style>";
|
||||
c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="<xyz></xyz>";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode||
|
||||
"undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup main mark meter nav output progress section summary time video",version:"3.6.2",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f);if(g)return a.createDocumentFragment();
|
||||
for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d<h;d++)c.createElement(e[d]);return c}};l.html5=e;q(f)})(this,document);
|
BIN
website/assets/img/eff-logo.png
Normal file
After Width: | Height: | Size: 7.8 KiB |
BIN
website/assets/img/fsf-logo.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
@ -1,40 +1,3 @@
|
||||
<?php
|
||||
|
||||
$path = "release";
|
||||
|
||||
if ( $_REQUEST['build'] == 'dev' )
|
||||
$path = "dev";
|
||||
|
||||
|
||||
function loadUserScriptHeader($path)
|
||||
{
|
||||
$result = Array();
|
||||
|
||||
$f = fopen ( $path, "rt" );
|
||||
while ( ( $line = fgets ( $f ) ) !== FALSE )
|
||||
{
|
||||
if ( preg_match ( '#//[ \\t]*==/UserScript==#', $line ) )
|
||||
break;
|
||||
|
||||
$matches = Array();
|
||||
if ( preg_match ( '#^//[ \\t]*(@[a-zA-Z0-9]+)[ \\t]+(.*)$#', $line, $matches ) )
|
||||
{
|
||||
$name = $matches[1];
|
||||
$value = $matches[2];
|
||||
|
||||
if ( ! array_key_exists ( $name, $result ) )
|
||||
{
|
||||
$result[$name] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fclose ( $f );
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
@ -53,9 +16,12 @@ function loadUserScriptHeader($path)
|
||||
|
||||
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="assets/js/html5shiv.js"></script>
|
||||
<script src="assets/html5shiv/html5shiv.js"></script>
|
||||
<![endif]-->
|
||||
|
||||
<!-- android uses the apple icons when adding shortcuts - looks better than favicons -->
|
||||
<link rel="apple-touch-icon-precomposed" href="/apple-touch-icon-precomposed.png">
|
||||
|
||||
<style>
|
||||
.nowrap { white-space: nowrap; }
|
||||
</style>
|
||||
@ -114,8 +80,10 @@ $pages = Array (
|
||||
'faq' => '<i class="icon-question-sign"></i> FAQ',
|
||||
'desktop' => '<i class="icon-chevron-right"></i> Desktop',
|
||||
'mobile' => '<i class="icon-chevron-right"></i> Mobile',
|
||||
'test' => '<i class="icon-wrench"></i> Test Builds',
|
||||
'developer' => '<i class="icon-cog"></i> Developers',
|
||||
'about' => '<i class="icon-info-sign"></i> About',
|
||||
'donate' => '<i class="icon-gift"></i> Donate',
|
||||
);
|
||||
|
||||
$page = $_REQUEST['page'];
|
||||
@ -137,7 +105,7 @@ foreach ( $pages as $key => $name )
|
||||
print "<li".($page == $key ? ' class="active"' :'')."><a href=\"$url\">$name</a></li>\n";
|
||||
|
||||
# after 'mobile', add a horizontal seperator
|
||||
if ( $key == 'mobile' )
|
||||
if ( $key == 'test' )
|
||||
print "<li class=\"divider\"></li>";
|
||||
}
|
||||
|
||||
|
76
website/page/code/apk/ApkParser.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
include_once dirname(__FILE__) . "/lib/ApkArchive.php";
|
||||
include_once dirname(__FILE__) . "/lib/ApkXmlParser.php";
|
||||
include_once dirname(__FILE__) . "/lib/ApkManifest.php";
|
||||
|
||||
/**
|
||||
* @author Tufan Baris YILDIRIM
|
||||
* @version v0.1
|
||||
* @since 27.03.2012
|
||||
* @link https://github.com/tufanbarisyildirim/php-apk-parser
|
||||
*
|
||||
* Main Class.
|
||||
* - Set the apk path on construction,
|
||||
* - Get the Manifest object.
|
||||
* - Print the Manifest XML.
|
||||
*
|
||||
* @todo Add getPackageName();
|
||||
* @todo Add getVersion();
|
||||
* @todo Add getUsesSdk();
|
||||
* @todo Add getMinSdk();
|
||||
*/
|
||||
class ApkParser
|
||||
{
|
||||
/**
|
||||
* @var ApkArchive
|
||||
*/
|
||||
private $apk;
|
||||
|
||||
/**
|
||||
* AndrodiManifest.xml
|
||||
*
|
||||
* @var ApkManifest
|
||||
*/
|
||||
private $manifest;
|
||||
|
||||
public function __construct($apkFile)
|
||||
{
|
||||
$this->apk = new ApkArchive($apkFile);
|
||||
$this->manifest = new ApkManifest(new ApkXmlParser($this->apk->getManifestStream()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Manifest Object
|
||||
* @return ApkManifest
|
||||
*/
|
||||
public function getManifest()
|
||||
{
|
||||
return $this->manifest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the apk. Zip handler.
|
||||
* - Extract all(or sp. entries) files,
|
||||
* - add file,
|
||||
* - recompress
|
||||
* - and other ZipArchive features.
|
||||
*
|
||||
* @return ApkArchive
|
||||
*/
|
||||
public function getApkArchive()
|
||||
{
|
||||
return $this->apk;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract apk content directly
|
||||
*
|
||||
* @param mixed $destination
|
||||
* @param array $entries
|
||||
* @return bool
|
||||
*/
|
||||
public function extractTo($destination,$entries = NULL)
|
||||
{
|
||||
return $this->apk->extractTo($destination,$entries);
|
||||
}
|
||||
}
|
47
website/page/code/apk/lib/ApkAndroidPlatform.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* @todo : Write comments!
|
||||
*/
|
||||
class ApkAndroidPlatform
|
||||
{
|
||||
public $level = NULL;
|
||||
public $platform = NULL;
|
||||
|
||||
private static $platforms = array(
|
||||
2 => array('name' => 'Android 1.1 Platfrom'),
|
||||
3 => array('name' => 'Android 1.5 Platfrom'),
|
||||
4 => array('name' => 'Android 1.6 Platfrom'),
|
||||
5 => array('name' => 'Android 2.0 Platfrom'),
|
||||
6 => array('name' => 'Android 2.0.1 Platfrom'),
|
||||
7 => array('name' => 'Android 2.1 Platfrom'),
|
||||
8 => array('name' => 'Android 2.2 Platfrom'),
|
||||
9 => array('name' => 'Android 2.3 Platfrom'),
|
||||
10 => array('name' => 'Android 2.3.3 Platfrom'),
|
||||
10 => array('name' => 'Android 2.3.3 / 2.3.4 Platfroms'),
|
||||
11 => array('name' => 'Android 3.0 Platfroms'),
|
||||
12 => array('name' => 'Android 3.1 Platfroms'),
|
||||
13 => array('name' => 'Android 3.2 Platfroms'),
|
||||
14 => array('name' => 'Android 4.0 Platfroms'),
|
||||
14 => array('name' => 'Android 4.0.3 Platfroms'),
|
||||
);
|
||||
|
||||
public function __construct($apiLevel)
|
||||
{
|
||||
$this->level = $apiLevel;
|
||||
$this->platform = $this->getPlatform();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getPlatform()
|
||||
{
|
||||
if(!isset(self::$platforms[$this->level]))
|
||||
throw new Exception("Unknown api level.");
|
||||
|
||||
$platform = self::$platforms[$this->level];
|
||||
$platform['level'] = $this->level;
|
||||
return $platform;
|
||||
}
|
||||
|
||||
}
|
117
website/page/code/apk/lib/ApkArchive.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
include_once dirname(__FILE__) . "/ApkStream.php";
|
||||
|
||||
/**
|
||||
* Customized ZipArchive for .apk files.
|
||||
* @author Tufan Baris YILDIRIM
|
||||
* @TODO Add ->getResource('file_name'), or getIcon() directly.
|
||||
* @todo Override the // extractTo() method. Rewrite all of XML files converted from Binary Xml to text based XML!
|
||||
*/
|
||||
class ApkArchive extends ZipArchive
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $filePath;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $fileName;
|
||||
|
||||
|
||||
public function __construct($file = false)
|
||||
{
|
||||
if($file && is_file($file))
|
||||
{
|
||||
$this->open($file);
|
||||
$this->fileName = basename($this->filePath = $file);
|
||||
}
|
||||
else
|
||||
throw new Exception($file . " not a regular file");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a file from apk Archive by name.
|
||||
*
|
||||
* @param string $name
|
||||
* @param int $length
|
||||
* @param int $flags
|
||||
* @return mixed
|
||||
*/
|
||||
public function getFromName($name,$length = NULL,$flags = NULL)
|
||||
{
|
||||
if(strtolower(substr($name,-4)) == '.xml')
|
||||
{
|
||||
$xmlParser = new ApkXmlParser(new ApkStream($this->getStream($name)));
|
||||
return $xmlParser->getXmlString();
|
||||
}
|
||||
else
|
||||
return parent::getFromName($name,$length,$flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an ApkStream whick contains AndroidManifest.xml
|
||||
* @return ApkStream
|
||||
*/
|
||||
public function getManifestStream()
|
||||
{
|
||||
return new ApkStream($this->getStream('AndroidManifest.xml'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Apk file path.
|
||||
* @return string
|
||||
*/
|
||||
public function getApkPath()
|
||||
{
|
||||
return $this->filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apk file name
|
||||
* @return string
|
||||
*/
|
||||
public function getApkName()
|
||||
{
|
||||
return $this->fileName;
|
||||
}
|
||||
|
||||
|
||||
public function extractTo($destination,$entries = NULL)
|
||||
{
|
||||
if($extResult = parent::extractTo($destination,$entries))
|
||||
{
|
||||
//TODO: ApkXmlParser can not parse the main.xml and others! only AndroidManifest.xml
|
||||
//return $extResult;
|
||||
|
||||
$xmlFiles = $this->glob_recursive($destination . '/*.xml');
|
||||
|
||||
|
||||
foreach($xmlFiles as $xmlFile)
|
||||
{
|
||||
// TODO : Remove this ifcheck , if ApkXml can parse! amk!
|
||||
if($xmlFile == "AndroidManifest.xml")
|
||||
ApkXmlParser::decompressFile($xmlFile);
|
||||
}
|
||||
}
|
||||
|
||||
return $extResult;
|
||||
|
||||
}
|
||||
|
||||
// Can Move to the Utils(???) class.
|
||||
private function glob_recursive($pattern, $flags = 0)
|
||||
{
|
||||
$files = glob($pattern, $flags);
|
||||
|
||||
foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir)
|
||||
{
|
||||
$files = array_merge($files, $this->glob_recursive($dir.'/'.basename($pattern), $flags));
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
}
|
269
website/page/code/apk/lib/ApkManifest.php
Normal file
@ -0,0 +1,269 @@
|
||||
<?php
|
||||
include_once dirname(__FILE__). '/ApkXml.php';
|
||||
include_once dirname(__FILE__). '/ApkManifestXmlElement.php';
|
||||
include_once dirname(__FILE__). '/ApkAndroidPlatform.php';
|
||||
|
||||
/**
|
||||
* ApkManifest
|
||||
* -- description is coming.
|
||||
*
|
||||
* @todo Add getPackageName();
|
||||
* @todo Add getVersion();
|
||||
* @todo Add getUsesSdk();
|
||||
* @todo Add getMinSdk();
|
||||
*/
|
||||
class ApkManifest extends ApkXml
|
||||
{
|
||||
/**
|
||||
* @var ApkXmlParser
|
||||
*/
|
||||
private $xmlParser;
|
||||
|
||||
private $attrs = null;
|
||||
|
||||
public function __construct(ApkXmlParser $xmlParser)
|
||||
{
|
||||
$this->xmlParser = $xmlParser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns ManifestXml as a String.
|
||||
* @return string
|
||||
*/
|
||||
public function getXmlString()
|
||||
{
|
||||
return $this->xmlParser->getXmlString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Application Permissions
|
||||
* @return array
|
||||
*/
|
||||
public function getPermissions()
|
||||
{
|
||||
return $this->getXmlObject()->getPermissions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Android Package Name
|
||||
* @return string
|
||||
*/
|
||||
public function getPackageName()
|
||||
{
|
||||
return $this->getAttribute('package');
|
||||
}
|
||||
|
||||
/**
|
||||
* Application Version Name
|
||||
* @return string
|
||||
*/
|
||||
public function getVersionName()
|
||||
{
|
||||
return $this->getAttribute('versionName');
|
||||
}
|
||||
|
||||
/**
|
||||
* Application Version Code
|
||||
* @return mixed
|
||||
*/
|
||||
public function getVersionCode()
|
||||
{
|
||||
return hexdec( $this->getAttribute('versionCode') );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isDebuggable()
|
||||
{
|
||||
return (bool)$this->getAttribute('debuggable');
|
||||
}
|
||||
|
||||
/**
|
||||
* The minimum API Level required for the application to run.
|
||||
* @return int
|
||||
*/
|
||||
public function getMinSdkLevel()
|
||||
{
|
||||
$xmlObj = $this->getXmlObject();
|
||||
$usesSdk = get_object_vars($xmlObj->{'uses-sdk'});
|
||||
return hexdec($usesSdk['@attributes']['minSdkVersion']);
|
||||
}
|
||||
|
||||
private function getAttribute($attributeName)
|
||||
{
|
||||
if($this->attrs === NULL)
|
||||
{
|
||||
$xmlObj = $this->getXmlObject();
|
||||
$vars = get_object_vars($xmlObj->attributes());
|
||||
$this->attrs = $vars['@attributes'];
|
||||
}
|
||||
|
||||
if(!isset($this->attrs[$attributeName]))
|
||||
throw new Exception("Attribute not found : " . $attributeName);
|
||||
|
||||
return $this->attrs[$attributeName];
|
||||
}
|
||||
|
||||
/**
|
||||
* More Information About The minimum API Level required for the application to run.
|
||||
* @return ApkAndroidPlatform
|
||||
*/
|
||||
public function getMinSdk()
|
||||
{
|
||||
return new ApkAndroidPlatform($this->getMinSdkLevel());
|
||||
}
|
||||
|
||||
/**
|
||||
* get SimleXmlElement created from AndroidManifest.xml
|
||||
*
|
||||
* @param mixed $className
|
||||
* @return ApkManifestXmlElement
|
||||
*/
|
||||
public function getXmlObject($className = 'ApkManifestXmlElement')
|
||||
{
|
||||
return $this->xmlParser->getXmlObject($className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basicly string casting method.
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->getXmlString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Android Permissions list
|
||||
* @see http://developer.android.com/reference/android/Manifest.permission.html
|
||||
*
|
||||
* @todo: Move to {lang}_perms.php file, for easly translations.
|
||||
* @var mixed
|
||||
*/
|
||||
public static $permissions = array(
|
||||
'ACCESS_CHECKIN_PROPERTIES' => 'Allows read/write access to the "properties" table in the checkin database, to change values that get uploaded.',
|
||||
'ACCESS_COARSE_LOCATION' => 'Allows an application to access coarse (e.g., Cell-ID, WiFi) location',
|
||||
'ACCESS_FINE_LOCATION' => 'Allows an application to access fine (e.g., GPS) location',
|
||||
'ACCESS_LOCATION_EXTRA_COMMANDS' => 'Allows an application to access extra location provider commands',
|
||||
'ACCESS_MOCK_LOCATION' => 'Allows an application to create mock location providers for testing',
|
||||
'ACCESS_NETWORK_STATE' => 'Allows applications to access information about networks',
|
||||
'ACCESS_SURFACE_FLINGER' => 'Allows an application to use SurfaceFlinger\'s low level features',
|
||||
'ACCESS_WIFI_STATE' => 'Allows applications to access information about Wi-Fi networks',
|
||||
'ACCOUNT_MANAGER' => 'Allows applications to call into AccountAuthenticators.',
|
||||
'ADD_VOICEMAIL' => 'Allows an application to add voicemails into the system.',
|
||||
'AUTHENTICATE_ACCOUNTS' => 'Allows an application to act as an AccountAuthenticator for the AccountManager',
|
||||
'BATTERY_STATS' => 'Allows an application to collect battery statistics',
|
||||
'BIND_APPWIDGET' => 'Allows an application to tell the AppWidget service which application can access AppWidget\'s data.',
|
||||
'BIND_DEVICE_ADMIN' => 'Must be required by device administration receiver, to ensure that only the system can interact with it.',
|
||||
'BIND_INPUT_METHOD' => 'Must be required by an InputMethodService, to ensure that only the system can bind to it.',
|
||||
'BIND_REMOTEVIEWS' => 'Must be required by a RemoteViewsService, to ensure that only the system can bind to it.',
|
||||
'BIND_TEXT_SERVICE' => 'Must be required by a TextService (e.g.',
|
||||
'BIND_VPN_SERVICE' => 'Must be required by an VpnService, to ensure that only the system can bind to it.',
|
||||
'BIND_WALLPAPER' => 'Must be required by a WallpaperService, to ensure that only the system can bind to it.',
|
||||
'BLUETOOTH' => 'Allows applications to connect to paired bluetooth devices',
|
||||
'BLUETOOTH_ADMIN' => 'Allows applications to discover and pair bluetooth devices',
|
||||
'BRICK' => 'Required to be able to disable the device (very dangerous!).',
|
||||
'BROADCAST_PACKAGE_REMOVED' => 'Allows an application to broadcast a notification that an application package has been removed.',
|
||||
'BROADCAST_SMS' => 'Allows an application to broadcast an SMS receipt notification',
|
||||
'BROADCAST_STICKY' => 'Allows an application to broadcast sticky intents.',
|
||||
'BROADCAST_WAP_PUSH' => 'Allows an application to broadcast a WAP PUSH receipt notification',
|
||||
'CALL_PHONE' => 'Allows an application to initiate a phone call without going through the Dialer user interface for the user to confirm the call being placed.',
|
||||
'CALL_PRIVILEGED' => 'Allows an application to call any phone number, including emergency numbers, without going through the Dialer user interface for the user to confirm the call being placed.',
|
||||
'CAMERA' => 'Required to be able to access the camera device.',
|
||||
'CHANGE_COMPONENT_ENABLED_STATE' => 'Allows an application to change whether an application component (other than its own) is enabled or not.',
|
||||
'CHANGE_CONFIGURATION' => 'Allows an application to modify the current configuration, such as locale.',
|
||||
'CHANGE_NETWORK_STATE' => 'Allows applications to change network connectivity state',
|
||||
'CHANGE_WIFI_MULTICAST_STATE' => 'Allows applications to enter Wi-Fi Multicast mode',
|
||||
'CHANGE_WIFI_STATE' => 'Allows applications to change Wi-Fi connectivity state',
|
||||
'CLEAR_APP_CACHE' => 'Allows an application to clear the caches of all installed applications on the device.',
|
||||
'CLEAR_APP_USER_DATA' => 'Allows an application to clear user data',
|
||||
'CONTROL_LOCATION_UPDATES' => 'Allows enabling/disabling location update notifications from the radio.',
|
||||
'DELETE_CACHE_FILES' => 'Allows an application to delete cache files.',
|
||||
'DELETE_PACKAGES' => 'Allows an application to delete packages.',
|
||||
'DEVICE_POWER' => 'Allows low-level access to power management',
|
||||
'DIAGNOSTIC' => 'Allows applications to RW to diagnostic resources.',
|
||||
'DISABLE_KEYGUARD' => 'Allows applications to disable the keyguard',
|
||||
'DUMP' => 'Allows an application to retrieve state dump information from system services.',
|
||||
'EXPAND_STATUS_BAR' => 'Allows an application to expand or collapse the status bar.',
|
||||
'FACTORY_TEST' => 'Run as a manufacturer test application, running as the root user.',
|
||||
'FLASHLIGHT' => 'Allows access to the flashlight',
|
||||
'FORCE_BACK' => 'Allows an application to force a BACK operation on whatever is the top activity.',
|
||||
'GET_ACCOUNTS' => 'Allows access to the list of accounts in the Accounts Service',
|
||||
'GET_PACKAGE_SIZE' => 'Allows an application to find out the space used by any package.',
|
||||
'GET_TASKS' => 'Allows an application to get information about the currently or recently running tasks: a thumbnail representation of the tasks, what activities are running in it, etc.',
|
||||
'GLOBAL_SEARCH' => 'This permission can be used on content providers to allow the global search system to access their data.',
|
||||
'HARDWARE_TEST' => 'Allows access to hardware peripherals.',
|
||||
'INJECT_EVENTS' => 'Allows an application to inject user events (keys, touch, trackball) into the event stream and deliver them to ANY window.',
|
||||
'INSTALL_LOCATION_PROVIDER' => 'Allows an application to install a location provider into the Location Manager',
|
||||
'INSTALL_PACKAGES' => 'Allows an application to install packages.',
|
||||
'INTERNAL_SYSTEM_WINDOW' => 'Allows an application to open windows that are for use by parts of the system user interface.',
|
||||
'INTERNET' => 'Allows applications to open network sockets.',
|
||||
'KILL_BACKGROUND_PROCESSES' => 'Allows an application to call killBackgroundProcesses(String).',
|
||||
'MANAGE_ACCOUNTS' => 'Allows an application to manage the list of accounts in the AccountManager',
|
||||
'MANAGE_APP_TOKENS' => 'Allows an application to manage (create, destroy, Z-order) application tokens in the window manager.',
|
||||
'MASTER_CLEAR' => '',
|
||||
'MODIFY_AUDIO_SETTINGS' => 'Allows an application to modify global audio settings',
|
||||
'MODIFY_PHONE_STATE' => 'Allows modification of the telephony state - power on, mmi, etc.',
|
||||
'MOUNT_FORMAT_FILESYSTEMS' => 'Allows formatting file systems for removable storage.',
|
||||
'MOUNT_UNMOUNT_FILESYSTEMS' => 'Allows mounting and unmounting file systems for removable storage.',
|
||||
'NFC' => 'Allows applications to perform I/O operations over NFC',
|
||||
'PERSISTENT_ACTIVITY' => 'This constant is deprecated. This functionality will be removed in the future; please do not use. Allow an application to make its activities persistent.',
|
||||
'PROCESS_OUTGOING_CALLS' => 'Allows an application to monitor, modify, or abort outgoing calls.',
|
||||
'READ_CALENDAR' => 'Allows an application to read the user\'s calendar data.',
|
||||
'READ_CONTACTS' => 'Allows an application to read the user\'s contacts data.',
|
||||
'READ_FRAME_BUFFER' => 'Allows an application to take screen shots and more generally get access to the frame buffer data',
|
||||
'READ_HISTORY_BOOKMARKS' => 'Allows an application to read (but not write) the user\'s browsing history and bookmarks.',
|
||||
'READ_INPUT_STATE' => 'Allows an application to retrieve the current state of keys and switches.',
|
||||
'READ_LOGS' => 'Allows an application to read the low-level system log files.',
|
||||
'READ_PHONE_STATE' => 'Allows read only access to phone state.',
|
||||
'READ_PROFILE' => 'Allows an application to read the user\'s personal profile data.',
|
||||
'READ_SMS' => 'Allows an application to read SMS messages.',
|
||||
'READ_SOCIAL_STREAM' => 'Allows an application to read from the user\'s social stream.',
|
||||
'READ_SYNC_SETTINGS' => 'Allows applications to read the sync settings',
|
||||
'READ_SYNC_STATS' => 'Allows applications to read the sync stats',
|
||||
'REBOOT' => 'Required to be able to reboot the device.',
|
||||
'RECEIVE_BOOT_COMPLETED' => 'Allows an application to receive the ACTION_BOOT_COMPLETED that is broadcast after the system finishes booting.',
|
||||
'RECEIVE_MMS' => 'Allows an application to monitor incoming MMS messages, to record or perform processing on them.',
|
||||
'RECEIVE_SMS' => 'Allows an application to monitor incoming SMS messages, to record or perform processing on them.',
|
||||
'RECEIVE_WAP_PUSH' => 'Allows an application to monitor incoming WAP push messages.',
|
||||
'RECORD_AUDIO' => 'Allows an application to record audio',
|
||||
'REORDER_TASKS' => 'Allows an application to change the Z-order of tasks',
|
||||
'RESTART_PACKAGES' => 'This constant is deprecated. The restartPackage(String) API is no longer supported.',
|
||||
'SEND_SMS' => 'Allows an application to send SMS messages.',
|
||||
'SET_ACTIVITY_WATCHER' => 'Allows an application to watch and control how activities are started globally in the system.',
|
||||
'SET_ALARM' => 'Allows an application to broadcast an Intent to set an alarm for the user.',
|
||||
'SET_ALWAYS_FINISH' => 'Allows an application to control whether activities are immediately finished when put in the background.',
|
||||
'SET_ANIMATION_SCALE' => 'Modify the global animation scaling factor.',
|
||||
'SET_DEBUG_APP' => 'Configure an application for debugging.',
|
||||
'SET_ORIENTATION' => 'Allows low-level access to setting the orientation (actually rotation) of the screen.',
|
||||
'SET_POINTER_SPEED' => 'Allows low-level access to setting the pointer speed.',
|
||||
'SET_PREFERRED_APPLICATIONS' => 'This constant is deprecated. No longer useful, see addPackageToPreferred(String) for details.',
|
||||
'SET_PROCESS_LIMIT' => 'Allows an application to set the maximum number of (not needed) application processes that can be running.',
|
||||
'SET_TIME' => 'Allows applications to set the system time',
|
||||
'SET_TIME_ZONE' => 'Allows applications to set the system time zone',
|
||||
'SET_WALLPAPER' => 'Allows applications to set the wallpaper',
|
||||
'SET_WALLPAPER_HINTS' => 'Allows applications to set the wallpaper hints',
|
||||
'SIGNAL_PERSISTENT_PROCESSES' => 'Allow an application to request that a signal be sent to all persistent processes',
|
||||
'STATUS_BAR' => 'Allows an application to open, close, or disable the status bar and its icons.',
|
||||
'SUBSCRIBED_FEEDS_READ' => 'Allows an application to allow access the subscribed feeds ContentProvider.',
|
||||
'SUBSCRIBED_FEEDS_WRITE' => '',
|
||||
'SYSTEM_ALERT_WINDOW' => 'Allows an application to open windows using the type TYPE_SYSTEM_ALERT, shown on top of all other applications.',
|
||||
'UPDATE_DEVICE_STATS' => 'Allows an application to update device statistics.',
|
||||
'USE_CREDENTIALS' => 'Allows an application to request authtokens from the AccountManager',
|
||||
'USE_SIP' => 'Allows an application to use SIP service',
|
||||
'VIBRATE' => 'Allows access to the vibrator',
|
||||
'WAKE_LOCK' => 'Allows using PowerManager WakeLocks to keep processor from sleeping or screen from dimming',
|
||||
'WRITE_APN_SETTINGS' => 'Allows applications to write the apn settings',
|
||||
'WRITE_CALENDAR' => 'Allows an application to write (but not read) the user\'s calendar data.',
|
||||
'WRITE_CONTACTS' => 'Allows an application to write (but not read) the user\'s contacts data.',
|
||||
'WRITE_EXTERNAL_STORAGE' => 'Allows an application to write to external storage',
|
||||
'WRITE_GSERVICES' => 'Allows an application to modify the Google service map.',
|
||||
'WRITE_HISTORY_BOOKMARKS' => 'Allows an application to write (but not read) the user\'s browsing history and bookmarks.',
|
||||
'WRITE_PROFILE' => 'Allows an application to write (but not read) the user\'s personal profile data.',
|
||||
'WRITE_SECURE_SETTINGS' => 'Allows an application to read or write the secure system settings.',
|
||||
'WRITE_SETTINGS' => 'Allows an application to read or write the system settings.',
|
||||
'WRITE_SMS' => 'Allows an application to write SMS messages.',
|
||||
'WRITE_SOCIAL_STREAM' => 'Allows an application to write (but not read) the user\'s social stream data.',
|
||||
'WRITE_SYNC_SETTINGS' => 'Allows applications to write the sync settings'
|
||||
);
|
||||
}
|
22
website/page/code/apk/lib/ApkManifestXmlElement.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
class ApkManifestXmlElement extends SimpleXMLElement
|
||||
{
|
||||
public function getPermissions()
|
||||
{
|
||||
/**
|
||||
* @var ApkManifestXmlElement
|
||||
*/
|
||||
$permsArray = $this->{'uses-permission'};
|
||||
|
||||
$perms = array();
|
||||
foreach($permsArray as $perm)
|
||||
{
|
||||
$permAttr = get_object_vars($perm);
|
||||
$objNotationArray = explode('.',$permAttr['@attributes']['name']);
|
||||
$permName = trim(end($objNotationArray));
|
||||
$perms[$permName] = ApkManifest::$permissions[$permName];
|
||||
}
|
||||
|
||||
return $perms;
|
||||
}
|
||||
}
|
88
website/page/code/apk/lib/ApkStream.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/**
|
||||
* @author Tufan Baris YILDIRIM
|
||||
* -- descrtiption is coming.
|
||||
*/
|
||||
class ApkStream
|
||||
{
|
||||
/**
|
||||
* file strem, like "fopen"
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
private $stream;
|
||||
|
||||
/**
|
||||
* @param resource $stream File stream.
|
||||
* @return ApkStream
|
||||
*/
|
||||
public function __construct($stream)
|
||||
{
|
||||
if(!is_resource($stream))
|
||||
// TODO : the resource type must be a regular file stream resource.
|
||||
throw new Exception( "Invalid stream" );
|
||||
|
||||
$this->stream = $stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the next character from stream.
|
||||
*
|
||||
* @param mixed $length
|
||||
*/
|
||||
public function read($length = 1)
|
||||
{
|
||||
return fread($this->stream,$length);
|
||||
}
|
||||
|
||||
/**
|
||||
* check if end of filestream
|
||||
*/
|
||||
public function feof()
|
||||
{
|
||||
return feof($this->stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Jump to the index!
|
||||
* @param int $offset
|
||||
*/
|
||||
public function seek($offset)
|
||||
{
|
||||
fseek($this->stream,$offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the stream
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
fclose($this->stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the next byte
|
||||
* @return byte
|
||||
*/
|
||||
public function readByte()
|
||||
{
|
||||
return ord($this->read());
|
||||
}
|
||||
|
||||
/**
|
||||
* fetch the remaining byte into an array
|
||||
*
|
||||
* @param mixed $count Byte length.
|
||||
* @return array
|
||||
*/
|
||||
public function getByteArray($count = null)
|
||||
{
|
||||
$bytes = array();
|
||||
|
||||
while(!$this->feof() && ($count === null || count($bytes) < $count))
|
||||
$bytes[] = $this->readByte();
|
||||
|
||||
return $bytes;
|
||||
}
|
||||
}
|
||||
|
5
website/page/code/apk/lib/ApkXml.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
abstract class ApkXml
|
||||
{
|
||||
|
||||
}
|
181
website/page/code/apk/lib/ApkXmlParser.php
Normal file
@ -0,0 +1,181 @@
|
||||
<?php
|
||||
include_once dirname(__FILE__) . "/ApkStream.php";
|
||||
|
||||
class ApkXmlParser
|
||||
{
|
||||
const END_DOC_TAG = 0x00100101;
|
||||
const START_TAG = 0x00100102;
|
||||
const END_TAG = 0x00100103;
|
||||
|
||||
private $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n";
|
||||
private $bytes = array();
|
||||
private $ready = false;
|
||||
|
||||
public static $indent_spaces = " ";
|
||||
|
||||
/**
|
||||
* Store the SimpleXmlElement object
|
||||
* @var SimpleXmlElement
|
||||
*/
|
||||
private $xmlObject = NULL;
|
||||
|
||||
|
||||
public function __construct(ApkStream $apkStream)
|
||||
{
|
||||
$this->bytes = $apkStream->getByteArray();
|
||||
}
|
||||
|
||||
public static function decompressFile($file,$destination = NULL)
|
||||
{
|
||||
if(!is_file($file))
|
||||
throw new Exception("{$file} is not a regular file");
|
||||
|
||||
$parser = new self(new ApkStream(fopen($file,'rd')));
|
||||
//TODO : write a method in this class, ->saveToFile();
|
||||
file_put_contents($destination === NULL ? $file : $destination,$parser->getXmlString());
|
||||
}
|
||||
|
||||
public function decompress()
|
||||
{
|
||||
$numbStrings = $this->littleEndianWord($this->bytes, 4*4);
|
||||
$sitOff = 0x24;
|
||||
$stOff = $sitOff + $numbStrings * 4;
|
||||
$this->bytesTagOff = $this->littleEndianWord($this->bytes, 3*4);
|
||||
|
||||
for ($ii = $this->bytesTagOff; $ii < count($this->bytes) - 4; $ii += 4):
|
||||
if ($this->littleEndianWord($this->bytes, $ii) == self::START_TAG) :
|
||||
$this->bytesTagOff = $ii;
|
||||
break;
|
||||
endif;
|
||||
endfor;
|
||||
|
||||
|
||||
|
||||
$off = $this->bytesTagOff;
|
||||
$indentCount = 0;
|
||||
$startTagLineNo = -2;
|
||||
|
||||
while ($off < count($this->bytes))
|
||||
{
|
||||
$currentTag = $this->littleEndianWord($this->bytes, $off);
|
||||
$lineNo = $this->littleEndianWord($this->bytes, $off + 2*4);
|
||||
$nameNsSi = $this->littleEndianWord($this->bytes, $off + 4*4);
|
||||
$nameSi = $this->littleEndianWord($this->bytes, $off + 5*4);
|
||||
|
||||
|
||||
switch($currentTag)
|
||||
{
|
||||
case self::START_TAG:
|
||||
{
|
||||
$tagSix = $this->littleEndianWord($this->bytes, $off + 6*4);
|
||||
$numbAttrs = $this->littleEndianWord($this->bytes, $off + 7*4);
|
||||
$off += 9*4;
|
||||
$tagName = $this->compXmlString($this->bytes, $sitOff, $stOff, $nameSi);
|
||||
$startTagLineNo = $lineNo;
|
||||
$attr_string = "";
|
||||
|
||||
for ($ii=0; $ii < $numbAttrs; $ii++)
|
||||
{
|
||||
$attrNameNsSi = $this->littleEndianWord($this->bytes, $off);
|
||||
$attrNameSi = $this->littleEndianWord($this->bytes, $off + 1*4);
|
||||
$attrValueSi = $this->littleEndianWord($this->bytes, $off + 2*4);
|
||||
$attrFlags = $this->littleEndianWord($this->bytes, $off + 3*4);
|
||||
$attrResId = $this->littleEndianWord($this->bytes, $off + 4*4);
|
||||
$off += 5*4;
|
||||
|
||||
$attrName = $this->compXmlString($this->bytes, $sitOff, $stOff, $attrNameSi);
|
||||
if($attrValueSi != 0xffffffff)
|
||||
$attrValue = $this->compXmlString($this->bytes, $sitOff, $stOff, $attrValueSi);
|
||||
else
|
||||
$attrValue = "0x" . dechex($attrResId);
|
||||
|
||||
$attr_string .= " " . $attrName . "=\"" . $attrValue . "\"";
|
||||
|
||||
}
|
||||
|
||||
$this->appendXmlIndent($indentCount, "<". $tagName . $attr_string . ">");
|
||||
$indentCount++;
|
||||
}
|
||||
break;
|
||||
|
||||
case self::END_TAG:
|
||||
{
|
||||
$indentCount--;
|
||||
$off += 6*4;
|
||||
$tagName = $this->compXmlString($this->bytes, $sitOff, $stOff, $nameSi);
|
||||
$this->appendXmlIndent($indentCount, "</" . $tagName . ">");
|
||||
}
|
||||
break;
|
||||
|
||||
case self::END_DOC_TAG:
|
||||
{
|
||||
$this->ready = true;
|
||||
break 2;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception("Unrecognized tag code '" . dechex($currentTag) . "' at offset " . $off);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function compXmlString($xml, $sitOff, $stOff, $str_index)
|
||||
{
|
||||
if ($str_index < 0)
|
||||
return null;
|
||||
|
||||
$strOff = $stOff + $this->littleEndianWord($xml, $sitOff + $str_index * 4);
|
||||
return $this->compXmlStringAt($xml, $strOff);
|
||||
}
|
||||
|
||||
public function appendXmlIndent($indent, $str)
|
||||
{
|
||||
$this->appendXml(substr(self::$indent_spaces,0, min($indent * 2, strlen(self::$indent_spaces))) . $str);
|
||||
}
|
||||
|
||||
public function appendXml($str)
|
||||
{
|
||||
$this->xml .= $str ."\r\n";
|
||||
}
|
||||
|
||||
public function compXmlStringAt($arr, $string_offset)
|
||||
{
|
||||
$strlen = $arr[$string_offset + 1] << 8 & 0xff00 | $arr[$string_offset] & 0xff;
|
||||
$string = "";
|
||||
|
||||
for ($i=0; $i<$strlen; $i++)
|
||||
$string .= chr($arr[$string_offset + 2 + $i * 2]);
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
public function littleEndianWord($arr, $off)
|
||||
{
|
||||
return $arr[$off+3] << 24&0xff000000 | $arr[$off+2] << 16&0xff0000 | $arr[$off+1]<<8&0xff00 | $arr[$off]&0xFF;
|
||||
}
|
||||
|
||||
public function output()
|
||||
{
|
||||
echo $this->getXmlString();
|
||||
}
|
||||
|
||||
public function getXmlString()
|
||||
{
|
||||
if(!$this->ready)
|
||||
$this->decompress();
|
||||
return $this->xml;
|
||||
}
|
||||
|
||||
public function getXmlObject($className = 'SimpleXmlElement')
|
||||
{
|
||||
if($this->xmlObject === NULL || !$this->xmlObject instanceof $className)
|
||||
$this->xmlObject = simplexml_load_string($this->getXmlString(),$className);
|
||||
|
||||
return $this->xmlObject;
|
||||
}
|
||||
}
|
64
website/page/code/desktop-download.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
include_once ( "userscript.php" );
|
||||
|
||||
|
||||
function iitcDesktopDownload ( $build )
|
||||
{
|
||||
$iitc_details = loadUserScriptHeader ( "$build/total-conversion-build.user.js" );
|
||||
$iitc_version = preg_replace ( '/^(\d+\.\d+\.\d+)\.(\d{8}\.\d{6})/', '\1<small class="muted">.\2</small>', $iitc_details['@version'] );
|
||||
|
||||
print "<p>IITC version $iitc_version</p>\n";
|
||||
|
||||
print "<a class=\"btn btn-large btn-primary\" onclick=\"if(track){track('desktop','iitc','$build');}\" href=\"$build/total-conversion-build.user.js\">Download</a>\n";
|
||||
}
|
||||
|
||||
|
||||
function iitcDesktopPluginDownloadTable ( $build )
|
||||
{
|
||||
?>
|
||||
<table class="table table-condensed table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>ID / Version</th>
|
||||
<th>Description</th>
|
||||
<th>Download</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
<?php
|
||||
foreach ( glob ( "$build/plugins/*.user.js" ) as $path )
|
||||
{
|
||||
$basename = basename ( $path, ".user.js" );
|
||||
|
||||
$details = loadUserScriptHeader ( $path );
|
||||
|
||||
print "<tr id=\"plugin-$basename\">\n";
|
||||
|
||||
# remove 'IITC Plugin: ' prefix if it's there, for neatness
|
||||
$name = preg_replace ( '/^IITC plugin: /i', '', $details['@name'] );
|
||||
|
||||
# format extended version info in less prominant font
|
||||
$version = preg_replace ( '/^(\d+\.\d+\.\d+)\.(\d{8}\.\d{6})/', '\1<small class="muted">.\2</small>', $details['@version'] );
|
||||
|
||||
# remove unneeded prefix from description
|
||||
$description = preg_replace ( '/^\[[^]]*\] */', '', $details['@description'] );
|
||||
|
||||
print "<td>$name</td>";
|
||||
print "<td>$basename<br />$version</td>";
|
||||
print "<td>$description</td>";
|
||||
print "<td><a onclick=\"if(track){track('desktop','iitc-plugin-$basename','$build');}\" href=\"$path\" class=\"btn btn-small btn-primary\">Download</a></td>";
|
||||
print "</tr>\n";
|
||||
}
|
||||
|
||||
?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php
|
||||
}
|
||||
|
||||
|
||||
?>
|
68
website/page/code/mobile-download.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
include_once ( "apk/ApkParser.php" );
|
||||
include_once ( "url/url_to_absolute.php" );
|
||||
include_once ( "userscript.php" );
|
||||
|
||||
function getMobileVersion ( $apkfile )
|
||||
{
|
||||
$result = Array();
|
||||
|
||||
|
||||
$apkinfo = new ApkParser ( $apkfile );
|
||||
|
||||
$manifest = $apkinfo->getManifest();
|
||||
|
||||
$result['apk_version'] = $manifest->getVersionName();
|
||||
|
||||
|
||||
$archive = $apkinfo->getApkArchive();
|
||||
$iitc_file = "assets/total-conversion-build.user.js";
|
||||
if ( $archive->statName ( $iitc_file ) === FALSE );
|
||||
$iitc_file = "assets/iitc.js";
|
||||
|
||||
$stream = $archive->getStream ( $iitc_file );
|
||||
|
||||
$header = loadUserScriptHeader ( $stream );
|
||||
|
||||
$result['iitc_version'] = $header['@version'];
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
function iitcMobileDownload ( $apkfile )
|
||||
{
|
||||
|
||||
$version = getMobileVersion ( $apkfile );
|
||||
|
||||
$apk_version = $version['apk_version'];
|
||||
$iitc_version = preg_replace ( '/^(\d+\.\d+\.\d+)\.(\d{8}\.\d{6})/', '\1<small class="muted">.\2</small>', $version['iitc_version'] );
|
||||
|
||||
# we need an absolute link for the QR Code
|
||||
# get the URL of this page itself
|
||||
$pageurl = ($_SERVER['HTTPS'] ? "https" : "http")."://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
|
||||
$apkurl = url_to_absolute ( $pageurl, $apkfile );
|
||||
?>
|
||||
|
||||
|
||||
<div>
|
||||
|
||||
<img style="float: right; margin: 10px;" src="https://chart.googleapis.com/chart?cht=qr&chs=120x120&chld=L|2&chl=<?php print urlencode($apkurl); ?>" alt="QR Code for download">
|
||||
|
||||
<p>
|
||||
IITC Mobile version <?php print $apk_version; ?>, with IITC version <?php print $iitc_version; ?>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a style="margin-right: 1em;" onclick="if(track)({track{'mobile','download','<?php print $apkfile; ?>');}" class="btn btn-large btn-primary" href="<?php print $apkfile; ?>">Download</a> or scan the QR Code
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div style="clear: both"></div>
|
||||
|
||||
|
||||
<?php
|
||||
|
||||
}
|
||||
|
||||
?>
|
24
website/page/code/url/CHANGELOG.txt
Normal file
@ -0,0 +1,24 @@
|
||||
Changelog
|
||||
---------
|
||||
|
||||
absoluteurl v1.6, March 12, 2010
|
||||
---------------------------------
|
||||
- added encode_url function to convert an absolute url to its percentage
|
||||
encoded equivalent, according to RFC 3986
|
||||
|
||||
absoluteurl v1.5, March 11, 2010
|
||||
---------------------------------
|
||||
- fixed to allow spaces in the path of url
|
||||
|
||||
absoluteurl v1.4, October 2, 2009
|
||||
----------------------------------------------
|
||||
- Percentage encoding of the absolute url disabled.
|
||||
|
||||
absoluteurl v1.2, 2009-02-27
|
||||
-----------------------------------------------
|
||||
- Minor bug fix
|
||||
|
||||
|
||||
absoluteurl v1.0, 2009-08-28
|
||||
----------------------------------------
|
||||
- Initial release
|
26
website/page/code/url/README.txt
Normal file
@ -0,0 +1,26 @@
|
||||
absoluteurl
|
||||
---------------------
|
||||
|
||||
This script converts the relative url to absolute url, provided a base url.
|
||||
|
||||
|
||||
For more, look here: http://publicmind.in/blog/urltoabsolute
|
||||
|
||||
Usage:
|
||||
----------
|
||||
|
||||
Extract the script (url_to_absolute.php) into your web directory, include it into your current php file using:
|
||||
|
||||
require(path-to-file);
|
||||
|
||||
then, you can convert the relative url to absolute url by calling:
|
||||
|
||||
url_to_absolute( $baseUrl, $relativeUrl);
|
||||
|
||||
It return false on failure, otherwise returns the absolute url. If the $relativeUrl is a valid absolute url, it is returned without any modification.
|
||||
|
||||
Author/credits
|
||||
-----------
|
||||
|
||||
1) Original author: David R. Nadeau, NadeauSoftware.com
|
||||
2) Edited and maintained by: Nitin Kr, Gupta, publicmind.in
|
485
website/page/code/url/url_to_absolute.php
Normal file
@ -0,0 +1,485 @@
|
||||
<?php
|
||||
/**
|
||||
* Edited by Nitin Kr. Gupta, publicmind.in
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copyright (c) 2008, David R. Nadeau, NadeauSoftware.com.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following
|
||||
* disclaimer in the documentation and/or other materials provided
|
||||
* with the distribution.
|
||||
*
|
||||
* * Neither the names of David R. Nadeau or NadeauSoftware.com, nor
|
||||
* the names of its contributors may be used to endorse or promote
|
||||
* products derived from this software without specific prior
|
||||
* written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
|
||||
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
||||
* OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is a BSD License approved by the Open Source Initiative (OSI).
|
||||
* See: http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
|
||||
/**
|
||||
* Combine a base URL and a relative URL to produce a new
|
||||
* absolute URL. The base URL is often the URL of a page,
|
||||
* and the relative URL is a URL embedded on that page.
|
||||
*
|
||||
* This function implements the "absolutize" algorithm from
|
||||
* the RFC3986 specification for URLs.
|
||||
*
|
||||
* This function supports multi-byte characters with the UTF-8 encoding,
|
||||
* per the URL specification.
|
||||
*
|
||||
* Parameters:
|
||||
* baseUrl the absolute base URL.
|
||||
*
|
||||
* url the relative URL to convert.
|
||||
*
|
||||
* Return values:
|
||||
* An absolute URL that combines parts of the base and relative
|
||||
* URLs, or FALSE if the base URL is not absolute or if either
|
||||
* URL cannot be parsed.
|
||||
*/
|
||||
function url_to_absolute( $baseUrl, $relativeUrl )
|
||||
{
|
||||
// If relative URL has a scheme, clean path and return.
|
||||
$r = split_url( $relativeUrl );
|
||||
if ( $r === FALSE )
|
||||
return FALSE;
|
||||
if ( !empty( $r['scheme'] ) )
|
||||
{
|
||||
if ( !empty( $r['path'] ) && $r['path'][0] == '/' )
|
||||
$r['path'] = url_remove_dot_segments( $r['path'] );
|
||||
return join_url( $r );
|
||||
}
|
||||
|
||||
// Make sure the base URL is absolute.
|
||||
$b = split_url( $baseUrl );
|
||||
if ( $b === FALSE || empty( $b['scheme'] ) || empty( $b['host'] ) )
|
||||
return FALSE;
|
||||
$r['scheme'] = $b['scheme'];
|
||||
|
||||
// If relative URL has an authority, clean path and return.
|
||||
if ( isset( $r['host'] ) )
|
||||
{
|
||||
if ( !empty( $r['path'] ) )
|
||||
$r['path'] = url_remove_dot_segments( $r['path'] );
|
||||
return join_url( $r );
|
||||
}
|
||||
unset( $r['port'] );
|
||||
unset( $r['user'] );
|
||||
unset( $r['pass'] );
|
||||
|
||||
// Copy base authority.
|
||||
$r['host'] = $b['host'];
|
||||
if ( isset( $b['port'] ) ) $r['port'] = $b['port'];
|
||||
if ( isset( $b['user'] ) ) $r['user'] = $b['user'];
|
||||
if ( isset( $b['pass'] ) ) $r['pass'] = $b['pass'];
|
||||
|
||||
// If relative URL has no path, use base path
|
||||
if ( empty( $r['path'] ) )
|
||||
{
|
||||
if ( !empty( $b['path'] ) )
|
||||
$r['path'] = $b['path'];
|
||||
if ( !isset( $r['query'] ) && isset( $b['query'] ) )
|
||||
$r['query'] = $b['query'];
|
||||
return join_url( $r );
|
||||
}
|
||||
|
||||
// If relative URL path doesn't start with /, merge with base path
|
||||
if ( $r['path'][0] != '/' )
|
||||
{
|
||||
$base = mb_strrchr( $b['path'], '/', TRUE, 'UTF-8' );
|
||||
if ( $base === FALSE ) $base = '';
|
||||
$r['path'] = $base . '/' . $r['path'];
|
||||
}
|
||||
$r['path'] = url_remove_dot_segments( $r['path'] );
|
||||
return join_url( $r );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter out "." and ".." segments from a URL's path and return
|
||||
* the result.
|
||||
*
|
||||
* This function implements the "remove_dot_segments" algorithm from
|
||||
* the RFC3986 specification for URLs.
|
||||
*
|
||||
* This function supports multi-byte characters with the UTF-8 encoding,
|
||||
* per the URL specification.
|
||||
*
|
||||
* Parameters:
|
||||
* path the path to filter
|
||||
*
|
||||
* Return values:
|
||||
* The filtered path with "." and ".." removed.
|
||||
*/
|
||||
function url_remove_dot_segments( $path )
|
||||
{
|
||||
// multi-byte character explode
|
||||
$inSegs = preg_split( '!/!u', $path );
|
||||
$outSegs = array( );
|
||||
foreach ( $inSegs as $seg )
|
||||
{
|
||||
if ( $seg == '' || $seg == '.')
|
||||
continue;
|
||||
if ( $seg == '..' )
|
||||
array_pop( $outSegs );
|
||||
else
|
||||
array_push( $outSegs, $seg );
|
||||
}
|
||||
$outPath = implode( '/', $outSegs );
|
||||
if ( $path[0] == '/' )
|
||||
$outPath = '/' . $outPath;
|
||||
// compare last multi-byte character against '/'
|
||||
if ( $outPath != '/' &&
|
||||
(mb_strlen($path)-1) == mb_strrpos( $path, '/', 'UTF-8' ) )
|
||||
$outPath .= '/';
|
||||
return $outPath;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function parses an absolute or relative URL and splits it
|
||||
* into individual components.
|
||||
*
|
||||
* RFC3986 specifies the components of a Uniform Resource Identifier (URI).
|
||||
* A portion of the ABNFs are repeated here:
|
||||
*
|
||||
* URI-reference = URI
|
||||
* / relative-ref
|
||||
*
|
||||
* URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
|
||||
*
|
||||
* relative-ref = relative-part [ "?" query ] [ "#" fragment ]
|
||||
*
|
||||
* hier-part = "//" authority path-abempty
|
||||
* / path-absolute
|
||||
* / path-rootless
|
||||
* / path-empty
|
||||
*
|
||||
* relative-part = "//" authority path-abempty
|
||||
* / path-absolute
|
||||
* / path-noscheme
|
||||
* / path-empty
|
||||
*
|
||||
* authority = [ userinfo "@" ] host [ ":" port ]
|
||||
*
|
||||
* So, a URL has the following major components:
|
||||
*
|
||||
* scheme
|
||||
* The name of a method used to interpret the rest of
|
||||
* the URL. Examples: "http", "https", "mailto", "file'.
|
||||
*
|
||||
* authority
|
||||
* The name of the authority governing the URL's name
|
||||
* space. Examples: "example.com", "user@example.com",
|
||||
* "example.com:80", "user:password@example.com:80".
|
||||
*
|
||||
* The authority may include a host name, port number,
|
||||
* user name, and password.
|
||||
*
|
||||
* The host may be a name, an IPv4 numeric address, or
|
||||
* an IPv6 numeric address.
|
||||
*
|
||||
* path
|
||||
* The hierarchical path to the URL's resource.
|
||||
* Examples: "/index.htm", "/scripts/page.php".
|
||||
*
|
||||
* query
|
||||
* The data for a query. Examples: "?search=google.com".
|
||||
*
|
||||
* fragment
|
||||
* The name of a secondary resource relative to that named
|
||||
* by the path. Examples: "#section1", "#header".
|
||||
*
|
||||
* An "absolute" URL must include a scheme and path. The authority, query,
|
||||
* and fragment components are optional.
|
||||
*
|
||||
* A "relative" URL does not include a scheme and must include a path. The
|
||||
* authority, query, and fragment components are optional.
|
||||
*
|
||||
* This function splits the $url argument into the following components
|
||||
* and returns them in an associative array. Keys to that array include:
|
||||
*
|
||||
* "scheme" The scheme, such as "http".
|
||||
* "host" The host name, IPv4, or IPv6 address.
|
||||
* "port" The port number.
|
||||
* "user" The user name.
|
||||
* "pass" The user password.
|
||||
* "path" The path, such as a file path for "http".
|
||||
* "query" The query.
|
||||
* "fragment" The fragment.
|
||||
*
|
||||
* One or more of these may not be present, depending upon the URL.
|
||||
*
|
||||
* Optionally, the "user", "pass", "host" (if a name, not an IP address),
|
||||
* "path", "query", and "fragment" may have percent-encoded characters
|
||||
* decoded. The "scheme" and "port" cannot include percent-encoded
|
||||
* characters and are never decoded. Decoding occurs after the URL has
|
||||
* been parsed.
|
||||
*
|
||||
* Parameters:
|
||||
* url the URL to parse.
|
||||
*
|
||||
* decode an optional boolean flag selecting whether
|
||||
* to decode percent encoding or not. Default = TRUE.
|
||||
*
|
||||
* Return values:
|
||||
* the associative array of URL parts, or FALSE if the URL is
|
||||
* too malformed to recognize any parts.
|
||||
*/
|
||||
function split_url( $url, $decode=FALSE)
|
||||
{
|
||||
// Character sets from RFC3986.
|
||||
$xunressub = 'a-zA-Z\d\-._~\!$&\'()*+,;=';
|
||||
$xpchar = $xunressub . ':@% ';
|
||||
|
||||
// Scheme from RFC3986.
|
||||
$xscheme = '([a-zA-Z][a-zA-Z\d+-.]*)';
|
||||
|
||||
// User info (user + password) from RFC3986.
|
||||
$xuserinfo = '(([' . $xunressub . '%]*)' .
|
||||
'(:([' . $xunressub . ':%]*))?)';
|
||||
|
||||
// IPv4 from RFC3986 (without digit constraints).
|
||||
$xipv4 = '(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})';
|
||||
|
||||
// IPv6 from RFC2732 (without digit and grouping constraints).
|
||||
$xipv6 = '(\[([a-fA-F\d.:]+)\])';
|
||||
|
||||
// Host name from RFC1035. Technically, must start with a letter.
|
||||
// Relax that restriction to better parse URL structure, then
|
||||
// leave host name validation to application.
|
||||
$xhost_name = '([a-zA-Z\d-.%]+)';
|
||||
|
||||
// Authority from RFC3986. Skip IP future.
|
||||
$xhost = '(' . $xhost_name . '|' . $xipv4 . '|' . $xipv6 . ')';
|
||||
$xport = '(\d*)';
|
||||
$xauthority = '((' . $xuserinfo . '@)?' . $xhost .
|
||||
'?(:' . $xport . ')?)';
|
||||
|
||||
// Path from RFC3986. Blend absolute & relative for efficiency.
|
||||
$xslash_seg = '(/[' . $xpchar . ']*)';
|
||||
$xpath_authabs = '((//' . $xauthority . ')((/[' . $xpchar . ']*)*))';
|
||||
$xpath_rel = '([' . $xpchar . ']+' . $xslash_seg . '*)';
|
||||
$xpath_abs = '(/(' . $xpath_rel . ')?)';
|
||||
$xapath = '(' . $xpath_authabs . '|' . $xpath_abs .
|
||||
'|' . $xpath_rel . ')';
|
||||
|
||||
// Query and fragment from RFC3986.
|
||||
$xqueryfrag = '([' . $xpchar . '/?' . ']*)';
|
||||
|
||||
// URL.
|
||||
$xurl = '^(' . $xscheme . ':)?' . $xapath . '?' .
|
||||
'(\?' . $xqueryfrag . ')?(#' . $xqueryfrag . ')?$';
|
||||
|
||||
|
||||
// Split the URL into components.
|
||||
if ( !preg_match( '!' . $xurl . '!', $url, $m ) )
|
||||
return FALSE;
|
||||
|
||||
if ( !empty($m[2]) ) $parts['scheme'] = strtolower($m[2]);
|
||||
|
||||
if ( !empty($m[7]) ) {
|
||||
if ( isset( $m[9] ) ) $parts['user'] = $m[9];
|
||||
else $parts['user'] = '';
|
||||
}
|
||||
if ( !empty($m[10]) ) $parts['pass'] = $m[11];
|
||||
|
||||
if ( !empty($m[13]) ) $h=$parts['host'] = $m[13];
|
||||
else if ( !empty($m[14]) ) $parts['host'] = $m[14];
|
||||
else if ( !empty($m[16]) ) $parts['host'] = $m[16];
|
||||
else if ( !empty( $m[5] ) ) $parts['host'] = '';
|
||||
if ( !empty($m[17]) ) $parts['port'] = $m[18];
|
||||
|
||||
if ( !empty($m[19]) ) $parts['path'] = $m[19];
|
||||
else if ( !empty($m[21]) ) $parts['path'] = $m[21];
|
||||
else if ( !empty($m[25]) ) $parts['path'] = $m[25];
|
||||
|
||||
if ( !empty($m[27]) ) $parts['query'] = $m[28];
|
||||
if ( !empty($m[29]) ) $parts['fragment']= $m[30];
|
||||
|
||||
if ( !$decode )
|
||||
return $parts;
|
||||
if ( !empty($parts['user']) )
|
||||
$parts['user'] = rawurldecode( $parts['user'] );
|
||||
if ( !empty($parts['pass']) )
|
||||
$parts['pass'] = rawurldecode( $parts['pass'] );
|
||||
if ( !empty($parts['path']) )
|
||||
$parts['path'] = rawurldecode( $parts['path'] );
|
||||
if ( isset($h) )
|
||||
$parts['host'] = rawurldecode( $parts['host'] );
|
||||
if ( !empty($parts['query']) )
|
||||
$parts['query'] = rawurldecode( $parts['query'] );
|
||||
if ( !empty($parts['fragment']) )
|
||||
$parts['fragment'] = rawurldecode( $parts['fragment'] );
|
||||
return $parts;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function joins together URL components to form a complete URL.
|
||||
*
|
||||
* RFC3986 specifies the components of a Uniform Resource Identifier (URI).
|
||||
* This function implements the specification's "component recomposition"
|
||||
* algorithm for combining URI components into a full URI string.
|
||||
*
|
||||
* The $parts argument is an associative array containing zero or
|
||||
* more of the following:
|
||||
*
|
||||
* "scheme" The scheme, such as "http".
|
||||
* "host" The host name, IPv4, or IPv6 address.
|
||||
* "port" The port number.
|
||||
* "user" The user name.
|
||||
* "pass" The user password.
|
||||
* "path" The path, such as a file path for "http".
|
||||
* "query" The query.
|
||||
* "fragment" The fragment.
|
||||
*
|
||||
* The "port", "user", and "pass" values are only used when a "host"
|
||||
* is present.
|
||||
*
|
||||
* The optional $encode argument indicates if appropriate URL components
|
||||
* should be percent-encoded as they are assembled into the URL. Encoding
|
||||
* is only applied to the "user", "pass", "host" (if a host name, not an
|
||||
* IP address), "path", "query", and "fragment" components. The "scheme"
|
||||
* and "port" are never encoded. When a "scheme" and "host" are both
|
||||
* present, the "path" is presumed to be hierarchical and encoding
|
||||
* processes each segment of the hierarchy separately (i.e., the slashes
|
||||
* are left alone).
|
||||
*
|
||||
* The assembled URL string is returned.
|
||||
*
|
||||
* Parameters:
|
||||
* parts an associative array of strings containing the
|
||||
* individual parts of a URL.
|
||||
*
|
||||
* encode an optional boolean flag selecting whether
|
||||
* to do percent encoding or not. Default = true.
|
||||
*
|
||||
* Return values:
|
||||
* Returns the assembled URL string. The string is an absolute
|
||||
* URL if a scheme is supplied, and a relative URL if not. An
|
||||
* empty string is returned if the $parts array does not contain
|
||||
* any of the needed values.
|
||||
*/
|
||||
function join_url( $parts, $encode=FALSE)
|
||||
{
|
||||
if ( $encode )
|
||||
{
|
||||
if ( isset( $parts['user'] ) )
|
||||
$parts['user'] = rawurlencode( $parts['user'] );
|
||||
if ( isset( $parts['pass'] ) )
|
||||
$parts['pass'] = rawurlencode( $parts['pass'] );
|
||||
if ( isset( $parts['host'] ) &&
|
||||
!preg_match( '!^(\[[\da-f.:]+\]])|([\da-f.:]+)$!ui', $parts['host'] ) )
|
||||
$parts['host'] = rawurlencode( $parts['host'] );
|
||||
if ( !empty( $parts['path'] ) )
|
||||
$parts['path'] = preg_replace( '!%2F!ui', '/',
|
||||
rawurlencode( $parts['path'] ) );
|
||||
if ( isset( $parts['query'] ) )
|
||||
$parts['query'] = rawurlencode( $parts['query'] );
|
||||
if ( isset( $parts['fragment'] ) )
|
||||
$parts['fragment'] = rawurlencode( $parts['fragment'] );
|
||||
}
|
||||
|
||||
$url = '';
|
||||
if ( !empty( $parts['scheme'] ) )
|
||||
$url .= $parts['scheme'] . ':';
|
||||
if ( isset( $parts['host'] ) )
|
||||
{
|
||||
$url .= '//';
|
||||
if ( isset( $parts['user'] ) )
|
||||
{
|
||||
$url .= $parts['user'];
|
||||
if ( isset( $parts['pass'] ) )
|
||||
$url .= ':' . $parts['pass'];
|
||||
$url .= '@';
|
||||
}
|
||||
if ( preg_match( '!^[\da-f]*:[\da-f.:]+$!ui', $parts['host'] ) )
|
||||
$url .= '[' . $parts['host'] . ']'; // IPv6
|
||||
else
|
||||
$url .= $parts['host']; // IPv4 or name
|
||||
if ( isset( $parts['port'] ) )
|
||||
$url .= ':' . $parts['port'];
|
||||
if ( !empty( $parts['path'] ) && $parts['path'][0] != '/' )
|
||||
$url .= '/';
|
||||
}
|
||||
if ( !empty( $parts['path'] ) )
|
||||
$url .= $parts['path'];
|
||||
if ( isset( $parts['query'] ) )
|
||||
$url .= '?' . $parts['query'];
|
||||
if ( isset( $parts['fragment'] ) )
|
||||
$url .= '#' . $parts['fragment'];
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function encodes URL to form a URL which is properly
|
||||
* percent encoded to replace disallowed characters.
|
||||
*
|
||||
* RFC3986 specifies the allowed characters in the URL as well as
|
||||
* reserved characters in the URL. This function replaces all the
|
||||
* disallowed characters in the URL with their repective percent
|
||||
* encodings. Already encoded characters are not encoded again,
|
||||
* such as '%20' is not encoded to '%2520'.
|
||||
*
|
||||
* Parameters:
|
||||
* url the url to encode.
|
||||
*
|
||||
* Return values:
|
||||
* Returns the encoded URL string.
|
||||
*/
|
||||
function encode_url($url) {
|
||||
$reserved = array(
|
||||
":" => '!%3A!ui',
|
||||
"/" => '!%2F!ui',
|
||||
"?" => '!%3F!ui',
|
||||
"#" => '!%23!ui',
|
||||
"[" => '!%5B!ui',
|
||||
"]" => '!%5D!ui',
|
||||
"@" => '!%40!ui',
|
||||
"!" => '!%21!ui',
|
||||
"$" => '!%24!ui',
|
||||
"&" => '!%26!ui',
|
||||
"'" => '!%27!ui',
|
||||
"(" => '!%28!ui',
|
||||
")" => '!%29!ui',
|
||||
"*" => '!%2A!ui',
|
||||
"+" => '!%2B!ui',
|
||||
"," => '!%2C!ui',
|
||||
";" => '!%3B!ui',
|
||||
"=" => '!%3D!ui',
|
||||
"%" => '!%25!ui',
|
||||
);
|
||||
|
||||
$url = rawurlencode($url);
|
||||
$url = preg_replace(array_values($reserved), array_keys($reserved), $url);
|
||||
return $url;
|
||||
}
|
||||
|
||||
?>
|
35
website/page/code/userscript.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
function loadUserScriptHeader($file)
|
||||
{
|
||||
$result = Array();
|
||||
|
||||
if ( is_string($file) )
|
||||
$file = fopen ( $file, "rt" );
|
||||
# else assume it's already a readable stream
|
||||
|
||||
while ( ( $line = fgets ( $file ) ) !== FALSE )
|
||||
{
|
||||
if ( preg_match ( '#//[ \\t]*==/UserScript==#', $line ) )
|
||||
break;
|
||||
|
||||
$matches = Array();
|
||||
if ( preg_match ( '#^//[ \\t]*(@[a-zA-Z0-9]+)[ \\t]+(.*)$#', $line, $matches ) )
|
||||
{
|
||||
$name = $matches[1];
|
||||
$value = $matches[2];
|
||||
|
||||
if ( ! array_key_exists ( $name, $result ) )
|
||||
{
|
||||
$result[$name] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fclose ( $f );
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
?>
|
@ -1,9 +1,7 @@
|
||||
<h2>IITC Browser Addon</h2>
|
||||
|
||||
<?php
|
||||
|
||||
if ( $path != "release" )
|
||||
print "<div class=\"alert alert-block alert-error\"><b>NOTE</b>: the <b>$path</b> build is currently selected. <a href=\"?page=desktop\">Return to the standard build</a>.</div>";
|
||||
include_once ( "code/desktop-download.php" );
|
||||
?>
|
||||
|
||||
<div class="alert alert-block">
|
||||
@ -51,16 +49,9 @@ Check your browser documentation for details on installing userscripts.
|
||||
<h3>Download</h3>
|
||||
|
||||
<?php
|
||||
$iitc_details = loadUserScriptHeader ( "$path/total-conversion-build.user.js" );
|
||||
$iitc_version = preg_replace ( '/^(\d+\.\d+\.\d+)\.(\d{8}\.\d{6})/', '\1<small class="muted">.\2</small>', $iitc_details['@version'] );
|
||||
iitcDesktopDownload ( "release" );
|
||||
?>
|
||||
|
||||
<p>
|
||||
IITC version <?php print $iitc_version;?>
|
||||
</p>
|
||||
|
||||
<a class="btn btn-large btn-primary" href="<?php print $path;?>/total-conversion-build.user.js">Download</a>
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
@ -71,47 +62,6 @@ Plugins extend/modify the IITC experience. You do <b>not</b> need to install all
|
||||
a minority of users.
|
||||
</p>
|
||||
|
||||
<table class="table table-condensed table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>ID / Version</th>
|
||||
<th>Description</th>
|
||||
<th>Download</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
<?php
|
||||
foreach ( glob ( "$path/plugins/*.user.js" ) as $path )
|
||||
{
|
||||
$basename = basename ( $path, ".user.js" );
|
||||
|
||||
$details = loadUserScriptHeader ( $path );
|
||||
|
||||
print "<tr id=\"plugin-$basename\">\n";
|
||||
|
||||
# remove 'IITC Plugin: ' prefix if it's there, for neatness
|
||||
$name = preg_replace ( '/^IITC plugin: /i', '', $details['@name'] );
|
||||
|
||||
# format extended version info in less prominant font
|
||||
$version = preg_replace ( '/^(\d+\.\d+\.\d+)\.(\d{8}\.\d{6})/', '\1<small class="muted">.\2</small>', $details['@version'] );
|
||||
|
||||
# remove unneeded prefix from description
|
||||
$description = preg_replace ( '/^\[[^]]*\] */', '', $details['@description'] );
|
||||
|
||||
print "<td>$name</td>";
|
||||
print "<td>$basename<br />$version</td>";
|
||||
print "<td>$description</td>";
|
||||
print "<td><a href=\"$path\" class=\"btn btn-small btn-primary\">Download</a></td>";
|
||||
|
||||
# print "<a href=\"$path\">".$details['@name']."</a> <i>$name - version ".$details['@version']."</i>: <br/>\n";
|
||||
# print $details['@description'];
|
||||
|
||||
print "</tr>\n";
|
||||
}
|
||||
|
||||
iitcDesktopPluginDownloadTable ( "release" );
|
||||
?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
55
website/page/donate.php
Normal file
@ -0,0 +1,55 @@
|
||||
<h2>Donate</h2>
|
||||
|
||||
<p>
|
||||
Thank you for wanting to support IITC. The developers have
|
||||
<a href="https://github.com/jonatkins/ingress-intel-total-conversion/issues/211">discussed this</a>
|
||||
and decided that we would prefer you make donations to charity.
|
||||
</p>
|
||||
|
||||
|
||||
<p>
|
||||
Our current suggestions are:
|
||||
</p>
|
||||
|
||||
<div class="media">
|
||||
<a class="pull-left" href="http://www.fsf.org/" style="min-width: 160px">
|
||||
<img class="media-object" src="assets/img/fsf-logo.png" alt="FSF Logo" title="FSF">
|
||||
</a>
|
||||
<div class="media-body">
|
||||
<h3 class="media-heading">Free Software Foundation</h3>
|
||||
<p>
|
||||
The <abbr title="Free Software Foundation">FSF</abbr> is a nonprofit with a worldwide mission to promote
|
||||
computer user freedom and to defend the rights of all free software users. <small><a href="http://www.fsf.org/about/">[more]</a></small>
|
||||
</p>
|
||||
<p>
|
||||
<a class="btn btn-primary" href="https://my.fsf.org/donate">Donate to the FSF</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="media">
|
||||
<a class="pull-left" href="https://www.eff.org/" style="min-width: 160px">
|
||||
<img class="media-object" src="assets/img/eff-logo.png" alt="EFF Logo" title="EFF">
|
||||
</a>
|
||||
<div class="media-body">
|
||||
<h3 class="media-heading">Electronic Frontier Foundation</h3>
|
||||
<p>
|
||||
The <abbr title="Electronic Frontier Foundation">EFF</abbr>:
|
||||
<ul>
|
||||
<li>Defend free speech for bloggers, journalists, dissidents and ordinary people online.</li>
|
||||
<li>Protect your privacy by fighting warrantless electronic searches and surveillance.</li>
|
||||
<li>Advocate for copyright and patent laws that promote rather than chill innovation.</li>
|
||||
<li>Educate the public about policy and legislation that threaten online freedom around the world, such as SOPA, ACTA, and TPP.</li>
|
||||
</ul>
|
||||
<small><a href="https://www.eff.org/about">[more]</a></small>
|
||||
</p>
|
||||
<p>
|
||||
<a class="btn btn-primary" href="https://supporters.eff.org/donate">Donate to the EFF</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</p>
|
||||
|
||||
<h3>
|
@ -36,9 +36,26 @@ IITC Mobile is still in the early stages of development. Many things do not yet
|
||||
|
||||
<h3>Download</h3>
|
||||
|
||||
<p>
|
||||
IITC Mobile version 0.3 (with IITC version 0.11.2).
|
||||
</p>
|
||||
<?php
|
||||
|
||||
<a href="mobile/IITC-Mobile-0.3.apk" class="btn btn-large btn-primary">Download</a>
|
||||
include_once ( "code/mobile-download.php" );
|
||||
|
||||
$apkfile = "mobile/IITC-Mobile-0.3.apk";
|
||||
|
||||
|
||||
if ( file_exists($apkfile) )
|
||||
{
|
||||
iitcMobileDownload ( $apkfile );
|
||||
}
|
||||
else
|
||||
{
|
||||
print "<div class=\"alert alert-error\">Error: <b>$apkfile</b> not found</div>\n";
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
|
||||
<div class="alert alert-info">
|
||||
As IITC Mobile is regularly updated, you may want to consider trying the latest
|
||||
<a href="?page=test#test-mobile">test build</a>.
|
||||
</div>
|
||||
|
81
website/page/test.php
Normal file
@ -0,0 +1,81 @@
|
||||
<h2>Test Builds</h2>
|
||||
|
||||
<p>
|
||||
These test builds are made available for those who would like to try the latest development code without
|
||||
needing to build it yourself. Automated scripts should update these builds within an hour of a change being
|
||||
<a href="https://github.com/jonatkins/ingress-intel-total-conversion/commits/master">committed</a> to Github.
|
||||
</p>
|
||||
|
||||
<div class="alert alert-block alert-error">
|
||||
Test builds are built automatically. They could be <b>broken at any time</b>. If you have any doubts about using
|
||||
unstable software, please use the standard <a href="?page=desktop">desktop</a> or <a href="?page=mobile">mobile</a>
|
||||
builds.
|
||||
</div>
|
||||
|
||||
<?php
|
||||
|
||||
include_once ( "code/desktop-download.php" );
|
||||
|
||||
|
||||
$path = "test";
|
||||
|
||||
if ( $_REQUEST['build'] == 'dev' )
|
||||
$path = "dev";
|
||||
|
||||
if ( $path != "test" )
|
||||
print "<div class=\"alert alert-block alert-error\"><b>NOTE</b>: A non-standard test build, <b>$path</b>, is currently selected. The notes <b>may not apply!</b> <a href=\"?page=test\">Return to the standard test build</a>.</div>";
|
||||
|
||||
|
||||
$timestamp_file = $path . "/.build-timestamp";
|
||||
if ( file_exists ( $timestamp_file ) )
|
||||
{
|
||||
$build_time = file_get_contents ( $timestamp_file );
|
||||
|
||||
print "<div class=\"alert alert-info\">The current test build was built at <b>$build_time</b></div>";
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<h3 id="test-desktop">Desktop test build</h3>
|
||||
|
||||
<?php
|
||||
iitcDesktopDownload ( $path );
|
||||
?>
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h4>Desktop test plugins</h4>
|
||||
|
||||
<?php
|
||||
iitcDesktopPluginDownloadTable ( $path );
|
||||
?>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3 id="test-mobile">Mobile test build</h3>
|
||||
|
||||
<?php
|
||||
|
||||
include_once ( "code/mobile-download.php" );
|
||||
|
||||
$apkfile = "$path/IITC_Mobile-test.apk";
|
||||
|
||||
|
||||
if ( file_exists($apkfile) )
|
||||
{
|
||||
iitcMobileDownload ( $apkfile );
|
||||
}
|
||||
else
|
||||
{
|
||||
print "<div class=\"alert alert-error\">Error: <b>$apkfile</b> not found</div>\n";
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
|
||||
|
||||
|