Files
Facilitor/APPL/API2/api2.inc
Jos Groot Lipman 9e11332d82 Versie 5.4.3 Gold A patches
svn path=/Website/trunk/; revision=23291
2014-11-10 13:25:20 +00:00

1165 lines
47 KiB
PHP
Raw Blame History

<% /*
$Revision$
$Id$
File: api2.inc
Description: Functies voor API's
Notes: Hier wordt van alles met de 'buitenwereld' gecommuniceerd.
Doel is dat de model's puur op json-data hoeven te werken
Status: Nog TODO's wegwerken
*/
%>
<!-- #include file="../Shared/save2db.inc" -->
<%
api2 = {
authenticate: function _authenticate(model)
{
var APIKEY;
if (S("fac_api_key_in_url"))
APIKEY = getQParam("APIKEY", "");
if (!APIKEY && Request.ServerVariables("HTTP_X_FACILITOR_API_KEY").Count)
APIKEY = String(Request.ServerVariables("HTTP_X_FACILITOR_API_KEY")); // Meegegeven als X-FACILITOR-API-Key
if (!APIKEY && Session("user_key") > 0)
{
user_key = Session("user_key"); // Hierdoor is de API intern te gebruiken zonder authenticatie
}
else
{
if (Session("user_key") > 0)
{} // Tijdens ontwikkeling heb je soms in tweede tab de GUI open. Laat dat ongemoeid.
else
Session.Abandon(); // Altijd, voor de zekerheid
var sql = "SELECT prs_perslid_key, prs_perslid_naam"
+ " FROM prs_perslid"
+ " WHERE prs_perslid_verwijder IS NULL"
+ " AND prs_perslid_apikey = " + safe.quoted_sql(APIKEY);
var oRs = Oracle.Execute(sql);
if (oRs.Eof || !APIKEY)
{
Response.Status = "401 Unauthorized";
//Response.AddHeader("WWW-Authenticate", "Basic realm=\"FACILITOR API\"");
Response.End;
};
__Log("API2 User is: " + oRs("prs_perslid_naam").Value);
/* global */ user_key = oRs("prs_perslid_key").Value;
oRs.Close();
}
/* global */ user = new Perslid(user_key);
CheckForLogging(Request.QueryString("logging")); // Nu pas kan autorisatie via user gecontrolerd worden
// Impersonate?
var IMPERS;
if (S("fac_api_key_in_url"))
IMPERS = getQParam("SWITCHUSER", "");
if (!IMPERS && Request.ServerVariables("HTTP_X_FACILITOR_SWITCH_USER").Count)
IMPERS = String(Request.ServerVariables("HTTP_X_FACILITOR_SWITCH_USER")); // Meegegeven als X-FACILITOR-SWITCH-USER
if (IMPERS) // && S("fac_api_allow_impersonate")
{
var sql = "SELECT prs_perslid_key, prs_perslid_naam"
+ " FROM prs_perslid"
+ " WHERE prs_perslid_verwijder IS NULL"
+ " AND prs_perslid_oslogin = " + safe.quoted_sql_upper(IMPERS);
var oRs = Oracle.Execute(sql);
if (oRs.Eof)
{
Response.Status = "412 Invalid X-Facilitor-Switch-User header";
Response.End;
};
__Log("IMPERS User is: " + oRs("prs_perslid_naam").Value);
var other_user_key = oRs("prs_perslid_key").Value;
oRs.Close();
if (model.impersonate_auth)
{
var xfunc = user.func_enabled2(model.module, { prs_key: other_user_key, isOptional: true });
var can = (xfunc && xfunc.canRead(model.impersonate_auth));
if (can)
/* global */ user_key = other_user_key;
}
if (user_key != other_user_key)
{
Response.Status = "412 Unauthorized X-Facilitor-Switch-User header";
Response.End;
}
}
},
process: function _process(model)
{
Session.Codepage = 65001; // We doen *uitsluitend* utf-8
Response.Charset = 'utf-8';
api2.authenticate(model);
var method = String(Request.ServerVariables("REQUEST_METHOD"));
if (!/GET|PUT|POST|DELETE/.test(method)) // Overigens houdt IIS deze al eerder tegen
{
Response.Status = "405 Method not allowed";
Response.End;
}
if (!("REST_" + method in model))
{
Response.Status = "501 Not Implemented";
// TODO The response MUST include an Allow header containing a list of valid methods for the requested resource.
Response.End;
}
var jsondata = {};
var filter = api2.qs2json();
filter = api2.plugin.transform_filter(filter);
var requestparams = { filter: filter, include: getQParamArray("include", []) };
if (/PUT|POST/.test(method)) // Dan is er in de body data meegestuurd
{
switch (getQParamSafe("format", "invalid").toLowerCase())
{
case "json":
{
var parsed = RequestJSON();
if (parsed.error)
api2.error(500, "Error parsing input JSON: " + parsed.error);
jsondata = api2.plugin.transform_incoming(requestparams, parsed.json);
if (!jsondata)
api2.error(500, "Error parsing input JSON: Empty");
break;
}
case "xml":
{
var parsed = RequestXML();
if (parsed.error)
api2.error(500, "Error parsing input XML: " + parsed.error);
jsondata = api2.xml2json(parsed.xml);
if (!jsondata)
api2.error(500, "Error parsing input XML: Empty");
break;
}
default:
UNKNOWN_FORMAT_TYPE;
}
if (!jsondata || !(model.record_name in jsondata || (model.multi_update && model.records_name in jsondata)))
{
api2.error(500, "No '{0}' found in input".format(model.record_name));
}
}
var key = getQParamInt("id", -1); // Voor POST/PUT/DELETE
var isSingle = /PUT|POST|DELETE/.test(method) || (key>0); // PUT, POST en DELETE altijd single
if (getQParamSafe("format", "json") == "api")
{
// TODO: Onderstaande in een of ander standaardformaat opleveren?
var result = { id: model.records_name,
methods: [],
includes: [],
filters: [] };
for (var i in model.includes)
result.includes.push(i);
if ("REST_GET" in model)
result.methods.push("GET");
if ("REST_PUT" in model)
result.methods.push("PUT");
if ("REST_POST" in model)
result.methods.push("POST");
if ("REST_DELETE" in model)
result.methods.push("DELETE");
for (var i in model.fields) // TODO: Misschien beter via een (beknopt) field-object?
{ // TODO: We missen hard-coded filters als reservableequipment/allowedinroom nu nog
if (model.fields[i].filter)
result.filters.push({ id: model.fields[i].name,
filter: model.fields[i].filter,
type: model.fields[i].typ
});
}
}
else
{
if (method == "GET" || method == "DELETE" || model.record_name in jsondata)
{
var result = model["REST_" + method]( requestparams, jsondata, key );
}
else
{ // Loop door de multiple records en geef de REST_ functie altijd <20><>n record
for (var record in jsondata[model.records_name])
{
var thisdata = {};
thisdata[model.record_name] = jsondata[model.records_name][record];
var result = model["REST_" + method]( requestparams, thisdata, thisdata[model.record_name].id );
}
}
}
switch (method)
{
case "DELETE":
{
Response.Status = "204 No Content";
Response.End;
break;
}
case "GET":
{
data = result;
break;
}
case "PUT":
case "POST":
{
var key = result.key;
if (key)
{
var params = { filter: api2.qs2json(), include: getQParamArray("include", []) }, jsondata, key
// requestparams.include is mogelijk uitgebreid met wat er in de body stond
data = model.REST_GET({ filter: { id: key }, include: requestparams.include }); // resulterende data weer terug
__Logj(data);
}
else
{
data = [];
isSingle = false;
}
}
}
api2.deliver(data, getQParamSafe("format", "json"),
model.records_name,
model.record_name,
isSingle);
},
qs2json: function _qs2json(params)
{
var filter = {};
for (var i = 1; i<= Request.QueryString.Count; i++)
{
filter[Request.QueryString.key(i)] = String(Request.QueryString(i));
}
return filter;
},
// Verwerk filtervelden die in de url zijn meegegeven
sqlfilter: function _sqlfilter(params, model)
{
var wheres = [];
if (params.filter)
{
for (var fld in model.fields)
{
var field = model.fields[fld];
var filter = field.filter;
if (!filter)
continue;
// We kijken of dit model-veld in de filterparameters voorkomt. Voor ranges kunnen prefixes in gebruik zijn
if (field.name in params.filter || "start_"+field.name in params.filter || "end_"+field.name in params.filter)
{
// filterval is de meegegeven filterwaarde voor dit veld
var filterval = params.filter[field.name];
// Voor ranges komt de naam (misschien) niet letterlijk voor, maar (mogelijk) met start_ of end_ ervoor
// meerdere filters voor 1 veld dus wellicht
if (filter == "range") {
filterval1 = params.filter["start_"+field.name];
filterval2 = params.filter["end_"+field.name];
//__Log(">>filterval1>>"+filterval1);
//__Log(">>filterval2>>"+filterval2);
}
// Nu is ook voor ranges alles weer normaal
//__Log(">>field>>"+field.name);
//__Log(">>filter>>"+filter);
//__Log(">>filterval>>"+filterval);
var clause;
var operand = " = ";
switch (field.typ)
{
case "key":
if (filterval instanceof Array)
{
safe_val = "(" + filterval.join(",") + ")";
operand = " IN ";
}
else if (String(filterval).indexOf(",") != -1) // let op: bij buildings/1234.json is id al numeriek gemaakt
// NB: index=-1 als het geen array is.
{
safe_val = "(" + getQParamIntArray(field.name).join(",") + ")"; // TODO: Niet via getQParamIntArray
operand = " IN ";
}
else
{
var safe_val = parseInt(filterval, 10);
if (isNaN(safe_val))
{
Response.Status = "404 Not Found";
Response.End;
}
}
break;
case "float":
var safe_val = parseFloat(filterval);
if (filter == "range")
{
// Als er maar 1 filterwaarde is, dan gedraagt zich die als start_
if (filterval && !filterval1) filterval1 = filterval;
if (filterval1) {
var safe_val1 = parseFloat(filterval1);
}
if (filterval2) {
var safe_val2 = parseFloat(filterval2);
}
if (filterval1 && filterval2) {
operand = " BETWEEN ";
safe_val = safe_val1+" AND "+safe_val2;
} else if (filterval1) {
operand = " >= ";
safe_val = safe_val1;
} else if (filterval2) {
operand = " <= ";
safe_val = safe_val2;
}
}
break;
case "number":
var safe_val = parseInt(filterval, 10);
if (filter == "range")
{
// Als er maar 1 filterwaarde is, dan gedraagt zich die als start_
if (filterval && !filterval1) filterval1 = filterval;
if (filterval1) {
var safe_val1 = parseInt(filterval1, 10);
}
if (filterval2) {
var safe_val2 = parseInt(filterval2, 10);
}
if (filterval1 && filterval2) {
operand = " BETWEEN ";
safe_val = safe_val1+" AND "+safe_val2;
} else if (filterval1) {
operand = " >= ";
safe_val = safe_val1;
} else if (filterval2) {
operand = " <= ";
safe_val = safe_val2;
}
}
break;
case "check":
var safe_val = parseInt(filterval, 10);
if (isNaN(safe_val))
{
Response.Status = "404 Not Found";
Response.End;
}
break;
case "varchar":
var safe_val = (filter == "exact" ? safe.quoted_sql(filterval) : safe.quoted_sql("%"+filterval+"%"));
break;
case "date": // onderscheid date en datetime?
case "datetime":
// exact: dbs == filterval; nodig? zoiets dan:
//if (filter == "exact")
//{
// var safe_val = new Date(parseInt(filterval, 10));
// safe_val = safe_val.toSQL();
// field.dbs = "TRUNC("+field.dbs+")";
//}
// range: (dbs >= start_value, dbs <= end_value)
if (filter == "range")
{
// Als er maar 1 filterwaarde is, dan gedraagt zich die als start_
if (filterval && !filterval1) filterval1 = filterval;
var safe_val;
if (filterval1) {
var safe_val1 = myJSON.internal_parsedate(null, filterval1);
//var safe_val1 = new Date(parseInt(filterval1, 10));
safe_val1 = safe_val1.beginToSQL();
}
if (filterval2) {
var safe_val2 = myJSON.internal_parsedate(null, filterval2);
//var safe_val2 = new Date(parseInt(filterval2, 10));
safe_val2 = safe_val2.endToSQL();
}
if (filterval1 && filterval2) {
operand = " BETWEEN ";
safe_val = safe_val1+" AND "+safe_val2;
} else if (filterval1) {
operand = " >= ";
safe_val = safe_val1;
} else if (filterval2) {
operand = " <= ";
safe_val = safe_val2;
}
}
break;
default:
UNKNOWN_FILTER_TYPE;
}
var dbs = field.dbs;
if (model.aliasprefix)
dbs = model.aliasprefix + dbs;
if (dbs.indexOf(".") < 0)
dbs = model.table + "." + field.dbs;
if (field.sql)
dbs = field.sql; /* dit werkt bv voor cnt-XD */
if (filter == "like")
{
operand = " like ";
if (field.typ == "varchar")
{
dbs = "UPPER("+dbs+")";
safe_val = safe_val.toUpperCase();
}
}
wheres.push(dbs + operand + safe_val);
}
}
}
return wheres;
},
// Als er voor een veldnaam een alias moet worden gebruikt, doe het dan op deze manier.
sqlfield_alias: function _sqlfield_alias(model, field)
{
return model.aliasprefix + (field.dbs == model.primary ? field.dbs : field.name);
},
// Bepaal bij een GET welke velden op te halen
sqlfields: function _sqlfields(params, model)
{
model.aliasprefix = model.aliasprefix || "";
var selects = [];
var tables = [ model.table ];
var wheres = [];
var name_cnt = 0;
for (var fld in model.fields)
{
var field = model.fields[fld];
if (!field.name)
continue;
var dbs = field.dbs;
if (field.sql)
{
if (model.aliasprefix)
dbs = model.aliasprefix + field.name;
selects.push(field.sql + " AS " + dbs);
}
else if (dbs)
{
// veldnamen moeten gelijk zijn bij het ophalen. Zie ook: sql2jsonval
if (dbs.indexOf(".") < 0)
{
dbs = model.table + "." + dbs;
if (model.aliasprefix)
dbs += " AS " + api2.sqlfield_alias(model, field);
}
selects.push(dbs);
}
if (field.val instanceof Function)
continue;
if (field.foreign && typeof field.foreign == 'string') // de functions komen later
{
var foreign = foreignKeyTable(field.foreign);
if (!foreign)
MISSING_FOREIGN;
var fieldname = (foreign.name||foreign.desc);
field.foreignsql = "SELECT " + fieldname
+ " FROM " + foreign.tbl + " " + (foreign.alias||"xx")
+ " WHERE ";
if (foreign.where)
field.foreignsql += foreign.where + " AND ";
field.foreignsql += (foreign.alias||"xx") + "." + foreign.key + " = " + (field.sql||(field.dbs.indexOf(".") < 0? model.table + ".":"") + field.dbs);
}
if (field.foreignsql)
{
name_cnt ++;
field._foreignname = "foreign_" + name_cnt; // Genereer een niet al te extreem lange naam
selects.push("(" + field.foreignsql + ") " + " AS " + model.aliasprefix + field._foreignname);
}
}
if (params.include && model.includes)
{
// Dubbelen er uit halen, dat geeft lelijke foutmeldingen en kan ontstaan
// als je include=visitors in de url meegeeft <20>n visitors in de body POST
var tmp = {};
for (var i in params.include) {
tmp[params.include[i]] = 1;
}
params.include = [];
for (var i in tmp) {
params.include.push(i);
}
var inccnt = 0;
for (var i in params.include)
{
if (params.include[i] in model.includes)
{
var inc = model.includes[params.include[i]];
if (inc.model)
{
if (inc.single_only && !params.filter.id)
{
abort_with_warning("Include '{0}' only allowed with single '{1}'".format(params.include[i], model.record_name));
}
inccnt ++;
inc.model.aliasprefix = "I" + inccnt + "_";
var incquery = api2.sqlfields(params, inc.model);
selects = selects.concat (incquery.selects);
tables = tables.concat (incquery.tables);
wheres = wheres.concat (incquery.wheres);
if (inc.joinfunction)
{
var where = inc.joinfunction(params);
wheres.push (where);
}
else
{
// simpel op joinfield
wheres.push ( model.table + "." + model.primary + "=" + inc.model.table + "." + inc.joinfield + "(+)");
}
}
}
else
__Log("Unknown include '{0}' requested".format(params.include[i]));
}
}
return { selects: selects, tables: tables, wheres: wheres };
},
// TODO the_key *moet* bestaan. Andere filtervelden negeren we
update_fields: function _update_fields(params, model, jsondata)
{
//__DoLogj(jsondata)
if (jsondata[model.record_name])
jsondata = jsondata[model.record_name]; // dereference
var fields = [];
for (var fld in model.fields)
{
var field = model.fields[fld];
// De key halen we uit de url, die in de JSON negeren we
if (field.name == "id")
continue;
if ("sql" in field)
continue;
if (!(field.name in jsondata) && !field.fnval)
continue;
if (field.readonly)
continue;
if (field.fnval)
var newval = field.fnval(jsondata);
else // simpel
var newval = jsondata[field.name];
switch (field.typ)
{
case "key": // De foreign keys action { "id": "5", "name": "afhalen" }
if (newval && typeof newval == "object" && "id" in newval)
{ // dereference
newval = newval.id;
jsondata[field.name] = newval;
}
break;
case "date":
case "datetime":
// LET OP: Een (new Date) gemaakt binnen een plugin is vreemd genoeg geen (instanceof Date)
// Waarschijnlijk gebruikt een wsc een ander Date object als (ASP)JScript?
if (typeof newval == "object" && !(newval instanceof Date))
newval = new Date(newval);
if (newval !== null && !(newval instanceof Date))
{
abort_with_warning("Invalid " + field.typ + " (" + model.record_name + "." + field.name + "): " + newval);
}
break;
case "float":
case "number":
if (isNaN(newval))
abort_with_warning("Invalid number (" + model.record_name + "." + field.name + "): " + newval);
break;
}
if (field.dbs.indexOf(".") >= 0) // complexe foreign key
continue;
//__Log("newval="+newval);
var newfield = { dbs: field.dbs,
typ: field.typ,
track: field.track,
val: newval
};
fields.push(newfield);
}
return fields;
},
// Verwerk de POST en PUT (== insert en update)
process_includes: function(params, model, jsondata, the_key)
{
if (jsondata[model.record_name])
jsondata = jsondata[model.record_name]; // dereference
if (!model.includes)
return [];
for (var incname in model.includes)
{
if (incname in jsondata) // i=="visitors"
{
var inc = model.includes[incname];
if (inc.model) // andere includes zijn nog niet bij te werken
{
// Als je bij een PUT/POST een include in de BODY zet geven we heb automagisch terug
params.include.push(incname);
var incmodel = inc.model;
// Vul existing_includes met bestaande records in de database
var sql = "SELECT " + incmodel.primary
+ " FROM " + incmodel.table
+ " WHERE " + incmodel.table + "." + inc.joinfield + "=" + the_key;
var existing_includes = {};
var oRs = Oracle.Execute(sql);
while (!oRs.Eof)
{
//__DoLog("Found in DB " + incname + " " + oRs(incmodel.primary).Value);
existing_includes[oRs(incmodel.primary).Value] = { found: false }; // vooralsnog niet in json-data gevonden
oRs.MoveNext();
}
oRs.Close();
var incdata = jsondata[incname]; // Array zoals via API aangeleverd
for (var j=0; j<incdata.length; j++)
{
var inckey = incdata[j]["id"]; // Die moet er zijn
if (!inckey || inckey < 0 || params.isNew)
{
__Log("Nu ga ik een '{0}' toevoegen".format(incname));
incmodel.REST_POST(params, incdata[j], the_key);
}
else if (inckey in existing_includes)
{
__Log("Nu ga ik '{0}' {1} updaten".format(incname, inckey));
incmodel.REST_PUT(params, incdata[j], inckey);
existing_includes[inckey].found = true;
}
}
for (oldi in existing_includes)
{
if (!existing_includes[oldi].found)
{
incmodel.REST_DELETE(params, incdata[j], oldi);
}
}
}
else
{
//if (inc.func)
}
}
}
},
// Geeft de GET terug
sql2jsonval: function(oRs, field, model)
{
if (field.val instanceof Function)
var val = field.val(oRs, field, model);
else if (field.dbs.indexOf(".") < 0)
{
var sqlfieldname = (model.aliasprefix ? api2.sqlfield_alias(model, field) : field.dbs);
var val = oRs(sqlfieldname).Value;
}
else
var val = oRs(field.dbs.split(".")[1]).Value;
if (field.typ == "date" && (val != null))
val = new Date(val)
if (field.typ == "datetime" && (val != null))
val = new Date(val)
// Wat te doen met lege waarde
// action: null
// action: {key: null, name: null}
// action: {}
// of helemaal weglaten? We hebben nu de 1e optie. Dat is zelfdocumenterend
// En wat bij een leeg (include) array? Dan kun je ook nog occupations:[] krijgen
if (val !== null && (field.foreign || field.foreignsql))
{
if (field.foreignsql)
{
var name = oRs(model.aliasprefix + field._foreignname).Value;
if (name != null && typeof name == "date" )
name = new Date(name);
}
else
{
var name = field.foreign(val);
}
val = { id: val };
val.name = name;
}
return val;
},
sql2jsonfields: function (oRs, model)
{
var record = {};
for (var fld in model.fields)
{
var field = model.fields[fld];
if (!field.name)
continue;
var val = api2.sql2jsonval(oRs, field, model);
if (field.readonly && !val.id)
continue;
record[field.name] = val;
}
return record;
},
sql2json: function _sql2json(params, sql, model)
{
var prefuncdata;
var prefuncdatainitialized = false;
var prefilterfuncdata;
if (model.filter && model.filter.prefunc)
prefilterfuncdata = model.filter.prefunc(params);
var data = [];
var oRs = Oracle.Execute(sql);
var lastkey = -1;
// Merk op dat onze recordset meer regels kan bevatten dan je zou verwachten
// omdat de includes er bij zijn gejoind
while (!oRs.Eof)
{
var key = oRs(model.primary).Value;
if (key != lastkey)
{
if (lastkey > 0 && "id" in record) // Record was er mogelijk uit gefilterd
data.push(record);
var record = {};
}
// Complexe filtering die we niet voor elkaar kregen met een WHERE-clause
if (model.filter && model.filter.func)
{
if (!model.filter.func(oRs, params, prefilterfuncdata))
{
lastkey = key;
oRs.MoveNext();
continue;
}
}
var fld;
for (fld in model.fields)
{
var field = model.fields[fld];
if (!field.name)
continue;
var val = api2.sql2jsonval(oRs, field, model)
if (field.readonly && !val.id)
continue;
record[field.name] = val;
}
if (params.include && model.includes)
{
for (var i in params.include) // welke includes worden opgevraagd?
{
var incname = params.include[i];
if (incname in model.includes) // Ondersteunen we deze include?
{
if (!(incname in record))
record[incname] = [];
var incmodel = model.includes[incname].model;
if (incmodel)
{ // Standaard include via model. Ons 'hoofd' record zal meerdere keren
// uit de query komen met telkens een ander 'include' record
incmodel.aliasprefix = incmodel.aliasprefix || "";
if (oRs(incmodel.aliasprefix + incmodel.primary).value == null) // Geen record door outer join
continue;
record[incname].push(api2.sql2jsonfields(oRs, incmodel));
}
else if (model.includes[incname].func) // include via callback functie zoals reservablerooms/occupation
{
if (model.includes[incname].prefunc && !prefuncdatainitialized)
{
prefuncdata = model.includes[incname].prefunc(params);
prefuncdatainitialized = true;
}
var incdata = model.includes[incname].func(key, params, oRs, record, prefuncdata);
if (incdata !== null)
record[incname] = incdata; // de callback geeft de gehele include in een keer
}
}
else
__Log("Unknown include '{0}' requested".format(incname));
}
}
lastkey = key;
oRs.MoveNext();
}
if (lastkey > 0)
data.push(record);
return data;
},
// Data is een array met 'records'
deliver: function _deliver(data, format, records_name, record_name, single )
{
if (single && !data.length)
{
Response.Status = "404 Not Found";
Response.End;
}
switch (format)
{
case "api":
{
var str_antwoord = JSON.stringify(data, null, getQParam("pretty","0")=="1"?2:0);
Response.ContentType = "application/json";
break;
}
case "json":
var result = { };
if (single)
result[record_name] = data[0];
else
result[records_name] = data;
var resultdata = api2.plugin.transform_outgoing({}, result);
var str_antwoord = JSON.stringify(resultdata, null, getQParam("pretty","0")=="1"?2:0);
var jsonp = getQParam("jsonp", getQParam("callback",""));
if (jsonp)
{
str_antwoord = jsonp + "(" + str_antwoord + ")";
Response.ContentType = "application/javascript";
}
else
Response.ContentType = "application/json";
break;
case "html":
Response.ContentType = "text/html";
var result = { };
if (single)
result[record_name] = data[0];
else
result[records_name] = data;
var antwoord = JSON.stringify(result, null, 2);
var str_antwoord = "<!DOCTYPE html><html><head></head><body><pre>"
+ Server.HTMLEncode(antwoord)
+ "</pre></body></html>";
break;
case "xml":
Response.ContentType = "text/xml";
var xml_antwoord = api2.json2xml(data, records_name, record_name, single);
// TODO: Output XSL transform ondersteunen?
if (getQParam("pretty","0")=="1")
{
var style = new ActiveXObject("MSXML2.DOMDocument.6.0");
style.async = false;
style.resolveExternals = false;
style.load(Server.MapPath(rooturl + "/appl/shared/indent.xsl")); // De stylesheet laden. API's redeneren vanuit de root
if (style.parseError.errorCode)
{
abort_with_warning("XSL error: " + style.parseError.reason + " @ " + style.parseError.line + "." + style.parseError.linepos );
}
var str_antwoord = xml_antwoord.transformNode(style); // terugstoppen in antwoord
}
else
var str_antwoord = xml_antwoord.xml;
//Response.ContentType = "application/json";
//var str_antwoord = JSON.stringify(api2.xml2json(xml_antwoord), null, 2);;
break;
default:
WRONG_FORMAT;
}
// str_antwoord heeft nu het te versturen antwoord
// Bepaal eTag
var oCrypto = new ActiveXObject("SLNKDWF.Crypto");
var eTag = '"' + oCrypto.hex_sha1(String(S("cache_changecounter")) + "_" + str_antwoord).toLowerCase() + '"';
Response.AddHeader("ETag", eTag);
if (Request.ServerVariables("HTTP_IF_NONE_MATCH") == eTag)
{ // We hebben een match! Effectief besparen wel alleen op dataverkeer, de queries zijn al geweest
Response.Clear();
Response.Status = "304 Not modified";
Response.End;
}
// if (API.apidata.loglevel) __Log2File(str_antwoord, APIname + "_OUT");
Response.write(str_antwoord);
},
// TODO: Wanneer attributes gebruiken en wanneer (sub)-elements?
// Streven: data == xml2json(json2xml(data))
json2xml: function _json2xml(data, rootname, record_name, single)
{
var xmlDoc = new ActiveXObject("MSXML2.DOMDocument.6.0");
xmlDoc.appendChild(xmlDoc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"utf-8\""));
var record2json = function(record, record_name)
{
var elementRecord = xmlDoc.createElement(record_name);
for (var fld in record)
{
var elementField = xmlDoc.createElement(fld);
if (record[fld] instanceof Date)
{
var elementFieldText = xmlDoc.createTextNode(String(record[fld].toJSON()));
elementField.appendChild(elementFieldText);
}
else if (record[fld] instanceof Array)
{
for (var i = 0; i < record[fld].length; i++)
elementField.appendChild(record2json(record[fld][i], "visitor")); // TODO Hardcoded
}
else if (record[fld] && typeof record[fld] == "object") // TODO: veronderstelt nog hardcoded dat dit foreign met name/key is
{ // misschien && "id" in record[fld]
if ("name" in record[fld] && "id" in record[fld])
{
elementField.setAttribute("name", record[fld].name);
elementField.setAttribute("id", record[fld].id);
}
else
elementField = record2json(record[fld], fld);
}
else
{
var elementFieldText = xmlDoc.createTextNode(record[fld]||"");
elementField.appendChild(elementFieldText);
}
elementRecord.appendChild(elementField);
}
return elementRecord;
};
if (single)
{
xmlDoc.appendChild(record2json(data[0], record_name));
}
else
{
var arrayElement = xmlDoc.createElement(rootname);
for (var i = 0; i < data.length; i++)
arrayElement.appendChild(record2json(data[i], record_name));
xmlDoc.appendChild(arrayElement);
}
return xmlDoc;
},
// Streven: data == json2xml(xml2json(xml))
// http://davidwalsh.name/convert-xml-json maar @attributes er uit gehaald
xml2json: function _xml2json(xml)
{
// Create the return object
var obj = {};
if (xml.nodeType == 1)
{ // element
// do attributes
if (xml.attributes.length > 0)
{
// JGL removed: obj["@attributes"] = {};
for (var j = 0; j < xml.attributes.length; j++)
{
var attribute = xml.attributes.item(j);
obj[attribute.nodeName] = attribute.nodeValue;
}
}
}
else if (xml.nodeType == 3)
{ // text
obj = xml.nodeValue;
}
// do children
if (xml.hasChildNodes())
{
for(var i = 0; i < xml.childNodes.length; i++)
{
var item = xml.childNodes.item(i);
var nodeName = item.nodeName;
if (typeof(obj[nodeName]) == "undefined")
{
// JGL Added: Only one Textnode is simplified. Autodetect data
if (item.nodeType == 3 && xml.childNodes.length == 1)
{
var dt = myJSON.internal_parsedate(null, item.nodeValue);
if (dt && dt instanceof Date)
return dt;
return item.nodeValue;
}
obj[nodeName] = api2.xml2json(item);
}
else
{
if (typeof(obj[nodeName].push) == "undefined")
{
var old = obj[nodeName];
obj[nodeName] = [];
obj[nodeName].push(old);
}
obj[nodeName].push(api2.xml2json(item));
}
}
}
return obj;
},
error: function (code, msg)
{
abort_with_warning(msg, code)
},
find_plugin: function()
{
var plugin_name = getQParamSafe("plugin", "").toLowerCase();
if (!plugin_name)
return {};
var fso = new ActiveXObject("Scripting.FileSystemObject");
var paths = ["/cust/" + customerId, "/cust", "/appl/api2"]; // Hieronder zoeken naar '/plugins' folder
for (var p in paths)
{
var ppath = Server.MapPath(rooturl + paths[p] + "/plugins/" + plugin_name + ".wsc")
//__Log(ppath);
if (fso.FileExists(ppath))
{
try
{
var hook = GetObject("script:" + ppath);
}
catch(e)
{
api2.error(500, "Loading {0} failed: {1}".format(ppath, e.description));
}
hook.initialize({ S: S, Oracle: Oracle, customerId: customerId, safe: safe });
return hook;
}
}
api2.error(500, "Undefined plugin {0}".format(plugin_name));
},
plugin: {
transform_filter: function(filter)
{
var outdata = filter;
var hook = api2.find_plugin();
if ("transform_filter" in hook)
{
outdata = hook.transform_filter(filter);
}
hook = null;
return outdata;
},
transform_incoming: function(params, data)
{
var outdata = data;
var hook = api2.find_plugin();
if ("transform_incoming" in hook)
{
outdata = hook.transform_incoming(params, data);
}
hook = null;
return outdata;
},
transform_outgoing: function(params, data)
{
var outdata = data;
var hook = api2.find_plugin();
if ("transform_outgoing" in hook)
outdata = hook.transform_outgoing(params, data);
hook = null;
return outdata;
}
}
}
// LET OP: Verwacht wordt dat de JSON-code in de body utf-8 encoded is, niet windows-1252!
// (in de praktijk moet je *moeite* doen om windows-1252 te krijgen dus dit is handiger)
function RequestJSON()
{
var jvraag;
if(Request.TotalBytes > 0)
{
var lngBytesCount = Request.TotalBytes;
jvraag = BytesToStr(Request.BinaryRead(lngBytesCount));
}
__Log("Vraag: " + jvraag);
try
{
var vraag = myJSON.parse(jvraag);
}
catch (e)
{
__DoLog("JSON eval faalt met: {0}<br>{1}".format(e.description, jvraag), "ffff00");
return { error: e.description };
}
return { json: vraag };
}
function RequestXML()
{
try
{
var inputXML = Server.CreateObject("MSXML2.DOMDocument.6.0");
inputXML.load(Request);
}
catch (e)
{
return { error: e.description };
}
if (inputXML.parseError.errorCode)
{
return { error: inputXML.parseError.reason + " @ " + inputXML.parseError.line + "." + inputXML.parseError.linepos };
}
return { xml: inputXML };
}
function BytesToStr(bytes)
{
var stream = Server.CreateObject("ADODB.Stream");
stream.type = 1;
stream.open;
stream.write(bytes);
stream.position = 0;
stream.type = 2; // Text
stream.charset = "utf-8";
var sOut = stream.readtext();
stream.close;
return sOut;
}
function getQParamISODate(pName, defVal)
{
return _get_ParamISODate(Request.Querystring, pName, defVal)
}
function getFParamISODate(pName, defVal)
{
return _get_ParamISODate(Request.Form, pName, defVal)
}
function _get_ParamISODate(pColl, pName, defVal)
{
var strval = _get_Param(pColl, pName, defVal, true); // force: een lege waarde wordt als afwezig beschouwd
if (strval)
{
var dt = myJSON.internal_parsedate(null, strval);
if (dt && dt instanceof Date)
return dt;
}
if (defVal instanceof Date)
{
return defVal;
}
if (defVal === null) // bewust triple===
{
return null;
}
// Error message will get to client and/or IIS logfiles
eval("INTERNAL_ERROR_PARAMETER_" + pName + "_IS_NOT_ISODATE");
}
%>