1646 lines
66 KiB
PHP
1646 lines
66 KiB
PHP
<% /*
|
||
$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;
|
||
|
||
if (typeof NO_ADDHEADER == "undefined" && Request.Servervariables("HTTP_FCLT_VERSION").Count > 0)
|
||
{ // wordt opgepikt door FCLTAPI.DLL voor in de logging en daarna gestript. Niet in Fiddler dus
|
||
Response.AddHeader ("FCLT_USERID", customerId + "\\" + String(user_key));
|
||
}
|
||
|
||
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';
|
||
|
||
if (typeof model == "function") // Nieuwe stijl is het een function. Even compatible.
|
||
model = new model();
|
||
|
||
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: [],
|
||
fields: []
|
||
};
|
||
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 fld in model.fields)
|
||
{ // TODO: We missen hard-coded filters als reservableequipment/allowedinroom nu nog
|
||
if (!model.fields.hidden)
|
||
result.fields.push({ id: fld,
|
||
filter: model.fields[fld].filter,
|
||
type: model.fields[fld].typ,
|
||
label: model.fields[fld].label
|
||
});
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (method == "DELETE")
|
||
{
|
||
var result = model["REST_" + method]( requestparams, key );
|
||
}
|
||
else if (method == "GET" || 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
|
||
__Log(data);
|
||
}
|
||
else
|
||
{
|
||
data = [];
|
||
isSingle = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
api2.deliver(data, model, getQParamSafe("format", "json"),
|
||
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;
|
||
},
|
||
form2JSONdata: function _form2JSONdata(model, params, formfields) // Maak een jsondata-object voor gebruik vanuit model_xxx.inc
|
||
{
|
||
var jsondata = {};
|
||
jsondata[model.record_name] = {id: params.filter.id};
|
||
|
||
// Form parameters van gewone velden.
|
||
for (i=0; i<formfields.length; i++)
|
||
{
|
||
// Voeg een nieuwe combinatie naam:waarde toe aan het jsondata object als er een frm of val bekend is.
|
||
var val;
|
||
if (formfields[i].frm)
|
||
{
|
||
var typ = model.fields[formfields[i].name].typ;
|
||
|
||
// Haal waarde uit de formparameters.
|
||
if (typ == "check" || typ == "check0")
|
||
{
|
||
if (Request.Form("has_" + formfields[i].frm).count == 0)
|
||
continue; // niet aanwezig
|
||
}
|
||
else
|
||
{
|
||
if (Request.Form(formfields[i].frm).count == 0)
|
||
continue; // niet aanwezig
|
||
}
|
||
|
||
switch (typ)
|
||
{
|
||
case "float":
|
||
val = getFParamFloat(formfields[i].frm, null);
|
||
break;
|
||
case "key":
|
||
case "number":
|
||
val = getFParamInt(formfields[i].frm, null);
|
||
break;
|
||
case "check":
|
||
case "check0":
|
||
val = (Request.Form(formfields[i].frm).count==1)?1:0;
|
||
break;
|
||
case "date":
|
||
case "datetime":
|
||
val = getFParamDate(formfields[i].frm, null);
|
||
break;
|
||
case "memo":
|
||
case "varchar":
|
||
val = getFParam(formfields[i].frm, null);
|
||
break;
|
||
default:
|
||
{
|
||
__DoLog(model.fields[formfields[i].name].typ);
|
||
INTERN_ERROR_UNKNOWN_TYPE;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
val = formfields[i].val;
|
||
//
|
||
jsondata[model.record_name][formfields[i].name] = val;
|
||
}
|
||
return jsondata;
|
||
},
|
||
|
||
// Verwerk filtervelden die in de url zijn meegegeven
|
||
sqlfilter: function _sqlfilter(params, model)
|
||
{
|
||
var wheres = [];
|
||
if (params.filter)
|
||
{
|
||
if (model.parent_key in params.filter)
|
||
wheres.push(model.table + "." + model.parent_key + " = " + parseInt(params.filter[model.parent_key], 10));
|
||
else if ("pid" in params.filter)
|
||
wheres.push(model.table + "." + model.parent_key + " = " + parseInt(params.filter["pid"], 10));
|
||
for (var fld in model.fields)
|
||
{
|
||
var field = model.fields[fld];
|
||
var filter = field.filter || "exact";
|
||
|
||
// We kijken of dit model-veld in de filterparameters voorkomt. Voor ranges kunnen prefixes in gebruik zijn
|
||
var filterval = null;
|
||
if (fld in params.filter || "start_" + fld in params.filter || "end_" + fld in params.filter)
|
||
{
|
||
// filterval is de meegegeven filterwaarde voor dit veld
|
||
if (fld in params.filter)
|
||
{
|
||
filterval = params.filter[fld];
|
||
if (filterval === "" || filterval === null)
|
||
continue;
|
||
if (filterval < 0 && field.typ == "key")
|
||
continue;
|
||
}
|
||
|
||
// 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") {
|
||
var filterval1 = params.filter["start_" + fld];
|
||
var filterval2 = params.filter["end_" + fld];
|
||
// Als er maar 1 filterwaarde is, dan gedraagt zich die als start_
|
||
if (filterval && !filterval1)
|
||
filterval1 = filterval;
|
||
if ((filterval1 === "" || filterval1 === null) && (filterval2 === "" || filterval2 === null))
|
||
continue;
|
||
}
|
||
// Nu is ook voor ranges alles weer normaal
|
||
var clause;
|
||
var operand = " = ";
|
||
switch (field.typ)
|
||
{
|
||
case "key":
|
||
if (!filterval)
|
||
continue;
|
||
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(fld).join(",") + ")"; // TODO: Niet via getQParamIntArray
|
||
operand = " IN ";
|
||
}
|
||
else if (String(filterval).substr(0,1) == '~'
|
||
&& field.foreign
|
||
&& typeof field.foreign == 'string'
|
||
) // Als je een foreign filter met ~ begint dan filtert hij op de tekst
|
||
{
|
||
operand = " IN ";
|
||
var foreign = foreignKeyTable(field.foreign);
|
||
if (!foreign)
|
||
MISSING_FOREIGN;
|
||
var fieldname = (foreign.name||foreign.desc);
|
||
var foreignsql = "SELECT " + foreign.key
|
||
+ " FROM " + foreign.tbl + " " + (foreign.alias||"xx")
|
||
+ " WHERE ";
|
||
if (foreign.where)
|
||
foreignsql += foreign.where + " AND ";
|
||
foreignsql += "UPPER(" + (foreign.alias||"xx") + "." + fieldname + ") LIKE " + safe.quoted_sql_wild("%"+String(filterval).substr(1) + "%");
|
||
safe_val = "(" + foreignsql + ")";
|
||
}
|
||
else
|
||
{
|
||
var safe_val = parseInt(filterval, 10);
|
||
if (isNaN(safe_val))
|
||
{
|
||
return ["0=1"];
|
||
}
|
||
}
|
||
break;
|
||
case "float":
|
||
var safe_val = parseFloat(filterval);
|
||
if (filter == "range")
|
||
{
|
||
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 (isNaN(safe_val))
|
||
{
|
||
return ["0=1"]; // niets gevonden
|
||
}
|
||
if (filter == "range")
|
||
{
|
||
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))
|
||
{
|
||
return ["0=1"]; // niets gevonden
|
||
}
|
||
break;
|
||
case "varchar": // Als fld.match(/^fclt_d_/) dan is het een old-style rapport filterveld
|
||
case "memo":
|
||
switch (filter)
|
||
{
|
||
case "exact":
|
||
var safe_val = safe.quoted_sql(filterval);
|
||
break;
|
||
case "like":
|
||
var safe_val = safe.quoted_sql_wild("%"+filterval+"%");
|
||
break;
|
||
case "range":
|
||
var safe_val;
|
||
if (filterval1) {
|
||
if (fld.match(/^fclt_d_/))
|
||
safe_val1 = api2.toDate(filterval1).beginToSQL();
|
||
else
|
||
safe_val1 = safe.quoted_sql(filterval1);
|
||
}
|
||
if (filterval2) {
|
||
if (fld.match(/^fclt_d_/))
|
||
safe_val2 = api2.toDate(filterval2).endToSQL();
|
||
else
|
||
safe_val2 = safe.quoted_sql(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;
|
||
}
|
||
break;
|
||
case "date": // onderscheid date en datetime?
|
||
case "datetime":
|
||
if (filter == "exact")
|
||
{
|
||
var safe_val = api2.toDate(filterval);
|
||
safe_val = safe_val.toSQL();
|
||
operand = " = ";
|
||
}
|
||
else if (filter == "range")
|
||
{
|
||
var safe_val;
|
||
if (filterval1) {
|
||
var safe_val1 = api2.toDate(filterval1);
|
||
safe_val1 = safe_val1.beginToSQL();
|
||
}
|
||
if (filterval2) {
|
||
var safe_val2 = api2.toDate(filterval2);
|
||
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 && !field.keepdbsforfilter)
|
||
dbs = field.sql; /* dit werkt bv voor cnt-XD */
|
||
|
||
if (filter == "like")
|
||
{
|
||
operand = " like ";
|
||
if (field.typ == "varchar" || field.typ == "memo")
|
||
{
|
||
dbs = "UPPER("+dbs+")";
|
||
safe_val = safe_val.toUpperCase();
|
||
}
|
||
}
|
||
if (filter == "range" && fld.match(/^fclt_d_/))
|
||
{
|
||
dbs = "TO_DATE(" + dbs + ",'dd-mm-yyyy')";
|
||
}
|
||
if (filter == "exact" && (field.typ == "date" || field.typ == "datetime"))
|
||
{
|
||
dbs = "TRUNC("+dbs+")";
|
||
}
|
||
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, fld)
|
||
{
|
||
var field = model.fields[fld];
|
||
return model.aliasprefix + (field.dbs == model.primary ? field.dbs : fld);
|
||
},
|
||
|
||
// Bepaal bij een GET welke velden op te halen
|
||
sqlfields: function _sqlfields(params, model)
|
||
{
|
||
model.aliasprefix = model.aliasprefix || "";
|
||
var selects = [];
|
||
var tables = [ model.tablesql || model.table ];
|
||
var wheres = [];
|
||
|
||
var name_cnt = 0;
|
||
for (var fld in model.fields)
|
||
{
|
||
if (fld.substring(0,1) == "_")
|
||
continue;
|
||
var field = model.fields[fld];
|
||
if (field.hidden)
|
||
continue;
|
||
|
||
var dbs = field.dbs;
|
||
if (field.sql)
|
||
{
|
||
if (model.aliasprefix)
|
||
dbs = model.aliasprefix + fld;
|
||
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 (field.translate)
|
||
dbs = lcl.xsql(dbs, model.table + "." + model.fields.id.dbs);
|
||
if (model.aliasprefix)
|
||
dbs += " AS " + api2.sqlfield_alias(model, fld);
|
||
else
|
||
if (field.translate)
|
||
dbs += " AS " + field.dbs;
|
||
}
|
||
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]];
|
||
// Geneste includes verwijderen, we doen hooguit 1 diep
|
||
if ("model" in inc) // reservablerooms/include/occupations heeft geen model
|
||
delete inc.model.includes;
|
||
|
||
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);
|
||
if (typeof where == "string")
|
||
wheres.push (where);
|
||
else
|
||
{
|
||
tables = tables.concat (where.tables);
|
||
wheres.push (where.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
|
||
// Bouw een fields object zoals save2db dat verwacht/ kent
|
||
update_fields: function _update_fields(params, model, 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 (fld == "id")
|
||
continue;
|
||
if ("sql" in field)
|
||
continue;
|
||
if (!(fld in jsondata) && !field.fnval)
|
||
continue;
|
||
if (field.readonly)
|
||
continue;
|
||
|
||
if (field.fnval)
|
||
var newval = field.fnval(jsondata);
|
||
else // simpel
|
||
var newval = jsondata[fld];
|
||
|
||
if (field.LOV && newval && typeof newval == "object" && "id" in newval)
|
||
{ // dereference
|
||
newval = newval.id;
|
||
}
|
||
|
||
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[fld] = newval;
|
||
}
|
||
break;
|
||
case "date":
|
||
case "datetime":
|
||
if (newval === "")
|
||
newval = null;
|
||
// 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 (newval !== null && 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 + "." + fld + "): " + newval);
|
||
}
|
||
break;
|
||
case "float":
|
||
case "number":
|
||
if (isNaN(newval))
|
||
abort_with_warning("Invalid number (" + model.record_name + "." + fld + "): " + newval);
|
||
break;
|
||
}
|
||
if (field.dbs.indexOf(".") >= 0) // complexe foreign key
|
||
continue;
|
||
|
||
// LET OP: newfield en fields zijn in het formaat wat save2db verwacht/kent
|
||
// dat lijkt wel op een standaard model.fields object maar is soms een fractie anders
|
||
// Zo heeft een save2db field geen "label" en is "track" geen boolean maar de label-tekst
|
||
// LET OP2: save2db werkt eigenlijk met een Array voor fields maar wij hebben een
|
||
// object met "name": { field.. } wat daar wel omgezet wordt.
|
||
var newfield = { dbs: field.dbs,
|
||
typ: field.typ,
|
||
track: field.track && field.label?field.label:field.track,
|
||
foreign: field.foreign,
|
||
val: newval,
|
||
len: field.len
|
||
};
|
||
fields[fld] = 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 = params.include || [];
|
||
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)
|
||
{
|
||
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, oldi);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
//if (inc.func)
|
||
}
|
||
}
|
||
}
|
||
},
|
||
// Geeft de GET terug
|
||
sql2jsonval: function(oRs, fld, model)
|
||
{
|
||
var field = model.fields[fld];
|
||
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, fld) : field.dbs);
|
||
var val = oRs(sqlfieldname).Value;
|
||
}
|
||
else
|
||
var val = oRs(field.dbs.split(".")[1]).Value;
|
||
if (val === null)
|
||
return val;
|
||
if (field.typ == "date")
|
||
val = new Date(val);
|
||
if (field.typ == "datetime")
|
||
val = new Date(val);
|
||
if (field.typ == "time") // In de database ondersteunen we dit niet maar komt voor bij fac_report
|
||
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 (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;
|
||
}
|
||
// if (field.typ == 'check' && !field.LOV)
|
||
// field.LOV = "0;" + L("lcl_No") + ";1;" + L("lcl_Yes");
|
||
|
||
if (field.LOV)
|
||
{
|
||
var spl = api2.splitLOV(field.LOV);
|
||
val = { id: val, name: spl[val] };
|
||
}
|
||
return val;
|
||
},
|
||
filterLOV: function _splitLOV(LOV, filter)
|
||
{
|
||
filter = filter.split(",");
|
||
var spl = api2.splitLOV(LOV);
|
||
var result = [];
|
||
for (var f in filter)
|
||
result.push(filter[f] + ";" + spl[filter[f]]);
|
||
return result.join(";");
|
||
},
|
||
splitLOV: function _splitLOV(LOV)
|
||
{
|
||
var splits = LOV.split(";");
|
||
if (splits % 2)
|
||
abort_with_warning("LOV '{0}' mismatch".format(LOV));
|
||
var result = {};
|
||
for (var i=0; i < splits.length / 2; i++)
|
||
result[splits[i*2]] = splits[i*2 + 1];
|
||
return result;
|
||
},
|
||
splitLOV2sql: function _splitLOV(LOV)
|
||
{
|
||
var spl = api2.splitLOV(LOV);
|
||
var dual = [];
|
||
for (var l in spl)
|
||
dual.push("SELECT " + safe.quoted_sql(l) + ", " + safe.quoted_sql(spl[l]) + " FROM DUAL");
|
||
return dual.join(" UNION ALL ");
|
||
},
|
||
splitLOV2select: function _splitLOV(LOV, current)
|
||
{
|
||
var spl = api2.splitLOV(LOV);
|
||
var options = [];
|
||
for (var l in spl)
|
||
options.push("<option value='" + safe.htmlattr(l) + "'" + (l == current?" selected='1'":"") + ">" + safe.html(spl[l]) + "</option>");
|
||
return "<select>" + options.join("") + "</select>";
|
||
},
|
||
sql2jsonfields: function (oRs, model)
|
||
{
|
||
var record = {};
|
||
for (var fld in model.fields)
|
||
{
|
||
if (fld.substring(0,1) == "_")
|
||
continue;
|
||
var field = model.fields[fld];
|
||
if (field.hidden)
|
||
continue;
|
||
|
||
var val = api2.sql2jsonval(oRs, fld, model);
|
||
//waarom was dit aanwezig?
|
||
//if (field.readonly && !val.id)
|
||
// continue;
|
||
record[fld] = 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 = 0;
|
||
var record = {};
|
||
// Merk op dat onze recordset meer regels kan bevatten dan je zou verwachten
|
||
// omdat de includes er bij zijn gejoind
|
||
var total_count = 0;
|
||
var limit = S("qp_maxrows"); // TODO: maximaliseren op qp_maxrows2?
|
||
if ("limit" in params.filter)
|
||
limit = params.filter.limit;
|
||
if (params.filter.nolimit || limit == -1)
|
||
limit = 99999999; // Excel/CSV alles tonen
|
||
while (!oRs.Eof)
|
||
{
|
||
if (data.length >= limit) // Alleen nog maar tellen
|
||
{
|
||
if (model.primary)
|
||
{
|
||
var key = oRs(model.primary).Value; // Oppassen met de includes
|
||
if (key != lastkey)
|
||
total_count ++;
|
||
lastkey = key;
|
||
}
|
||
else
|
||
{
|
||
total_count ++;
|
||
}
|
||
oRs.MoveNext();
|
||
continue;
|
||
}
|
||
if (model.primary)
|
||
{
|
||
var key = oRs(model.primary).Value;
|
||
if (key != lastkey)
|
||
{
|
||
if (key < lastkey && params.include && params.include.length)
|
||
{
|
||
abort_with_warning("Include '{0}' only allowed when main record is ordered by '{1}'".format(params.include[0], model.primary));
|
||
}
|
||
|
||
if (lastkey && "id" in record) // Record was er mogelijk uit gefilterd
|
||
{
|
||
data.push(record);
|
||
total_count ++;
|
||
if (data.length == limit)
|
||
continue; // alleen nog tellen, 'volgende' record niet meer opbouwen
|
||
}
|
||
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)
|
||
{
|
||
if (fld.substring(0,1) == "_")
|
||
continue;
|
||
var field = model.fields[fld];
|
||
if (field.hidden)
|
||
continue;
|
||
|
||
var val = api2.sql2jsonval(oRs, fld, model)
|
||
//waarom was dit aanwezig?
|
||
//if (field.readonly && !val.id)
|
||
// continue;
|
||
record[fld] = 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;
|
||
|
||
var inc_record = api2.sql2jsonfields(oRs, incmodel);
|
||
var inc_record_exists = false;
|
||
for (var i=0; i<record[incname].length; i++)
|
||
{
|
||
var tmp_record = record[incname];
|
||
var cur_record = tmp_record[i];
|
||
if (JSON.stringify(cur_record) == JSON.stringify(inc_record))
|
||
inc_record_exists = true;
|
||
}
|
||
if (!inc_record_exists)
|
||
record[incname].push(inc_record);
|
||
//else
|
||
// __Log("Deze bestaat al");
|
||
}
|
||
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;
|
||
if (!model.primary)
|
||
{
|
||
data.push(record);
|
||
record = {};
|
||
total_count ++;
|
||
}
|
||
oRs.MoveNext();
|
||
}
|
||
if (lastkey && data.length < limit)
|
||
{
|
||
total_count ++;
|
||
data.push(record);
|
||
}
|
||
model.limit = limit;
|
||
//model.offset = offset;
|
||
model.total_count = total_count;
|
||
return data;
|
||
},
|
||
// Data is een array met 'records'
|
||
deliver: function _deliver(data, model, format, single )
|
||
{
|
||
if (single && !data.length)
|
||
{
|
||
Response.Status = "404 Not Found";
|
||
Response.End;
|
||
}
|
||
|
||
if (format == "html" || format == "json")
|
||
{
|
||
var result = { };
|
||
if (single)
|
||
result[model.record_name] = data[0];
|
||
else
|
||
{
|
||
result.total_count = model.total_count;
|
||
result.limit = model.limit;
|
||
result.offset = 0;
|
||
result[model.records_name] = data;
|
||
}
|
||
|
||
var resultdata = api2.plugin.transform_outgoing({}, result);
|
||
}
|
||
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 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 antwoord = JSON.stringify(resultdata, 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, model, 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, model, single)
|
||
{
|
||
var rootname = model.records_name;
|
||
var record_name = model.record_name;
|
||
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)
|
||
{
|
||
var inc_record_name;
|
||
for (var ii in model.includes)
|
||
{
|
||
if (model.includes[ii].model.records_name == fld)
|
||
inc_record_name = model.includes[ii].model.record_name;
|
||
}
|
||
for (var i = 0; i < record[fld].length; i++)
|
||
elementField.appendChild(record2json(record[fld][i], inc_record_name));
|
||
}
|
||
else if (record[fld] && typeof record[fld] == "object") // Veronderstelt dat dit foreign met name/key is
|
||
{
|
||
if ("name" in record[fld] && "id" in record[fld])
|
||
{
|
||
elementField.setAttribute("name", fld);
|
||
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;
|
||
}
|
||
},
|
||
find_fieldindex_by_dbsname: function(array, value)
|
||
{
|
||
for(var i = 0; i < array.length; i++)
|
||
{
|
||
if(array[i].hasOwnProperty("dbs") && array[i]["dbs"] === value)
|
||
{
|
||
return i;
|
||
}
|
||
}
|
||
return -1;
|
||
},
|
||
// Pas op: fields is hier alleen de fields die bijgewerkt moeten worden
|
||
field_alter: function(fields, fld, newval, model)
|
||
{
|
||
if (fld in fields)
|
||
{
|
||
fields[fld].val = newval;
|
||
}
|
||
else
|
||
{
|
||
// Als veld niet aanwezig is, dan toevoegen.
|
||
// Het typ halen we uit het api-model.
|
||
var newfield = {};
|
||
newfield.dbs = model[fld].dbs;
|
||
newfield.typ = model[fld].typ;
|
||
if (model[fld].track)
|
||
newfield.fld = model[fld].label||model[fld].track;
|
||
newfield.val = newval;
|
||
__Log(newfield);
|
||
fields[fld] = newfield;
|
||
}
|
||
},
|
||
get_jdata_refkey: function(jsondataobject)
|
||
{
|
||
var value = null;
|
||
if (jsondataobject)
|
||
{
|
||
if (jsondataobject.id && typeof jsondataobject.id != "object")
|
||
value = jsondataobject.id;
|
||
else
|
||
value = jsondataobject;
|
||
}
|
||
return value;
|
||
},
|
||
toDate: function (dt)
|
||
{
|
||
if (dt instanceof Date)
|
||
return dt;
|
||
// 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 (dt !== null && typeof dt == "object")
|
||
dt = new Date(dt);
|
||
if (typeof dt == 'string')
|
||
{
|
||
if (dt.match(/^[0-9]+$/))
|
||
return new Date(parseInt(dt, 10));
|
||
var dt = myJSON.internal_parsedate(null, dt);
|
||
if (dt && dt instanceof Date)
|
||
return dt;
|
||
}
|
||
NOT_A_DATE;
|
||
},
|
||
generic_REST: function(model)
|
||
{
|
||
if (!model.autfunction)
|
||
GENERIC_ONLY_WITH_AUTFUNCTION;
|
||
|
||
model.REST_GET = generic_REST_GET(model);
|
||
model.REST_POST = generic_REST_POST(model);
|
||
model.REST_PUT = generic_REST_PUT(model);
|
||
model.REST_DELETE = generic_REST_DELETE(model);
|
||
}
|
||
}
|
||
|
||
// 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 generic_REST_GET(model)
|
||
{
|
||
user.checkAutorisation(model.autfunction); // Als je zelfs geen readrechten hebt ben je heel fout bezig
|
||
|
||
return function _generic_REST_GET(params, jsondata)
|
||
{
|
||
var query = api2.sqlfields(params, model);
|
||
|
||
var wheres = api2.sqlfilter(params, model);
|
||
query.wheres = query.wheres.concat(wheres);
|
||
|
||
if (model.soft_delete && params.filter.show_deleted != "on")
|
||
query.wheres.push(model.soft_delete + " IS NULL");
|
||
|
||
var sql = "SELECT " + query.selects.join(", ")
|
||
+ " FROM " + query.tables.join(", ")
|
||
+ (query.wheres.length ? " WHERE " + query.wheres.join(" AND " ) : "")
|
||
+ " ORDER BY " + (model.fields.name?model.fields.name.dbs:model.fields.id.dbs);
|
||
|
||
var maxcnt = params.filter.showall==1?S("qp_maxrows2"):S("qp_maxrows");
|
||
var sql = "SELECT * FROM (" + sql + ") WHERE ROWNUM <= " + (maxcnt + 1); // Eentje extra om overflow-melding te triggeren in resultsettable
|
||
|
||
var json = api2.sql2json (params, sql, model);
|
||
return json;
|
||
}
|
||
}
|
||
|
||
function generic_REST_POST(model)
|
||
{
|
||
var autparams = user.checkAutorisation(model.autfunction, true);
|
||
if (!autparams || autparams.PRSwritelevel == 9 || autparams.ALGwritelevel == 9)
|
||
return false;
|
||
|
||
if (!model.fields.id.seq) // We willen per se een sequence
|
||
return false;
|
||
|
||
return function _generic_REST_POST(params, jsondata, the_key)
|
||
{
|
||
var fields = api2.update_fields(params, model, jsondata);
|
||
|
||
fields["id"] = model.fields["id"]; // Die zal een seq hebben
|
||
var xxxIns = buildInsert(model.table, fields, { noValidateToken: true });
|
||
var the_key = xxxIns.sequences[model.fields.id.dbs];
|
||
// TODO: Generieke tracking?
|
||
// TODO: de includes?
|
||
|
||
var err = Oracle.Execute(xxxIns.sql, true);
|
||
if (err.friendlyMsg)
|
||
abort_with_warning(err.friendlyMsg);
|
||
|
||
return { key: the_key, warning: "" };
|
||
}
|
||
}
|
||
|
||
function generic_REST_PUT(model)
|
||
{
|
||
var autparams = user.checkAutorisation(model.autfunction, true);
|
||
if (!autparams || autparams.PRSwritelevel == 9 || autparams.ALGwritelevel == 9)
|
||
return false;
|
||
|
||
return function _generic_REST_PUT(params, jsondata, the_key)
|
||
{
|
||
var result = {};
|
||
var fields = api2.update_fields(params, model, jsondata);
|
||
var wheres = [model.fields.id.dbs + " = " + the_key];
|
||
var xxxUpd = buildTrackingUpdate(model.table, wheres.join(" AND " ), fields, { noValidateToken: true });
|
||
// TODO: Generieke tracking?
|
||
// TODO: de includes?
|
||
|
||
var err = Oracle.Execute(xxxUpd.sql, true);
|
||
if (err.friendlyMsg)
|
||
abort_with_warning(err.friendlyMsg);
|
||
|
||
return { key: the_key, warning: "" };
|
||
}
|
||
}
|
||
|
||
function generic_REST_DELETE(model)
|
||
{
|
||
var autparams = user.checkAutorisation(model.autfunction, true);
|
||
if (!autparams || autparams.PRSwritelevel == 9 || autparams.ALGwritelevel == 9)
|
||
return false;
|
||
|
||
return function _generic_REST_DELETE(params, the_key)
|
||
{
|
||
if (model.soft_delete)
|
||
{
|
||
var sql = "UPDATE " + model.table
|
||
+ " SET " + model.soft_delete + " = SYSDATE"
|
||
+ " WHERE " + model.fields.id.dbs + " = " + the_key
|
||
+ " AND " + model.soft_delete + " IS NULL";
|
||
}
|
||
else
|
||
{
|
||
var sql = "DELETE " + model.table
|
||
+ " WHERE " + model.fields.id.dbs + " = " + the_key;
|
||
}
|
||
|
||
var err = Oracle.Execute(sql, true);
|
||
if (err.friendlyMsg)
|
||
abort_with_warning(err.friendlyMsg);
|
||
|
||
return { key: the_key, warning: "" };
|
||
}
|
||
}
|
||
|
||
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");
|
||
}
|
||
|
||
// Backwards compatible FACMGT_LCL
|
||
var __F = [];
|
||
function F(tekst)
|
||
{
|
||
if (!__F.length)
|
||
{
|
||
// TODO: Inlezen uit FAC_LOCALE_MGT, zoek op vbLCL
|
||
}
|
||
return tekst;
|
||
}
|
||
%> |