998 lines
42 KiB
PHP
998 lines
42 KiB
PHP
<% /*
|
||
$Revision$
|
||
$Id$
|
||
|
||
File: api2_rest.inc
|
||
Description: Functies voor de http-REST interface op de modellen
|
||
Notes: Hier wordt van alles met de 'buitenwereld' gecommuniceerd.
|
||
Het manipuleren met behulp van modellen gebeurt in api2.inc
|
||
Status:
|
||
|
||
*/
|
||
%>
|
||
<!-- #include file="api2.inc" -->
|
||
<!-- #include file="api2_swagger.inc" -->
|
||
<!-- #include file="../scf/scaffolding_common.inc" -->
|
||
<%
|
||
var DEZE = this;
|
||
api2_rest = {
|
||
authenticate: function _authenticate()
|
||
{
|
||
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
|
||
var method = String(Request.ServerVariables("REQUEST_METHOD"));
|
||
if (method != "GET") // Vereis dan wel het CSRF token
|
||
{
|
||
var token = Request.ServerVariables("HTTP_X_CSRF_TOKEN").Count // Meegegeven als X-CSRF-TOKEN
|
||
? String(Request.ServerVariables("HTTP_X_CSRF_TOKEN"))
|
||
: "";
|
||
protectRequest.validateToken(token);
|
||
}
|
||
}
|
||
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, prs_perslid_oslogin"
|
||
+ " 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";
|
||
// Sommige applicaties kunnen in reactie hierop een b64 encoded username:password sturen
|
||
// Die onderscheppen wij in LoginTry.asp uiteindelijk
|
||
if (S("basic_auth_realm"))
|
||
Response.AddHeader("WWW-Authenticate", "Basic realm=\"" + S("basic_auth_realm") + "\"");
|
||
Response.End;
|
||
};
|
||
__Log("API2 User is: {0} ({1})".format(oRs("prs_perslid_naam").Value, oRs("prs_perslid_oslogin").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();
|
||
}
|
||
|
||
// APP? Die kan meta-rechten hebben (bijvoorbeeld auth-token opvragen van gebruiker)
|
||
var APPKEY;
|
||
if (S("fac_api_key_in_url"))
|
||
APPKEY = getQParam("HTTP_X_FACILITOR_APP_KEY", "");
|
||
if (!APPKEY && Request.ServerVariables("HTTP_X_FACILITOR_APP_KEY").Count)
|
||
APPKEY = String(Request.ServerVariables("HTTP_X_FACILITOR_APP_KEY")); // Meegegeven als X-FACILITOR-APP-Key
|
||
if (APPKEY)
|
||
{
|
||
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(APPKEY);
|
||
var oRs = Oracle.Execute(sql);
|
||
if (oRs.Eof)
|
||
{
|
||
__DoLog("Unauthorized app");
|
||
Response.Status = "401 Unauthorized";
|
||
Response.End;
|
||
};
|
||
__Log("APP User is: " + oRs("prs_perslid_naam").Value);
|
||
/* global */ app_user_key = oRs("prs_perslid_key").Value;
|
||
/* global */ app_user = new Perslid(app_user_key);
|
||
oRs.Close()
|
||
}
|
||
|
||
/* global */ user = new Perslid(user_key); // wordt mogelijk nog overruled door imporsonate
|
||
CheckForLogging(Request.QueryString("logging")); // Nu pas kan autorisatie via user gecontroleerd worden
|
||
},
|
||
|
||
impersonate: function _impersonate(model)
|
||
{
|
||
// Impersonate? (anno jan-2016 in de praktijk nergens gebruikt, kan mogelijk vervallen)
|
||
if (!S("fac_api_allow_impersonate") || !model.impersonate_auth)
|
||
return;
|
||
|
||
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)
|
||
return;
|
||
|
||
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();
|
||
|
||
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;
|
||
/* global */ user = new Perslid(user_key);
|
||
}
|
||
else
|
||
{
|
||
Response.Status = "412 Unauthorized X-Facilitor-Switch-User header";
|
||
Response.End;
|
||
}
|
||
},
|
||
process: function _process(model)
|
||
{
|
||
var wasCodePage = Session.Codepage;
|
||
Session.Codepage = 65001; // We doen *uitsluitend* utf-8
|
||
Response.Charset = 'utf-8';
|
||
var inputformat = outputformat = getQParamSafe("format", "invalid").toLowerCase();
|
||
if (outputformat == "auto")
|
||
{
|
||
var accept = String(Request.ServerVariables("HTTP_ACCEPT")).split(",")[0]; // Altijd alleen eerste bekijken
|
||
switch (accept.toLowerCase())
|
||
{
|
||
case "application/xml": outputformat = "xml"; break;
|
||
case "application/json": outputformat = "json"; break;
|
||
case "text/html": outputformat = "html"; break; // vanuit browser?
|
||
default: outputformat = "json";
|
||
}
|
||
}
|
||
|
||
if (outputformat == "json")
|
||
/* global */ JSON_Result = true; // Zelf doen we er niets mee maar
|
||
// shared.simple_page kijkt er naar
|
||
|
||
api2_rest.authenticate();
|
||
// Kip-ei: de omzetting naar new model() mag pas als je geauthenticeerd bent
|
||
// Hierboven willen we het echter al wel meegeven
|
||
if (typeof model == "function") // Nieuwe stijl is het een function. Even compatible.
|
||
model = new model();
|
||
|
||
api2_rest.impersonate(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 = shared.qs2json(model);
|
||
filter = api2_rest.plugin.transform_filter(filter);
|
||
var requestparams = { filter: filter, include: getQParamArray("include", []) };
|
||
|
||
if (/PUT|POST/.test(method)) // Dan is er in de body data meegestuurd
|
||
{
|
||
if (filter.mode == "attachment" && "custom_fields" in model.includes)
|
||
{
|
||
// Body bevat application/octet-stream, dat lezen we later wel uit
|
||
}
|
||
else
|
||
{
|
||
if (inputformat == "auto")
|
||
{
|
||
var contenttype = String(Request.ServerVariables("HTTP_CONTENT_TYPE")).split(",")[0]; // Altijd alleen eerste bekijken
|
||
switch (contenttype.toLowerCase())
|
||
{
|
||
case "application/xml":
|
||
inputformat = "xml";
|
||
break;
|
||
case "application/json":
|
||
inputformat = "json";
|
||
break;
|
||
case "application/x-www-form-urlencoded":
|
||
inputformat = "form";
|
||
break;
|
||
default:
|
||
inputformat = outputformat;
|
||
}
|
||
}
|
||
|
||
switch (inputformat)
|
||
{
|
||
case "json":
|
||
{
|
||
var parsed = RequestJSON();
|
||
if (parsed.error)
|
||
api2.error(400, "Error parsing input JSON: " + parsed.error);
|
||
jsondata = api2_rest.plugin.transform_incoming(requestparams, parsed.json);
|
||
if (!jsondata)
|
||
api2.error(400, "Error parsing input JSON: Empty");
|
||
break;
|
||
}
|
||
case "xml":
|
||
{
|
||
var parsed = RequestXML();
|
||
if (parsed.error)
|
||
api2.error(400, "Error parsing input XML: " + parsed.error);
|
||
jsondata = api2_rest.xml2json(parsed.xml);
|
||
if (!jsondata)
|
||
api2.error(400, "Error parsing input XML: Empty");
|
||
break;
|
||
}
|
||
//case "form":
|
||
//{
|
||
// jsondata = { };
|
||
// jsondata[model.recordname] = shared.form2json(model); // of api2.form2JSONdata inzetten?
|
||
//}
|
||
default:
|
||
UNKNOWN_CONTENT_TYPE;
|
||
}
|
||
if (!jsondata || !(model.record_name in jsondata || (model.multi_update && model.records_name in jsondata)))
|
||
{
|
||
api2.error(400, "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 (outputformat == "doc")
|
||
{
|
||
// Dan hoeven we verder bijna niets te doen
|
||
if ("autfunction" in model)
|
||
model.autfunctionname = L("lcl_" + model.autfunction);
|
||
// TODO: velden strippen waar je niets mee te maken hebt?
|
||
}
|
||
else if (outputformat == "api" && getQParamInt("swagger", 0) == 0)
|
||
{
|
||
// TODO: Onderstaande in een of ander standaardformaat opleveren?
|
||
var result = { id: model.records_name,
|
||
"name": model.record_title,
|
||
"names": model.records_title,
|
||
"records_name": model.records_name,
|
||
"record_name": model.record_name,
|
||
"authorization": model.autfunction,
|
||
methods: [],
|
||
includes: [],
|
||
fields: []
|
||
};
|
||
for (var i in model.includes)
|
||
result.includes.push(i);
|
||
if (model["REST_GET"])
|
||
result.methods.push("GET");
|
||
if (model["REST_PUT"])
|
||
result.methods.push("PUT");
|
||
if (model["REST_POST"])
|
||
result.methods.push("POST");
|
||
if (model["REST_DELETE"])
|
||
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,
|
||
required: model.fields[fld].required,
|
||
valuelist: model.fields[fld].LOV && api2.splitLOV(model.fields[fld].LOV)
|
||
});
|
||
}
|
||
}
|
||
else if (outputformat == "api" && getQParamInt("swagger", 0) == 1)
|
||
{
|
||
result = swaggermodel(model);
|
||
}
|
||
else
|
||
{
|
||
if (method == "DELETE")
|
||
{
|
||
var result = model["REST_" + method]( requestparams, key );
|
||
}
|
||
else if (filter.mode == "attachment" && "custom_fields" in model.includes)
|
||
{ // GET/PUT/POST https://xxxx.facilitor.nl/trunk/api2/visitors/99685/attachments/1040/testfile.jpg"
|
||
if (wasCodePage != 65001)
|
||
{
|
||
// Door de IIS rewriter is de filenaam in de url utf-8 encoded
|
||
// Zet dat hier terug om naar Windows-1252
|
||
var fileStream = new ActiveXObject("ADODB.Stream");
|
||
fileStream.Open();
|
||
fileStream.Type = 2; // adTypeText
|
||
fileStream.Charset = 'Windows-1252';
|
||
fileStream.WriteText(filter.filename);
|
||
fileStream.Position = 0;
|
||
fileStream.Charset = 'utf-8';
|
||
filter.filename = fileStream.ReadText();
|
||
fileStream.Close();
|
||
}
|
||
|
||
requestparams.filter.id = key; // Die kan er maar beter wel zijn!
|
||
requestparams.include = ["custom_fields"];
|
||
var jsondata = { "custom_fields": [
|
||
{
|
||
"propertyid": parseInt(filter.subfolder, 10),
|
||
"value": filter.filename, // Gaat de database in bij "F"
|
||
"attachments": [
|
||
{
|
||
"name": filter.filename // Zo gaat hij heten op het filesysteem
|
||
}
|
||
]
|
||
}
|
||
]
|
||
};
|
||
if (method == "PUT" || method == "POST")
|
||
{
|
||
var bytes = Request.TotalBytes;
|
||
if (bytes == 0)
|
||
{ // TODO
|
||
//__DoLog("api_gen_import empty body posted", "#ffff00");
|
||
//Response.Write("Error: no data posted for API import");
|
||
//Response.End; // Grof maar anders AiAi, dat is nog erger
|
||
}
|
||
var fileStream = Server.CreateObject("ADODB.Stream");
|
||
fileStream.Type = 1; // adTypeBinary eerst nog
|
||
fileStream.Open();
|
||
fileStream.Write(Request.BinaryRead(bytes));
|
||
jsondata["custom_fields"][0]["attachments"][0].datastream = fileStream;
|
||
}
|
||
var data = model["REST_" + method](requestparams, jsondata, key);
|
||
if (method == "GET")
|
||
{
|
||
if (!data.length) // mogelijk not authorized op hele record
|
||
Response.Status = "404 Not Found";
|
||
else
|
||
model.includes["custom_fields"].model.streamattachment(filter, data[0].custom_fields);
|
||
Response.End;
|
||
}
|
||
else
|
||
result = data;
|
||
}
|
||
else if (method == "GET")
|
||
{
|
||
var result = model["REST_" + method]( requestparams, null, key );
|
||
}
|
||
else if (model.record_name in jsondata) // een enkel record
|
||
{
|
||
if (jsondata[model.record_name] instanceof Array)
|
||
api2.error(400, "{0} should be single record only.".format(method))
|
||
|
||
var result = model["REST_" + method]( requestparams, jsondata[model.record_name], 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 = jsondata[model.records_name][record];
|
||
var result = model["REST_" + method]( requestparams, thisdata, thisdata.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 > 0)
|
||
{
|
||
var params = { filter: shared.qs2json(model), 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_rest.deliver(data, model, outputformat, isSingle);
|
||
|
||
},
|
||
// 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" || format == "table")
|
||
{
|
||
var result = { };
|
||
if (model.formatted_get)
|
||
result = data;
|
||
else 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_rest.plugin.transform_outgoing({}, result);
|
||
}
|
||
switch (format)
|
||
{
|
||
case "api":
|
||
{
|
||
//var xml_antwoord = api2_rest.json2xml(data, model, single);
|
||
var str_antwoord = JSON.stringify(data, null, getQParam("pretty","0")=="1"?2:0);
|
||
Response.ContentType = "application/json";
|
||
break;
|
||
}
|
||
case "doc":
|
||
{
|
||
if (model.fields) // Ga de hints er bij zoeken
|
||
{
|
||
var safefieldnames = [];
|
||
var lcl2fld = {};
|
||
for (var fld in model.fields)
|
||
{
|
||
if (fld.substring(0,1) == "_")
|
||
{
|
||
delete model.fields[fld];
|
||
continue;
|
||
}
|
||
|
||
for (var prop in model.fields[fld])
|
||
{
|
||
if (typeof model.fields[fld][prop] == "function") // Bijvoorbeeld model_issues.filter
|
||
model.fields[fld][prop] = "<<function>>";
|
||
}
|
||
|
||
if (model.fields[fld].LOV)
|
||
model.fields[fld].valuelist = api2.splitLOV(model.fields[fld].LOV, "lov_");
|
||
|
||
var lclname = "{0}.{1}.hint".format(model.records_name, fld);
|
||
lcl2fld[lclname] = fld;
|
||
safefieldnames.push(safe.quoted_sql(lclname));
|
||
}
|
||
if (safefieldnames.length)
|
||
{
|
||
//model.fields[fld].hint = "Hallo";
|
||
var sql = "SELECT fac_locale_xsl_label, "
|
||
+ " COALESCE(fac_locale_xsl_cust, fac_locale_xsl_tekst) fac_locale_xsl_tekst"
|
||
+ " FROM fac_locale_xsl xsl"
|
||
+ " WHERE fac_locale_xsl_lang = " + safe.quoted_sql(user_lang)
|
||
+ " AND fac_locale_xsl_module = 'ASP'"
|
||
+ " AND fac_locale_xsl_label IN (" + safefieldnames.join(", ") + ")";
|
||
var oRs = Oracle.Execute(sql);
|
||
while (!oRs.Eof)
|
||
{
|
||
model.fields(lcl2fld[oRs("fac_locale_xsl_label").Value]) = oRs("fac_locale_xsl_tekst").value;
|
||
oRs.MoveNext();
|
||
}
|
||
oRs.Close();
|
||
}
|
||
}
|
||
Response.ContentType = "text/html";
|
||
var str_antwoord = simple_json2xml(model, "api");
|
||
var xml_antwoord = new ActiveXObject("MSXML2.DOMDocument.6.0");
|
||
xml_antwoord.loadXML(str_antwoord);
|
||
if (xml_antwoord.parseError.errorCode)
|
||
{
|
||
abort_with_warning("XSL error: " + xml_antwoord.parseError.reason + " @ " + xml_antwoord.parseError.line + "." + xml_antwoord.parseError.linepos + "\n"+ xml_antwoord.parseError.srcText);
|
||
}
|
||
|
||
var style = new ActiveXObject("MSXML2.DOMDocument.6.0");
|
||
style.async = false;
|
||
style.resolveExternals = true; // XSL kan includes hebben
|
||
style.validateOnParse = true; // en moet correct zijn
|
||
if (Request.QueryString("debug").Count == 0)
|
||
{
|
||
var xslname = model.xslname || "reference.xsl";
|
||
style.load(Server.MapPath(rooturl + "/appl/api2/" + xslname)); // De stylesheet laden. API's redeneren vanuit de root
|
||
var str_antwoord = xml_antwoord.transformNode(style); // terugstoppen in antwoord
|
||
}
|
||
else
|
||
{
|
||
// Het kan zijn dat de stylesheet bepaalde informatie verbergt.
|
||
// Daarom niet zo maar aanbieden
|
||
if (Application("otap_environment") != "O")
|
||
ONLY_ON_OTAP_O;
|
||
|
||
style.load(Server.MapPath(rooturl + "/appl/shared/indent.xsl")); // De stylesheet laden. API's redeneren vanuit de root
|
||
var str_antwoord = "<pre>" + Server.HTMLEncode(xml_antwoord.transformNode(style)) + "</pre>";
|
||
}
|
||
if (style.parseError.errorCode)
|
||
{
|
||
abort_with_warning("XSL error: " + style.parseError.reason + " @ " + style.parseError.line + "." + style.parseError.linepos );
|
||
}
|
||
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>"
|
||
+ "<meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'>"
|
||
+ "<meta name='viewport' content='width=device-width, initial-scale=1.0, user-scalable=yes'>"
|
||
+ "<title>{0}</title>".format(single?model.record_name:model.records_name)
|
||
+ "</head>"
|
||
+ "<body><pre>"
|
||
+ Server.HTMLEncode(antwoord)
|
||
+ "</pre></body></html>";
|
||
break;
|
||
case "table":
|
||
Response.ContentType = "text/html";
|
||
if (single)
|
||
var antwoord = api2_rest.json2htmltable([resultdata[model.record_name]], model, single);
|
||
else
|
||
var antwoord = api2_rest.json2htmltable(resultdata[model.records_name], model, single);
|
||
var str_antwoord = "<!DOCTYPE html>"
|
||
+ "<html>"
|
||
+ "<head>"
|
||
+ " <title>{0}</title>".format(single?model.record_name:model.records_name)
|
||
+ " <link rel=stylesheet type='text/css' href='" + rooturl + "/appl/api2/table.css'>"
|
||
+ "</head>"
|
||
+ "<body>"
|
||
+ antwoord
|
||
+ "</body>"
|
||
+ "</html>";
|
||
break;
|
||
case "xml":
|
||
Response.ContentType = "text/xml";
|
||
var xml_antwoord = api2_rest.json2xml(data, model, single);
|
||
// TODO: Output XSL transform ondersteunen?
|
||
var xsl = getQParamSafe("xsl", "");
|
||
var xslfile;
|
||
if (xsl)
|
||
{
|
||
var fso = new ActiveXObject("Scripting.FileSystemObject");
|
||
xslfile = Server.MapPath(custpath + "/xsl/" + xsl + ".xsl");
|
||
if (!fso.FileExists(xslfile))
|
||
abort_with_warning("Stylesheet '{0}' not found".format(xsl));
|
||
}
|
||
else if (getQParam("pretty","0")=="1")
|
||
xslfile = Server.MapPath(rooturl + "/appl/shared/indent.xsl");
|
||
if (xslfile)
|
||
{
|
||
var style = new ActiveXObject("MSXML2.DOMDocument.6.0");
|
||
style.async = false;
|
||
style.resolveExternals = false;
|
||
style.load(xslfile);
|
||
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_rest.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;
|
||
}
|
||
|
||
Response.write(str_antwoord);
|
||
},
|
||
|
||
json2htmltable: function _json2htmltable(data, model, single)
|
||
{
|
||
var trs = [];
|
||
if (data.length) // Header maken
|
||
{
|
||
var tds = [];
|
||
for (var fld in data[0])
|
||
{
|
||
var lbl = fld;
|
||
if (fld in model.fields)
|
||
lbl = model.fields[fld].label;
|
||
else
|
||
{
|
||
var val = data[0][fld];
|
||
if (val && typeof val == "object")
|
||
{
|
||
if (model.includes && fld in model.includes)
|
||
var lbl = model.includes[fld].model.records_title;
|
||
else
|
||
lbl = fld; // Attachments/flexfiles
|
||
}
|
||
}
|
||
tds.push("<th title='{0}'>{1}</th>".format(safe.htmlattr(lbl), fld));
|
||
}
|
||
trs.push(tds.join(""));
|
||
}
|
||
for (var i = 0; i < data.length; i++)
|
||
{
|
||
var rec = data[i];
|
||
var tds = [];
|
||
for (var fld in rec)
|
||
{
|
||
var val = rec[fld];
|
||
var safeval = Server.HTMLEncode(String(val));
|
||
if (val === null)
|
||
safeval = ' ';
|
||
else if (typeof val == 'object' && val instanceof Date)
|
||
safeval = toISODateTimeString(val);
|
||
else if (val && typeof val == "object" && "id" in val)
|
||
{
|
||
var naam = val.name||"???";
|
||
if (typeof naam == 'object' && naam instanceof Date) // Bij appointment
|
||
naam = toISODateTimeString(naam);
|
||
safeval = val.id + " (" + Server.HTMLEncode(naam) + ")";
|
||
}
|
||
else if (val && typeof val == "object" && model.includes && fld in model.includes)
|
||
safeval = api2_rest.json2htmltable(val, model.includes[fld].model, single); // dereference
|
||
else if (val instanceof Array) // attachments array
|
||
safeval = api2_rest.json2htmltable(val, model, single);
|
||
|
||
tds.push(safeval);
|
||
}
|
||
trs.push("<td>" + tds.join("</td><td>") + "</td>");
|
||
}
|
||
return "\n<table border='1'>\n<tr>" + trs.join("</tr>\n<tr>") + "</tr></table>";
|
||
},
|
||
|
||
// 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;
|
||
if (ii == fld)
|
||
inc_record_name = model.includes[ii].model.record_name;
|
||
}
|
||
// Fallback voor attachments array --> "attachment"
|
||
if (!inc_record_name)
|
||
inc_record_name = fld.substr(0, fld.length-1);
|
||
|
||
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", record[fld].name===null?"":record[fld].name);
|
||
elementField.setAttribute("id", record[fld].id);
|
||
}
|
||
else
|
||
elementField = record2json(record[fld], fld);
|
||
}
|
||
else
|
||
{
|
||
var elementFieldText = xmlDoc.createTextNode(String(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);
|
||
arrayElement.setAttribute("total_count", model.total_count);
|
||
arrayElement.setAttribute("limit", model.limit);
|
||
arrayElement.setAttribute("offset", 0);
|
||
|
||
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_rest.xml2json(item);
|
||
}
|
||
else
|
||
{
|
||
if (typeof(obj[nodeName].push) == "undefined")
|
||
{
|
||
var old = obj[nodeName];
|
||
obj[nodeName] = [];
|
||
obj[nodeName].push(old);
|
||
}
|
||
obj[nodeName].push(api2_rest.xml2json(item));
|
||
}
|
||
}
|
||
}
|
||
return obj;
|
||
},
|
||
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));
|
||
}
|
||
// Via DEZE kan de aanroeper eigenlijk alle globale functies benaderen
|
||
// zoals __Log
|
||
hook.initialize({ S: S, Oracle: Oracle, customerId: customerId, safe: safe, DEZE: DEZE });
|
||
return hook;
|
||
}
|
||
}
|
||
api2.error(500, "Undefined plugin {0}".format(plugin_name));
|
||
},
|
||
plugin: {
|
||
transform_filter: function(filter)
|
||
{
|
||
var outdata = filter;
|
||
var hook = api2_rest.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_rest.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_rest.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)
|
||
{
|
||
result = { error: e.description || (e.message + ": " + e.name) };
|
||
__DoLog("JSON eval faalt met: '{0}'\n{1}".format(result, jvraag), "ffff00");
|
||
return result ;
|
||
}
|
||
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");
|
||
}
|
||
|
||
var simple_json2xml = (function (undefined) {
|
||
"use strict";
|
||
var tag = function (name, closing) {
|
||
return (closing ? "</" : "\n<") + name + ">";
|
||
};
|
||
return function (obj, rootname) {
|
||
var xml = "";
|
||
for (var i in obj) {
|
||
if (obj.hasOwnProperty(i)) {
|
||
var value = obj[i],
|
||
type = typeof value;
|
||
if (value === obj) // bij merged_model zouden we hier oneindige recursie krijgen
|
||
continue;
|
||
if (value instanceof Array && type == 'object') {
|
||
for (var sub in value) {
|
||
xml += simple_json2xml(value[sub]);
|
||
}
|
||
} else if (value instanceof Object && type == 'object') {
|
||
xml += tag(i) + simple_json2xml(value) + tag(i, 1);
|
||
} else {
|
||
xml += tag(i) + (value===null?"":Server.HTMLEncode(value)) + tag(i, 1);
|
||
}
|
||
}
|
||
}
|
||
|
||
return rootname ? tag(rootname) + xml + tag(rootname, 1) : xml;
|
||
};
|
||
})(simple_json2xml || {});
|
||
%> |