Files
Facilitor/UTILS/Exchange/ms_graph.js
Koen Reefman 1be764b6b5 Merge 2023.3 Gold B changes
svn path=/Website/trunk/; revision=63306
2024-01-23 12:34:53 +00:00

1175 lines
51 KiB
JavaScript

/*
$Revision$
$Id$
File: ms_Graph.js
Description: MS Graph koppeling hulpfuncties
*/
/* GLOBALS */
var fso = new ActiveXObject("Scripting.FileSystemObject");
var oCrypto = new ActiveXObject("SLNKDWF.Crypto");
var S_msgraph_sync_level;
var FCLT_KEY_PROP_ID_NAME = "String {00020329-0000-0000-c000-000000000046} Name fclt.res_rsv_ruimte.key";
var REANIMATE_THRESHOLD_MS = 30000; // Niet reanimeren als langer dan 30s geleden verwijderd
/*////////////////////////////////
// //
// Bearer token acquisition //
// //
////////////////////////////////*/
function requestToken(config)
{
setMSGraphSyncLevel();
return requestTokenCC(config);
}
// Request bearer token using [C]lient [C]redentials
function requestTokenCC(config)
{
if (config.certificate)
return requestTokenCertificate(config);
else
{
var data = 'grant_type=client_credentials'
+ '&client_secret=' + config.client_secret
+ '&client_id=' + config.client_id
+ '&scope=' + 'https://graph.microsoft.com/.default';
var request = {
"method": "POST",
"url": "https://login.microsoftonline.com/" + config.tenant + "/oauth2/v2.0/token",
"body": data,
"headers": { "Content-Type": "application/x-www-form-urlencoded" }
}
var xhr = doHTTP(request);
return xhr ? xhr.access_token : null;
}
}
// Request bearer token using a Certificate
function requestTokenCertificate(config)
{
if (!unlockChilkat()) {
return false;
}
var pfx = new ActiveXObject("Chilkat_9_5_0.Pfx");
var success = pfx.LoadPfxFile(custabspath + "\\Exchange\\" + config.certificate, config.certificate_password);
if (success != 1) {
__DoLog("LoadEncryptedPemFile geen succes", "#FF0000");
__DoLog(pfx.LastErrorText);
return null;
}
var privKey = pfx.GetPrivateKey(0);
var jwt = new ActiveXObject("Chilkat_9_5_0.Jwt");
var jose = new ActiveXObject("Chilkat_9_5_0.JsonObject");
var thumbprint = "thumbprint" in config ? config.thumbprint : config.client_assertion.header.x5t;
jose.AppendString("alg", "RS256");
jose.AppendString("typ", "JWT");
// x5t to base64
jose.AppendString("x5t", oCrypto.hex2base64(thumbprint, false, true));
var claims = new ActiveXObject("Chilkat_9_5_0.JsonObject");
var jwtDateTime = new Date();
claims.AppendString("iss", config.client_id);
claims.AppendString("jti", getGUID()); // (Unique) JWT token identifier
claims.AppendString("sub", config.client_id);
claims.AppendString("aud", "https://login.microsoftonline.com/" + config.tenant + "/oauth2/v2.0/token");
claims.AddIntAt(-1, "iat", Math.floor(jwtDateTime.getTime()/1000)); // Issued at time
claims.AddIntAt(-1, "nbf", Math.floor(jwtDateTime.getTime()/1000)); // Not before
jwtDateTime.setMinutes(jwtDateTime.getMinutes() + 10);
claims.AddIntAt(-1, "exp", Math.floor(jwtDateTime.getTime()/1000)); // Expiration time
var parms = 'grant_type=client_credentials'
+ '&client_id=' + config.client_id
+ '&scope=' + 'https://graph.microsoft.com/.default'
+ '&client_assertion=' + jwt.CreateJwtPk(jose.Emit(), claims.Emit(), privKey)
+ '&client_assertion_type=' + 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer';
var request = {
"method": "POST",
"url": "https://login.microsoftonline.com/" + config.tenant + "/oauth2/v2.0/token",
"body": parms,
"headers": { "Content-Type": "application/x-www-form-urlencoded" }
}
var xhr = doHTTP(request);
return xhr ? xhr.access_token : null;
}
/*////////////////////////////////
// //
// Helper functions //
// //
////////////////////////////////*/
function loadconfig(configpath)
{
if (!fso.FileExists(configpath))
return null;
var f = fso.OpenTextFile(configpath, 1); // ForReading
var config = eval("(" + f.ReadAll() + ")");
config.loglevel = config.loglevel || 0;
f.Close();
return config;
}
function setMSGraphSyncLevel()
{
if (S_msgraph_sync_level !== null)
{
var sql = "SELECT COALESCE(fac_setting_pvalue, fac_setting_default) fac_setting_value"
+ " FROM fac_setting"
+ " WHERE fac_setting_name = 'msgraph_sync_level'";
var oRs = Oracle.Execute(sql);
/* global */ S_msgraph_sync_level = oRs("fac_setting_value").Value;
oRs.Close();
}
}
function getMSGraphSyncLevel()
{
if (S_msgraph_sync_level === null)
setMSGraphSyncLevel();
return S_msgraph_sync_level;
}
/* Retuns the dates in between Facilitor synchronises appointments for this room */
function getCalendarViewDates(res_ruimte_extern_id) {
var dateFrom = new Date();
dateFrom.setDate(dateFrom.getDate() - config.fullpast);
dateFrom.setHours(0, 0, 0, 0);
var dateTo = new Date();
dateTo.setDate(dateTo.getDate() + config.fullfuture);
dateTo.setHours(23, 59, 59, 999);
var startdatum = null;
var vervaldatum = null;
var sql = "SELECT res_ruimte_startdatum, res_ruimte_vervaldatum"
+ " FROM res_ruimte"
+ " WHERE res_ruimte_extern_id = " + safe.quoted_sql(res_ruimte_extern_id)
+ " AND res_ruimte_verwijder IS NULL";
var oRs = Oracle.Execute(sql);
if (!oRs.EoF) {
startdatum = oRs("res_ruimte_startdatum").Value;
vervaldatum = oRs("res_ruimte_vervaldatum").Value;
}
oRs.Close();
if (startdatum !== null) {
startdatum = new Date(startdatum);
}
if (vervaldatum !== null) {
vervaldatum = new Date(vervaldatum);
}
if (startdatum !== null && startdatum.getTime() >= dateTo.getTime() || // Ruimte begint pas na de einddatum van de calenderView
vervaldatum !== null && vervaldatum.getTime() <= dateFrom.getTime()) { // Ruimte is vervallen voor de begindatum van de calendarView
return false; // Nothing to do
}
if (startdatum !== null && startdatum.getTime() > dateFrom.getTime()) {
dateFrom = startdatum;
}
dateTo.setHours(0, 0, 0, 0); // Vergelijk op dag
if (vervaldatum !== null && vervaldatum.getTime() <= dateTo.getTime()) {
dateTo = new Date(vervaldatum.setDate(vervaldatum.getDate() - 1));
}
dateTo.setHours(23, 59, 59, 999);
if (dateTo.getTime() <= dateFrom.getTime()) {
return false; // Nothing to do
}
return { "from": dateFrom, "to": dateTo };
}
/* Alleen als ze bij locations staan en geaccepteerd hebben */
function getLocationsFromEvent(event) {
var results = [];
if (event && ("locations" in event) && ("attendees" in event)) {
for (var l in event.locations) {
// Vind de locaties bij de attendees en kijk of ze geaccepteerd hebben
for (var a in event.attendees) {
if (event.locations[l].uniqueId === event.attendees[a].emailAddress.address &&
event.attendees[a].status.response === "accepted" &&
!inArray(event.attendees[a].emailAddress.address.toUpperCase(), results))
{
results.push(event.attendees[a].emailAddress.address.toUpperCase());
}
}
}
}
return results;
}
/* Returns the event data */
function getCalendarEventByID(userPrincipalName, id)
{
var request = {
"method": "GET",
"url": "https://graph.microsoft.com/v1.0/users/" + encodeURIComponent(userPrincipalName) + "/events/" + id
+ "?$expand=singleValueExtendedProperties($filter=id eq '" + FCLT_KEY_PROP_ID_NAME + "')",
"headers": { "Authorization": "Bearer " + token }
}
return doHTTP(request);
}
/* Returns the event data */
function getCalendarEventByICalUId(userPrincipalName, iCalUId, isOccurrence)
{ // occurrences (en exceptions) zijn alleen via de calenderView beschikbaar, seriesMaster alleen via events (singleInstance via beiden)
var endpoint = isOccurrence ? "calendarView" : "events";
var url = "https://graph.microsoft.com/v1.0/users/" + encodeURIComponent(userPrincipalName) + "/" + endpoint + "?$filter=iCalUId eq '" + iCalUId + "'"
+ "&$expand=singleValueExtendedProperties($filter=id eq '" + FCLT_KEY_PROP_ID_NAME + "')";
if (isOccurrence) { // Dit endpoint heeft verplichte datum-parameters
var calendarDates = getCalendarViewDates(userPrincipalName);
if (!calendarDates) {
return null;
}
url += "&startDateTime=" + calendarDates.from.toISOString() + "&endDateTime=" + calendarDates.to.toISOString();
}
var request = {
"method": "GET",
"url": url,
"headers": { "Authorization": "Bearer " + token }
}
var xhr = doHTTP(request);
return xhr ? xhr.value[0] : null;
}
/* Returns the event data */
function getCalendarEventByRsv(userPrincipalName, res_rsv_ruimte_key)
{
var request = {
"method": "GET",
"url": "https://graph.microsoft.com/v1.0/users/" + encodeURIComponent(userPrincipalName) + "/events?"
+ "$filter=singleValueExtendedProperties/Any(ep: ep/id eq '" + FCLT_KEY_PROP_ID_NAME + "' and ep/value eq '" + res_rsv_ruimte_key + "')",
"headers": { "Authorization": "Bearer " + token }
}
var xhr = doHTTP(request);
if (xhr && xhr.value && xhr.value.length === 1) { /* Eenduidig gevonden? */
return xhr.value[0];
} else {
return null;
}
}
/* Returns all changed events within the configurated timeframe */
function getCalendarItems(userPrincipalName, skiptoken, deltatoken)
{
var parms;
if (skiptoken) { // Set parameters to read the next batch of events
parms = "$skiptoken=" + skiptoken;
} else if (deltatoken) { // Set parameters to read first batch of changed events
parms = "$deltatoken=" + deltatoken;
} else { // Set timeframe parameters to read all changed events
var calendarDates = getCalendarViewDates(userPrincipalName);
if (!calendarDates) {
return false;
}
parms = 'startDateTime=' + calendarDates.from.toISOString() + '&endDateTime=' + calendarDates.to.toISOString();
__Log("Full syncing from " + calendarDates.from.toISOString() + " to " + calendarDates.to.toISOString() + " for user " + userPrincipalName
+ " (" + Math.ceil((calendarDates.to.getTime() - calendarDates.from.getTime() - (1000 * 3600)) / (1000 * 3600 * 24)) + " days)"); // 1u extra aftrekken om de wintertijd niet als dag te laten tellen
}
var request = {
"method": "GET",
"url": "https://graph.microsoft.com/v1.0/users/" + encodeURIComponent(userPrincipalName) + "/calendarView/delta?" + parms,
"headers": { "Accept": "application/json", "Authorization": "Bearer " + token }
}
return doHTTP(request);
}
/* Returns all known subscriptions */
function getSubscriptions(skiptoken)
{
var parms = "";
if (typeof skiptoken !== "undefined") { // Set parameters to read the next batch of events
parms = "?$skiptoken=" + skiptoken;
}
var request = {
"method": "GET",
"url": "https://graph.microsoft.com/v1.0/subscriptions" + parms,
"headers": { "Accept": "application/json", "Authorization": "Bearer " + token }
}
return doHTTP(request);
}
/* Returns the notificationUrl used by MS Graph to send Facilitor notifications about changes in resources on which we are subscribed */
function getNotificationUrl(customerId)
{
if (!customerId) {
return false;
}
var hookurl;
if (config.hookurl) {
hookurl = config.hookurl;
} else { // Autodetect ngrok started with: ngrok http sggr.facws001.sg.nl:80
var fac_version = custabspath.split("\\")[custabspath.split("\\").length - 3]; // 'Branch#####' of 'Trunk', 'APPL' op productie, maar die komt hier niet
var request = {
"method": "GET",
"url": "http://127.0.0.1:4040/api/tunnels/command_line",
"headers": { "Accept": "application/json" },
"params": {
"callback": function (request, response) {
__Log("Ngrok detected: " + response.public_url);
__Log(" tip: view traffic at http://127.0.0.1:4040");
hookurl = response.public_url + "/" + fac_version + "/";
}
}
}
doHTTP(request);
}
if (!hookurl) {
_AiAi("INTERNAL_ERROR attempting to subscribe without a valid hookurl");
}
var apikey;
var apiname = "MSGRAPHNOTIFICATION";
var sql = "SELECT prs_perslid_apikey FROM prs_perslid WHERE prs_perslid_oslogin = '_MSGRAPHNOTIFICATION'";
var oRs = Oracle.Execute(sql);
if (!oRs.EoF) {
apikey = oRs("prs_perslid_apikey").Value;
} else {
__DoLog("Error: There exists no user with login '_MSGRAPHNOTIFICATION'", "#FF0000");
}
oRs.Close();
if (apikey == null) {
__DoLog("Error: The user with login '_MSGRAPHNOTIFICATION' does not have an API-key configured", "#FF0000");
return false;
}
return hookurl + "?API={0}&APIKEY={1}&fac_id={2}".format(apiname, apikey, customerId); // fac_id is vooral voor ngrok nodig
}
function validNotificationUrl(url) {
// Test zelf ook even de hookurl direct op dezelfde manier als MS-Graph dat doen.
// Zelf kunnen we betere foutmeldingen geven
var validationToken = "Validation: testing MS Graph <-> notificationUrl connection (" + new Date().toISOString() + ")";
__Log("\nPre-testing webhook url (like MS Graph will do soon): ");
__Log("get "+ url + "&validationToken=" + encodeURIComponent(validationToken));
var request = {
"method": "POST",
"url": url + "&validationToken=" + encodeURIComponent(validationToken),
"headers": {"Accept": "application/json" },
"params": {
"callback": function (request, response) {
return response.responseText == validationToken;
}
}
}
return doHTTP(request);
}
function getSyncToken(zaalemail) {
var token = null;
// Zoek token op
var sql = "SELECT s.res_ruimte_syncstate" // syncstate = deltatoken
+ " FROM res_ruimte r"
+ " , res_ruimte_sync s"
+ " WHERE r.res_ruimte_key = s.res_ruimte_key(+)"
+ " AND r.res_ruimte_verwijder IS NULL"
+ " AND res_ruimte_extern_id = " + safe.quoted_sql(zaalemail);
var oRs = Oracle.Execute(sql);
if (!oRs.EoF) {
token = oRs("res_ruimte_syncstate").Value;
}
oRs.Close();
return token;
}
function upsertSyncToken(zaalemail, token) {
var res_ruimte_key;
if (!isNaN(zaalemail) && typeof zaalemail === "number") { // De res_ruimte_key is al meegegeven (ipv de zaalemail)
res_ruimte_key = zaalemail;
} else if (typeof zaalemail === "string") { // Bepaal res_ruimte_key
var sql = "SELECT res_ruimte_key"
+ " FROM res_ruimte"
+ " WHERE res_ruimte_verwijder IS NULL"
+ " AND res_ruimte_extern_id = " + safe.quoted_sql(zaalemail);
var oRs = Oracle.Execute(sql);
if (oRs.EoF) {
__DoLog("Error: Ruimte met extern id '" + zaalemail + "' niet gevonden");
oRs.Close();
return false;
}
res_ruimte_key = oRs("res_ruimte_key").Value;
oRs.Close();
} else {
__DoLog("Error: Invalid type zaalemail '" + zaalemail + "' [" + (typeof zaalemail) + "]");
return false;
}
// Bestaat er voor deze ruimte al een syncstate?
sql = "SELECT 'dummy'"
+ " FROM res_ruimte_sync"
+ " WHERE res_ruimte_key = " + res_ruimte_key;
oRs = Oracle.Execute(sql);
var isNew = oRs.EoF;
oRs.Close();
// Upsert de syncstate
if (isNew) { // Deze ruimte wordt voor het eerst gekoppeld, maak een res_ruimte_sync-record aan
sql = "INSERT INTO res_ruimte_sync (res_ruimte_key,"
+ " res_ruimte_syncstate,"
+ " res_ruimte_syncdate)"
+ " VALUES (" + res_ruimte_key + ", " + safe.quoted_sql(token) + ", SYSDATE)";
} else { // De res_ruimte_sync-record bestaan al voor deze ruimte, update de syncstate/deltatoken
sql = "UPDATE res_ruimte_sync"
+ " SET res_ruimte_syncstate = " + safe.quoted_sql(token)
+ " , res_ruimte_syncdate = SYSDATE"
+ " WHERE res_ruimte_key = " + res_ruimte_key;
}
Oracle.Execute(sql);
return true;
}
/* Mogelijk is deze notificatie een @removed/isCancelled-notificatie
In deze functie valideren we dat deze notificaties ook zouden moeten leiden tot een Facilitor [D]elete
Mogelijk is er nl. alleen van ruimte gewisseld, en moeten we de reservering niet weggooien
*Deze functie werkt momenteel alleen voor Facilitor ruimte wijzigingen
*/
function validateNotification(userPrincipalName, roomCalendarEvent) {
if ((getMSGraphSyncLevel() & 4) && roomCalendarEvent.isCancelled) {
// Mogelijk slechts van ruimte gewisseld, kunnen we dit event nog terugvinden bij de organisator en zit er een Facilitor-ruimte bij deze afspraak?
var organizerCalendarEvent = getCalendarEventByICalUId(roomCalendarEvent.organizer.emailAddress.address, roomCalendarEvent.iCalUId, roomCalendarEvent.seriesMasterId);
if (organizerCalendarEvent && organizerCalendarEvent.isCancelled === false && organizerCalendarEvent.attendees && organizerCalendarEvent.attendees.length) {
for (var i = 0; i < organizerCalendarEvent.attendees.length; i++) {
var attendee = organizerCalendarEvent.attendees[i];
if (attendee.type === "resource" && attendee.emailAddress.address != userPrincipalName && attendee.status.response !== "declined") { // Status none/notResponded/accepted krijgen we via een andere notificatie binnen en verwerken we daar/later
var sql = "SELECT '' FROM res_ruimte WHERE res_ruimte_extern_id = " + safe.quoted_sql(attendee.emailAddress.address);
var oRs = Oracle.Execute(sql);
var roomChanged = !oRs.EoF; // Ruimte is gewisseld naar een bestaande Facilitor ruimte; negeer deze delete
oRs.Close();
if (roomChanged) {
return false;
}
}
}
}
}
return true;
}
/* Zo wordt het externnr in Facilitor opgeslagen */
function getExternnrFromEvent(thisEvent, params) {
params = params || {};
var externnr;
// Bepaal externnr
switch (thisEvent.type) {
// case "seriesMaster": // Komt hier niet voor
// case "occurrence": // Komt hier niet voor
case "exception": // We doen hier niets met iCalUId want dat gebruiken we alleen voor room-changes, en dat ondersteunen we niet in Outlook-series
externnr = thisEvent.seriesMasterId + "|" + thisEvent.id;
break;
case "singleInstance":
externnr = (params.iCalUId ? "##iCalUId##" + thisEvent.iCalUId : thisEvent.id) + "|";
break;
}
return externnr;
}
/*
Update het Facilitor res_rsv_ruimte-record met link-data/id's
Returns the new res_rsv_ruimte_externnr
*/
function updateFcltRecord(res_rsv_ruimte_key, thisEvent, externnr2, params) {
var externnr;
var sql = "UPDATE res_rsv_ruimte"
+ " SET res_rsv_ruimte_externsyncdate = SYSDATE";
if (thisEvent) {
externnr = getExternnrFromEvent(thisEvent, params);
sql += " , res_rsv_ruimte_externnr = " + safe.quoted_sql(externnr)
} else if (thisEvent === null) { // Expliciet null maakt het veld leeg
sql += " , res_rsv_ruimte_externnr = NULL"
}
if (getMSGraphSyncLevel() & 4) {
if (externnr2) {
sql += ", res_rsv_ruimte_externnr2 = " + safe.quoted_sql(externnr2);
} else {
sql += ", res_rsv_ruimte_externnr2 = "
+ " (CASE"
+ " WHEN res_rsv_ruimte_externnr2 = '##PENDING##'"
+ " THEN NULL" // Herstel de koppeling van deze reservering
+ " ELSE res_rsv_ruimte_externnr2"
+ " END)";
}
}
sql += " WHERE res_rsv_ruimte_key = " + res_rsv_ruimte_key;
Oracle.Execute(sql);
return typeof externnr === undefined || externnr;
}
/*
Heeft dit roomCalendarEvent een Facilitor key aan boord die we kunnen matchen?
*/
function getFcltKeyFromEvent(roomCalendarEvent) {
if ("singleValueExtendedProperties" in roomCalendarEvent) {
for (var i = 0; i < roomCalendarEvent.singleValueExtendedProperties.length; i++) {
if (roomCalendarEvent.singleValueExtendedProperties[i].id === FCLT_KEY_PROP_ID_NAME) { // Key gevonden
return parseInt(roomCalendarEvent.singleValueExtendedProperties[i].value, 10); // => res_rsv_ruimte_key
}
}
}
return -1;
}
/* Pas de FCLT ruimte aan indien nodig */
function checkUpdateRoomchange(roomCalendarEvent, recordset) {
var zalen = getLocationsFromEvent(roomCalendarEvent);
if (zalen.length === 1 && zalen[0] !== recordset("res_ruimte_extern_id").Value) { // De ruimte in Exchange != de gematchte FCLT ruimte; pas aan indien bekend
var zaalChangeSql = "UPDATE res_rsv_ruimte"
+ " SET res_ruimte_opstel_key = "
+ " (CASE"
+ " WHEN (SELECT exc.getOpstelling (" + safe.quoted_sql(zalen[0]) + ") FROM DUAL) IS NOT NULL"
+ " THEN (SELECT exc.getOpstelling (" + safe.quoted_sql(zalen[0]) + ") FROM DUAL)"
+ " ELSE res_ruimte_opstel_key"
+ " END)"
+ " WHERE res_rsv_ruimte_key = " + recordset("res_rsv_ruimte_key").Value;
Oracle.Execute(zaalChangeSql);
// De rest (dirty-state, objecten & artikelen) wordt met de import wel rechtgetrokken, bezoekers niet, deleted = deleted
}
}
/* Undelete een reservering incl. bijbehorenden */
function undeleteFclt(res_rsv_ruimte_key) {
/* Reanimeer de Delen */
sql = "UPDATE res_rsv_deel"
+ " SET res_rsv_deel_verwijder = NULL"
+ " WHERE res_rsv_ruimte_key = " + res_rsv_ruimte_key
+ " AND res_rsv_deel_verwijder = (SELECT res_rsv_ruimte_verwijder"
+ " FROM res_rsv_ruimte"
+ " WHERE res_rsv_ruimte_key = " + res_rsv_ruimte_key + ")";
Oracle.Execute(sql);
/* Reanimeer de Artikelen */
sql = "UPDATE res_rsv_artikel"
+ " SET res_rsv_artikel_verwijder = NULL"
+ " WHERE res_rsv_ruimte_key = " + res_rsv_ruimte_key
+ " AND res_rsv_artikel_verwijder = (SELECT res_rsv_ruimte_verwijder"
+ " FROM res_rsv_ruimte"
+ " WHERE res_rsv_ruimte_key = " + res_rsv_ruimte_key + ")";
Oracle.Execute(sql);
/* Reanimeer de Reservering */
var sql = "UPDATE res_rsv_ruimte"
+ " SET res_rsv_ruimte_externsyncdate = SYSDATE"
+ " , res_rsv_ruimte_verwijder = NULL"
+ " , res_status_fo_key = 2"
+ " WHERE res_rsv_ruimte_key = " + res_rsv_ruimte_key;
Oracle.Execute(sql);
// Afspraken zijn hard verwijderd, dat kan dus niet meer hersteld worden
}
/* Test of de ruimte uberhaupt al geaccepteerd heeft */
function eventReadyForProcessing(userPrincipalName, thisEvent) {
if (!thisEvent.isOrganizer && ("attendees" in thisEvent)) { // Een notificatie van een uitgenodigde ruimte
for (var i in thisEvent.attendees) {
var attendee = thisEvent.attendees[i];
if (attendee.emailAddress.address === userPrincipalName && attendee.status.response != "accepted") { // Er is nog helemaal niet geaccepteerd
return false;
}
}
}
return true;
}
/* Alleen bij S(msgraph_sync_level) & 4
Deze functie wordt aangeroepen na het ontvangen van een notificatie van een event die in Facilitor nog niet bekend is
We proberen de notificatie te matchen met een Facilitor reservering (als die bestaat) en te complementeren met het externnr
Reserveringen vanuit Outlook gebruiken deze functie niet, die hebben al direct bij het aanmaken een externnr
*/
function updateExternnr(userPrincipalName, id)
{
var roomCalendarEvent = getCalendarEventByID(userPrincipalName, id);
if (!roomCalendarEvent || !validateNotification(userPrincipalName, roomCalendarEvent)) {
return false;
}
if (roomCalendarEvent.type === "seriesMaster") { // Niets te updaten, we registreren geen seriesMaster's in Facilitor, de occurrences volgen hierna
return null;
}
if (!eventReadyForProcessing(userPrincipalName, roomCalendarEvent)) {
return -1; // 'Errorcode'; -> Stop this import
}
/* Kunnen we de notificatie matchen met een Facilitor reservering die zojuist is gemaakt op basis van de res_rsv_ruimte_key? */
var res_rsv_ruimte_key = getFcltKeyFromEvent(roomCalendarEvent);
var externnr = null;
var sql = "SELECT rr.res_rsv_ruimte_key"
+ " , rr.res_rsv_ruimte_verwijder"
+ " , r.res_ruimte_extern_id"
+ " FROM res_rsv_ruimte rr"
+ " , res_ruimte_opstelling ro"
+ " , res_ruimte r"
+ " WHERE ro.res_ruimte_opstel_key = rr.res_ruimte_opstel_key"
+ " AND r.res_ruimte_key = ro.res_ruimte_key";
if (res_rsv_ruimte_key > -1) { // Het event bevat een Facilitor res_rsv_ruimte_key, zoek daarop
sql += " AND rr.res_rsv_ruimte_key = " + res_rsv_ruimte_key;
} else if (roomCalendarEvent.type === "singleInstance") { // Probeer dan te matchen op iCalUId
sql += " AND rr.res_rsv_ruimte_externnr = " + safe.quoted_sql("##iCalUId##" + roomCalendarEvent.iCalUId + "|");
} else {
sql += " AND 1 = 0";
}
var oRs = Oracle.Execute(sql);
if (!oRs.EoF) { // Gevonden
var isDeleted = oRs("res_rsv_ruimte_verwijder").Value !== null;
var reanimate = !(roomCalendarEvent["@removed"] || roomCalendarEvent.isCancelled) &&
isDeleted &&
new Date().getTime() - new Date(oRs("res_rsv_ruimte_verwijder").Value).getTime() < REANIMATE_THRESHOLD_MS;
if (reanimate) {
undeleteFclt(oRs("res_rsv_ruimte_key").Value);
} else if (isDeleted) { // 'Grace period' van een soft delete overschreden; verwijder link onder water en ga verder met nieuwe afspraak
if (res_rsv_ruimte_key > -1) { // Haal de rsv-key weg bij het event; we gaan deze nooit meer gebruiken.
patchEventWithFcltKey(roomCalendarEvent);
}
updateFcltRecord(oRs("res_rsv_ruimte_key").Value, null); // Verwijder de opgeslagen linked id's permanent uit Facilitor
oRs.Close();
return null; // Niets (relevants) gevonden, dus niets te updaten
} else if (res_rsv_ruimte_key === -1 && !(roomCalendarEvent["@removed"] || roomCalendarEvent.isCancelled)) { // Gevonden, het actuele event bevat nog geen Facilitor res_rsv_ruimte_key, patch dat direct maar
patchEventWithFcltKey(roomCalendarEvent, oRs("res_rsv_ruimte_key").Value);
}
checkUpdateRoomchange(roomCalendarEvent, oRs);
externnr = updateFcltRecord(oRs("res_rsv_ruimte_key").Value, roomCalendarEvent);
}
oRs.Close();
return externnr;
}
/*
Attempts to update the Facilitor-rsv-record with the organizer-event-id
Only called when S(msgraph_sync_level) & 4
*/
function updateExternnr2(userPrincipalName, id, hostPrincipalName)
{
var roomCalendarEvent = getCalendarEventByID(userPrincipalName, id);
if (!roomCalendarEvent || roomCalendarEvent.type === "seriesMaster" /* Hier doet Facilitor niets mee */)
return "";
var organizerCalendarEvent = getCalendarEventByICalUId(hostPrincipalName, roomCalendarEvent.iCalUId, roomCalendarEvent.seriesMasterId);
if (!organizerCalendarEvent)
return "";
var matchByExternnr;
if (roomCalendarEvent.type == "singleInstance") {
matchByExternnr = "res_rsv_ruimte_externnr = " + safe.quoted_sql(id + "|"); // Exact
} else { // occurrence of exception
var occurrenceId = id.replace(/_/g, '\\_');
matchByExternnr = "res_rsv_ruimte_externnr LIKE " + safe.quoted_sql("%|" + occurrenceId) + " ESCAPE '\\'"; // LIKE
}
id = id.replace(/_/g, '\\_'); // Escape de underscore
var sql = "UPDATE res_rsv_ruimte"
+ " SET res_rsv_ruimte_externnr2 = " + safe.quoted_sql(organizerCalendarEvent.id)
+ " WHERE " + matchByExternnr;
Oracle.Execute(sql);
return organizerCalendarEvent.id;
}
/* Patch het Exchange event met de Facilitor res_rsv_ruimte_key */
function patchEventWithFcltKey(roomCalendarEvent, res_rsv_ruimte_key) {
var userPrincipalName = roomCalendarEvent.organizer.emailAddress.address;
var organisatorEvent = getCalendarEventByICalUId(userPrincipalName, roomCalendarEvent.iCalUId, roomCalendarEvent.seriesMasterId);
var data = {
"singleValueExtendedProperties": [{
"id": FCLT_KEY_PROP_ID_NAME,
"value": "-1"
}]
}
if (res_rsv_ruimte_key) {
data.singleValueExtendedProperties[0].value = String(res_rsv_ruimte_key);
}
var request = {
"method": "PATCH",
"url": "https://graph.microsoft.com/v1.0/users/" + encodeURIComponent(userPrincipalName) + "/events/" + organisatorEvent.id,
"body": JSON.stringify(data),
"headers": { "Content-Type": "application/json", "Authorization": "Bearer " + token }
}
var result = doHTTP(request);
return result;
}
/*////////////////////////////////
// //
// Main functions //
// //
////////////////////////////////*/
function doCheckExpiringAuth()
{
if (!unlockChilkat()) {
return false;
}
var certificates = new ActiveXObject("Chilkat_9_5_0.CertStore");
var success = certificates.LoadPfxFile(custabspath + "\\Exchange\\" + config.certificate, config.certificate_password);
if (success) {
var cert = certificates.GetCertificate(0);
var expirationDate = new Date(cert.ValidToStr);
var expDays = Math.floor((expirationDate.getTime() - new Date().getTime()) / 1000 / 60 / 60 / 24); // Days untill expiration
if (expDays < 30) {
var txt = "Uw Outlook koppeling certificaat verloopt over " + expDays + " dagen. Neem contact op met Facilitor voor een nieuw certificaat";
var sql = "BEGIN fac.putsystemnotification({0}, 3); END;".format(safe.quoted_sql(txt)); // 3=portal+email
Oracle.Execute(sql);
}
}
return true;
}
function syncPending(res_ruimte_key) {
if (!(getMSGraphSyncLevel() & 4)) { // Alleen voor de volledige koppeling
return false;
}
// Zoek de ##PENDING## reserveringen op
var sql = _getSql("C")
+ " AND rrr.res_rsv_ruimte_externnr2 = '##PENDING##'";
if (res_ruimte_key > 0) {
sql += " AND rr.res_ruimte_key = " + res_ruimte_key
}
var oRs = Oracle.Execute(sql);
while (!oRs.EoF) {
var rsv_ruimte_key = oRs("res_rsv_ruimte_key").Value;
var thisEvent = getCalendarEventByRsv(oRs("host_mail").Value, rsv_ruimte_key); // Is deze afspraak al wel bekend in Exchange?
if (thisEvent === null) { /* Niet gevonden */
_resToGraph(oRs, "C"); // Dan maken we hem bij deze alsnog aan
} else { /* We hebben hem toch gevonden! Link ze aan elkaar; */
updateFcltRecord(rsv_ruimte_key, thisEvent, thisEvent.id, { "iCalUId": true });
}
oRs.moveNext();
}
oRs.Close();
return true;
}
// Let op, deze functie doet eerst een full-sync
function createSubscription(userPrincipalName, res_ruimte_key, notificationUrl, urlValidated) {
if (!urlValidated && !validNotificationUrl(notificationUrl)) {
return false;
}
// Ververs syncstate (=maak leeg) zodat we niet de incremental changes binnenhalen, maar de hele calendarView
upsertSyncToken(+res_ruimte_key, null);
// Nu full-'sync'en, dan hebben we zsm een deltaToken waarmee we alleen mutaties kunnen ophalen
doImport(res_ruimte_key, userPrincipalName);
// En dan nu de subscription aanmaken
var expirationDate = new Date();
expirationDate.setDate(expirationDate.getDate() + 2); // 2 dagen (max is 3)
__Log("Creating hook for " + userPrincipalName);
var changeType = "updated";
var clientState = oCrypto.hex_random(20);
// Alle afspraken komen binnen met de "updated"-notificatie (de update is nl. dat de ruimte de afspraak heeft geaccepteerd),
// behalve afspraken die direct in een ruimte-agenda worden geboekt want die hoeven niet eerst geaccepteerd te worden.
// Die afspraken willen we alleen in Facilitor importeren als we een fallback-user hebben geconfigureerd, anders niet.
var sql = "SELECT prs_perslid_email FROM prs_v_aanwezigperslid WHERE prs_perslid_oslogin = " + safe.quoted_sql_upper("_MSGRAPH_FALLBACK_ROOMS");
var oRs = Oracle.Execute(sql);
if (!oRs.EoF && oRs("prs_perslid_email").Value) {
changeType = "created,updated";
}
oRs.Close();
var data = {
"changeType" : changeType,
"notificationUrl" : notificationUrl + "&res_ruimte=" + res_ruimte_key + "&hookcreated=" + new Date().toISOString(),
"resource" : "/users/" + encodeURIComponent(userPrincipalName) + "/events",
"clientState" : clientState,
"expirationDateTime": expirationDate
}
var newSubscriptionCallback = function _newSubscriptionCallback(request, response) {
__Log("Gelukt");
__Log(JSON.stringify(response, null, 2));
// Update de data aangaande de (ver)nieuw(d)e subscription
sql = "UPDATE res_ruimte"
+ " SET res_ruimte_graphhooksecret = " + safe.quoted_sql(clientState) // Met dit secret verifieren we dat requests van MS Graph ook echt van MS Graph zijn
+ " , res_ruimte_externsyncdate = SYSDATE" // Dit is de datum waarop de subscription is aangemaakt
+ " WHERE res_ruimte_key = " + res_ruimte_key;
Oracle.Execute(sql);
// Kijk nu of we nog pending (facilitor)reserveringen hebben en corrigeer die
syncPending(res_ruimte_key);
return true;
}
var request = {
"method": "POST",
"url": "https://graph.microsoft.com/v1.0/subscriptions",
"body": JSON.stringify(data),
"headers": { "Content-Type": "application/json", "Authorization": "Bearer " + token },
"params": { "callback": newSubscriptionCallback }
}
return doHTTP(request);
}
function deleteSubscription(userPrincipalName) {
var response = getSubscriptions();
var subscriptionList = [];
if (response && response.value) {
var resource = "/users/" + encodeURIComponent(userPrincipalName) + "/events";
for (var i = 0; i < response.value.length; i++) {
if (response.value[i].resource == resource) { // Filter op Extern ID
subscriptionList.push(response.value[i].id);
}
}
while (response.skiptoken) {
response = getSubscriptions(response.skiptoken);
for (var i = 0; i < response.value.length; i++) {
if (response.value[i].resource == resource) { // Filter op Extern ID
subscriptionList.push(response.value[i].id);
}
}
}
}
var success = true;
for (var i = 0; i < subscriptionList.length; i++) {
var request = {
"method": "DELETE",
"url": "https://graph.microsoft.com/v1.0/subscriptions/" + subscriptionList[i],
"headers": { "Accept": "application/json", "Authorization": "Bearer " + token }
}
success = success && doHTTP(request);
}
return success;
}
/*////////////////////////////////
// //
// Utils //
// //
////////////////////////////////*/
function doHTTP(method, url, body, headers, params)
{
if (arguments.length === 1 && typeof arguments[0] === "object") {
request = arguments[0];
var method = request.method;
var url = request.url;
var body = request.body || null;
var headers = request.headers;
headers["client-request-id"] = getGUID();
request.params = request.params || {};
var params = request.params;
} else { // Deprecated; het request is nu 1 object als argument
var params = params || {};
}
// var SXH_PROXY_SET_PROXY = 2;
var SXH_OPTION_IGNORE_SERVER_SSL_CERT_ERROR_FLAGS = 2;
var SXH_SERVER_CERT_IGNORE_ALL_SERVER_ERRORS = 0x3300;
var objXMLHTTP = new ActiveXObject("MSXML2.ServerXMLHTTP.6.0");
objXMLHTTP.setTimeouts(1200000, 270000, 255000, 240000);
// Defaults: Unlimited, 60000, 30000, 30000
objXMLHTTP.open(method, url, !!params.async);
if (headers) {
if ("Authorization" in headers && headers.Authorization === "Bearer null") {
headers.Authorization = "Bearer " + requestToken(config);
}
var header;
for (header in headers) {
objXMLHTTP.setRequestHeader(header, headers[header]);
}
}
var user_lang = "nl";
var server_tz = "Europe/Amsterdam";
if (typeof DEZE !== "undefined") { // Alleen vanuit de browser
user_lang = (DEZE.Session("user_lang") || "nl").toLowerCase();
server_tz = DEZE.S("fac_server_timezone");
}
objXMLHTTP.setRequestHeader("Accept-Language", user_lang);
objXMLHTTP.setRequestHeader("Prefer", "outlook.timezone=\"" + server_tz + "\""); // Dan hoeven wij niets meer om te zetten
objXMLHTTP.setOption(SXH_OPTION_IGNORE_SERVER_SSL_CERT_ERROR_FLAGS, SXH_SERVER_CERT_IGNORE_ALL_SERVER_ERRORS);
if (config.loglevel > 0 && body) {
__Log2File("request.xml", body);
}
var start = new Date().getTime();
try {
objXMLHTTP.send(body);
} catch (e) {
var duration = new Date().getTime() - start;
__DoLog("doHTTP ERROR: " + e.description + " (" + duration + "ms)", "#FF0000"); // Bijvoorbeeld; 'A connection with the server could not be established' als ngrok lokaal niet draait
if (request) {
__DoLog(request);
}
return null;
}
return _handleMsGraphResponse(request, objXMLHTTP);
}
/* Evalueert een MS Graph response */
function _handleMsGraphResponse(request, httpResponse)
{
if (request.params.async) {
if (httpResponse.readyState && httpResponse.readyState != 4) {
httpResponse.waitForResponse(180); // Wacht max. 180s op antwoord
}
}
if (!request.batch && // Als request.batch = true, dan is httpResponse een geparsed object, en geen echt httpResponse
!(httpResponse.readyState && httpResponse.readyState == 4) ||
!(httpResponse.status && httpResponse.status >= 200 && httpResponse.status < 300)) {
return _handleMsGraphFailure(request, httpResponse);
} else if (request.method === "DELETE") { // 204 = 'No Content' dus niks terug te geven
return true;
}
/* Parse het alvast, 'batch' is al een geparsed object, en geen httpResponse */
if (request.batch) {
httpResponse = httpResponse.body;
} else { /* MS Graph returned altijd JSON */
try {
httpResponse = JSON.parse(httpResponse.responseText);
} catch (e) { /* Ok, dan niet */ }
}
if (request.method === "GET") { // Detecteer skip/delta-tokens en zet die in het response-object
if ("@odata.deltaLink" in httpResponse) { // Dit is de deltatoken die we opslaan als syncstate; "Tot hier zijn we gesynced"
httpResponse.deltatoken = httpResponse["@odata.deltaLink"].split("$deltatoken=")[1];
} else if ("@odata.nextLink" in httpResponse) { // Er is meer; we roepen ons zelf straks nogmaals aan met dit token
httpResponse.skiptoken = httpResponse["@odata.nextLink"].split("$skiptoken=")[1];
}
}
if (request.params.callback) {
return request.params.callback(request, httpResponse);
}
return httpResponse;
}
// Onderstaande functie wordt alleen aangeroepen als de response niet aanwezig, of onsuccesvol is
function _handleMsGraphFailure(request, httpResponse) {
if (httpResponse && (!httpResponse.readyState || httpResponse.readyState == 4) && httpResponse.status) {
try {
var msGraphResponse = httpResponse.responseText ? JSON.parse(httpResponse.responseText) : httpResponse.body; // Die laatste is voor batches
} catch (e) {
var msGraphResponse = httpResponse.responseText || "";
__DoLog("Error parsing MS Graph response (" + e.description + ")", "#FF0000");
__DoLog(msGraphResponse);
}
// Hier gaat iets fout
if (msGraphResponse !== null && typeof msGraphResponse == "object" && typeof msGraphResponse.error == "object") { // MS Graph errors
switch (msGraphResponse.error.code) {
case "ErrorItemNotFound":
__Log("Reservering kon niet in Exchange gevonden worden", "#FFFF44");
break;
case "ResyncRequired":
case "SyncStateNotFound":
__Log("[" + httpResponse.status + "] - MS Graph [" + msGraphResponse.error.code + "] - " + msGraphResponse.error.message, "#FFFF44");
__Log("Refreshing syncstate ...");
var userPrincipalName = request.url.substring(
request.url.indexOf("/users/") + "/users/".length,
request.url.lastIndexOf("/calendarView/")
);
return getCalendarItems(userPrincipalName, null, null); // delta token is gone, invalid of expired -> full sync
case "ApplicationThrottled":
__DoLog("[" + httpResponse.status + "] - MS Graph [ApplicationThrottled] - " + msGraphResponse.error.message, "#FF0000");
/* UNTESTED
var retryAfterXSeconds = parseInt(httpResponse.getResponseHeader("Retry-After"), 10);
__DoLog("volgens MS Graph moeten we het over " + retryAfterXSeconds + "s weer proberen", "#FFFF44");
if (!request || retryAfterXSeconds > 100) { // Dan niet hoor
return false;
}
var oSLNKDWF = new ActiveXObject("SLNKDWF.About");
oSLNKDWF.Sleep(retryAfterXSeconds * 1000);
return doHTTP(request);
*/
break;
case "FailedDependency":
__DoLog("[" + httpResponse.status + "] - MS Graph [FailedDependency] - " + msGraphResponse.error.message + " => Retrying individually ..", "#FFFF44");
delete request.batch;
delete request.dependsOn;
delete request.id;
request.url = "https://graph.microsoft.com/v1.0" + request.url;
request.body = JSON.stringify(request.body);
return doHTTP(request); // Individueel nog een keer proberen dan maar
// Vanaf hier lossen we de fouten niet zelf meer op
case "ErrorInvalidUser": // Gebruiker niet gevonden
case "ResourceNotFound": // Ruimte niet gevonden
case "MailboxNotEnabledForRESTAPI": // Gebruiker is inactief/soft-deleted in Exchange
default:
__DoLog("[" + httpResponse.status + "] - MS Graph [" + msGraphResponse.error.code + "] - " + msGraphResponse.error.message + " (request-id: " + httpResponse.getResponseHeader("request-id") + ")", "#FF0000");
if (request) {
__DoLog(request);
}
break;
}
} else if (msGraphResponse !== null && typeof msGraphResponse == "object" && typeof msGraphResponse.error == "string") { // Azure errors
__DoLog("[" + httpResponse.status + "] - Azure error: [" + msGraphResponse.error + "]: " + msGraphResponse.error_description, "#FF0000");
} else {
__DoLog("MS Graph HTTP-request NIET succesvol afgehandeld, onderstaand antwoord ontvangen", "#FF0000");
__DoLog(msGraphResponse);
}
} else {
__DoLog("Error: Geen response van MS Graph ontvangen (Configuratie/verbindings-problemen).", "#FF0000");
}
return false;
}
function getImportAppKey(appCode) {
var result = "";
var sql = "SELECT i.fac_import_app_key "
+ " FROM fac_import_app i"
+ " WHERE i.fac_import_app_code = " + safe.quoted_sql(appCode);
var oRs = Oracle.Execute(sql);
if (oRs.EOF) {
__DoLog("FATAL: Import '" + appCode + "' not found");
return false;
}
result = oRs("fac_import_app_key").Value;
oRs.Close();
return result;
}
function doImport(res_ruimte_key, zaalemail, import_type) {
if (typeof import_type === "undefined") {
var import_type = "EXCHFULL";
}
var import_app_key = getImportAppKey(import_type);
var fileStream = generateCalendarImport(zaalemail, import_app_key, true); // as Stream
if (!fileStream || !fileStream.Size) {
__Log("No action required after receiving MS Graph import", "#FFFF44");
return true; // Klaar
}
if (typeof DEZE !== "undefined") { /* Alleen vanuit de asp, en niet bij het runnen van refreshMSGraphSubscriptions.bat */
user_key = DEZE.user_key;
customerId = DEZE.customerId;
}
var procedure_params = ", " + (config.fullpast || 0) + ", " + (config.fullfuture || 90);
var res = impReadStream(fileStream,
import_app_key,
{ fac_home: custabspath + "/exchange/",
filepathname: "SYNC_" + zaalemail,
customerId: customerId,
ref_key: res_ruimte_key,
keep_old: 300, // Parallelle import 300 seconden ondersteunen (nodig voor deze import?)
user_key: user_key,
keep_backup: false, // mits fac_import_app_folder gezet
is_stream: true,
proc_params: procedure_params
});
if (res.success) {
var processres = impProcessStream(res.import_key, {
customerId: customerId,
proc_params: procedure_params
});
} else {
__DoLog(res);
}
return true;
}
if (!Date.prototype.toISOString) {
Date.prototype.toISOString = function () {
function pad(n) { return n < 10 ? '0' + n : n }
return this.getUTCFullYear() + '-'
+ pad(this.getUTCMonth() + 1) + '-'
+ pad(this.getUTCDate()) + 'T'
+ pad(this.getUTCHours()) + ':'
+ pad(this.getUTCMinutes()) + ':'
+ pad(this.getUTCSeconds()) + 'Z';
};
}
function getGUID() {
// 12345678-1234-1234-1234-123456789012 lijkt het meest gebruikte format, laten wij dat ook maar aanhouden dan
var guids = [];
guids.push(oCrypto.hex_random(8));
guids.push(oCrypto.hex_random(4));
guids.push(oCrypto.hex_random(4));
guids.push(oCrypto.hex_random(4));
guids.push(oCrypto.hex_random(12));
return guids.join("-");
}
function safefilename(naam) // geen 'lage' karakters en geen (back)slashes, *,%,<,>, '"', ':', ';' '?' and '|' of '+'
{
return naam.replace(/[\x00-\x1F|\/|\\|\*|\%\<\>\"\:\;\?\|\+]+/g, "_"); // " syntax highlight correctie
}
function __Log2File(log_file, data) {
if(data.indexOf("error") > -1)
{
__Log("__Log2File is misgegaan omdat data een error heeft: " + data + ".. Deze zal worden overgeslagen.");
}
else
{
try {
var target = custabspath + "\\Exchange\\" + config.xmlfolder + safefilename(log_file);
var utf8Stream = new ActiveXObject("ADODB.Stream");
utf8Stream.Open();
utf8Stream.Type = 2;
utf8Stream.CharSet = "utf-8";
utf8Stream.WriteText(data);
utf8Stream.SaveToFile(target, 2);
utf8Stream.Close();
}
catch (e)
{
__DoLog("__Log2File {0} failed: {1}".format(target, e.description));
var oSLNKDWF = new ActiveXObject("SLNKDWF.About");
__DoLog("Perhaps folder does nog exist or {0} does not have modify permissions".format(oSLNKDWF.UserContext));
}
}
}
// timeZone is hier altijd gelijk aan de server timeZone, dus geen conversie nodig
function parseISOString(ISOString) {
var s = ISOString.split(/\D+/); // Split op non-digits
s[6] = Math.round(+s[6] / 10000); // Precisie van de milliseconden van 7 -> 3
return new Date(+s[0], --s[1], +s[2], +s[3], +s[4], +s[5], +s[6]);
}
function unlockChilkat() {
try {
var glob = new ActiveXObject("Chilkat_9_5_0.Global");
} catch (e) {
__DoLog(e.description, "#FF0000");
}
var sql = "SELECT COALESCE(fac_setting_pvalue, fac_setting_default) fac_setting_value"
+ " FROM fac_setting"
+ " WHERE fac_setting_name = 'puo_chilkat_secret'";
var oRs = Oracle.Execute(sql);
var success = glob.UnlockBundle(oRs("fac_setting_value").Value);
oRs.Close();
if (success != 1) {
__DoLog("Chilkat_9_5_0 Unlock failed:\n" + oChilglob.LastErrorText, "#FF0000");
return false;
}
return true;
}
function inArray(needle, haystack) {
var length = haystack.length;
for(var i = 0; i < length; i++) {
if(haystack[i] == needle) return true;
}
return false;
}
String.prototype.format = function()
{
var formatted = this;
for (var i = 0; i < arguments.length; i++)
{
if (typeof arguments[i] == "string")
arguments[i] = arguments[i].replace(/\$/g, '$$$$');
var regexp = new RegExp('\\{'+i+'\\}', 'gi');
formatted = formatted.replace(regexp, arguments[i]);
}
return formatted;
};