<% /*
    $Revision$
    $Id$

    File:        shared.inc
    Description: Generieke functies die door elke pagina gebruikt worden.
    Parameters:
    Context:     Vanuit ELK asp bestand
    Note:        Dus gebruik met mate

*/ %>
<%
var shared = {
      trackaction:
        function (paction, pkey, poms) /* paction is something like 'BEZMUT', 'MLDAFM' etc */
        {   // noot: trackaction genereert ook eventuele notificaties
            var sql = "BEGIN fac.trackaction(" + safe.quoted_sql(paction) + ", " + pkey + ", " + user_key + ", NULL, " + safe.quoted_sql(poms, 2000) + "); END;"
            Oracle.Execute(sql);
            if (paction.substr(0,1) != "#") // # voor '#MLDBEH' betekent 'niet notificeren'
                putorders.sendnotifications(pkey, paction);
        },

      // Keep track of GUI actions, params.daily summarizes per day anonymously
      registeraction:
        function (pgroup, params)
        {
            var counter_key = -1;

            var flds = [];
            var safevals = [];
            flds.push("fac_gui_counter_group");
            safevals.push(safe.quoted_sql(pgroup));
            if (!params.daily)
            {
                flds.push("prs_perslid_key");
                safevals.push(user_key);
            }
            if (params.truncdate || params.daily)
            {
                flds.push("fac_gui_counter_date");
                safevals.push("TRUNC(SYSDATE)");
            }
            if (params.info)
            {
                flds.push("fac_gui_counter_info");
                safevals.push(safe.quoted_sql(params.info));
            }
            if (params.refkey)
            {
                flds.push("fac_gui_counter_refkey");
                safevals.push(params.refkey);
            }
            if (!params.daily)
            {
                var sql = "SELECT faq_s_fac_gui_counter_key.nextval FROM dual";
                var oRs = Oracle.Execute( sql );
                counter_key = oRs(0).value;
                oRs.Close();
                flds.push("fac_gui_counter_key");
                safevals.push(counter_key);

                sql = "BEGIN"
                    + " INSERT INTO fac_gui_counter ("+flds.join(", ")+")"
                    + "    VALUES ("+safevals.join(", ")+");"
                    + " EXCEPTION WHEN dup_val_on_index" // Twee keer in dezelfde seconde negeren
                    + " THEN "
                    + "   NULL; "
                    + " END;"
                Oracle.Execute(sql);
            }
            else
            {
                // upsert the daily counter, relies on the unique index
                // you might think that update-insert is more efficient (because more often) than insert-update
                // JGL: inderdaad, dat is efficienter dus omgedraaid.
                var wheres = [];
                for (var i=0; i<flds.length; i++) {
                    wheres.push(flds[i] + "=" + safevals[i]);
                }
                var sql = "BEGIN"
                        + "  UPDATE fac_gui_counter SET fac_gui_counter_count = fac_gui_counter_count + 1"
                        + "   WHERE ("+ wheres.join(" AND ")+");"
                        + "  IF SQL%ROWCOUNT = 0 THEN"
                        + "       INSERT INTO fac_gui_counter ("+flds.join(", ")+", fac_gui_counter_count)"
                        + "       VALUES ("+safevals.join(", ")+", 1);"
                        + "  END IF;"
                        + " END;";
                Oracle.Execute(sql);
            }
            return counter_key;
        },

      // Verondersteld dat head al is geweest.
      simpel_body: function(bodyhtml)
      {
            Response.Write("<body class='simpelpage'>");
            Response.Write(bodyhtml);
            Response.Write("</body></html>");
            Response.End;
      },

      // Maak een heel simpele pagina.
      // Als DOCTYPE_Disable dan veronderstellen we een JSON-vraag en sturen we JSON terug
      // (Anders zou er waarschijnlijk een Ajax Error optreden)
      simpel_page: function(bodyhtml)
      {
        if (typeof mobile == "object" && 'simpel_page' in mobile)
          mobile.simpel_page(bodyhtml);
        else
          if (typeof DOCTYPE_Disable == "undefined" || !JSON_Result || typeof JSON == "undefined")
          {
              Response.Clear();
              Response.Write("<html class='simpel_page'><head>");
              FCLTHeader.Generate();
              Response.Write("</head>");
              shared.simpel_body(bodyhtml);
          }
          else
          {
              bodyhtml = bodyhtml.replace(/\<br\>/ig, "\n")
                                 .replace(/\<p\>/ig, "\n")
                                 .replace(/\<\/p\>/ig, "\n");
              Response.Clear();
              Response.Write(JSON.stringify({success: false, message: bodyhtml}));
              Response.End;
          }
      },

      internal_error: function (message)
      {
            shared.simpel_page(L("lcl_internal_error") + "<p>" + message + "</p>");
      },

      record_not_found: function (html)
      {
            shared.simpel_page(L("lcl_recnotfound_error") + (html||""));
      },

      // Delay in milliseconde zonder dat het processor-kracht kost
      // Handig als je traag gedrag wilt simuleren
      delay: function (ms)
      {
        var oAbout = new ActiveXObject("SLNKDWF.About");
        oAbout.Sleep(ms);
      },

      // b64 versie levert op volgens rfc4648 zodat de waarde url-safe is
      // geen +/ maar -_
      // Let op: len is het aantal gewenste karakters, dat zal groter zijn dan het aantal bytes
      random: function (len, encoding)
      {
        len = len || 32;
        encoding = encoding || "base64";
        switch (encoding)
        {
            case "base64": bytes = Math.ceil(len * 6 / 8); break;
            case "base32": bytes = Math.ceil(len * 5 / 8); break;
            case "hex":    bytes = len * 4 / 8; break;
            default: BAD_RANDOM_ENCODING;
        }
        try
        {
            var oCrypto = new ActiveXObject("SLNKDWF.Crypto");
            var rnd = oCrypto.hex_random(bytes);
            switch (encoding)
            {
                case "base64": rnd = oCrypto.hex2base64(rnd, false, true); break; // no padding, urlsafe
                case "base32": rnd = oCrypto.hex2base32(rnd); break;
                case "hex":    rnd = rnd; break;
                default: BAD_RANDOM_ENCODING;
            }
        }
        catch(e)
        {   // Minstens versie 4.14 voor random
            abort_with_warning("SLNKDWF.DLL not properly installed or too old version.\n{0}".format(e.description));
        }
        return rnd.substr(0, len);
      },

      isGoodNumber: function(str, intOnly, posOnly, numLen, numDec)
      {
        var posOnlyStr = (posOnly ? "" : "\\-?");
        var numLenStr  = (numLen == -1 ? "" : (numDec == -1 ? numLen : (intOnly ? numLen : numLen - numDec)));
        var numDecStr  = (numDec == -1 ? "" : numDec);

        var intOnlyStr = "\\d{1," + numLenStr + "}";
        var dblOnlyStr = "\\d{0," + numLenStr + "}[.,]\\d{1," + numDecStr + "}" + "|"
                       + "\\d{1," + numLenStr + "}[.,]\\d{0," + numDecStr + "}" ;

        var anum = "^"
                 + posOnlyStr + "(" + intOnlyStr
                                    + (intOnly ? "" : "[.,]?" + "|" + dblOnlyStr)
                              + ")"
                 + "$";

        var patt = new RegExp(anum);
        return patt.test(str);
      },

      // Checken of het eenheden selectveld met dagen/uren bij "Acceptatietijd" niet readonly was en wel is meegegeven.
      // Als dit eenheden selectieveld readonly was dan wordt deze niet opgeslagen en dan moet de tijdsduur ook niet opgeslagen worden.
      // Als 0 dagen/uren dan beide velden leeg maken. Als eenheidsveld aanwezig is, moet ook het tijdsduurveld aangwezig zijn.
      add_time_field: function add_time_field(fields, dbsname, frm_tijd, frm_eenheid, track)
      {
        var dayshours = Request.Form(frm_eenheid).count > 0 && getFParamFloat(frm_tijd, -1) > 0;
        if (dayshours)
          fields.push({ dbs: dbsname + ".tijdsduur", obj: "MLD_T_UITVOERTIJD", typ: "float",   frm: frm_tijd,    track: (track? track.tracktijdsduur : null) },
                      { dbs: dbsname + ".eenheid",   obj: "MLD_T_UITVOERTIJD", typ: "varchar", frm: frm_eenheid, track: (track? track.trackeenheid : null) });
        else // Leegmaken
          fields.push({ dbs: dbsname + ".tijdsduur", obj: "MLD_T_UITVOERTIJD", typ: "float",   val: null, track: (track? track.tracktijdsduur : null) },
                      { dbs: dbsname + ".eenheid",   obj: "MLD_T_UITVOERTIJD", typ: "varchar", val: null, track: (track? track.trackeenheid : null) });
        return fields;
      },
      // Converteer zo veel mogelijk direct naar goede type
      _qssafe: function __qssafe(model, fld, strval)
      {
        if (fld.toLowerCase() == 'id')
            return parseInt(strval, 10);

        return strval; // NOTE: Rest nog even niet, gaf nog problemen met foreigns (FSN#33730)

        var field = model.fields[fld];
        if (!field)
            return strval;
        switch (field.typ)
        {
            case "float":
                return parseFloat(strval.replace(/,/g,"."));
                break;
            case "key":
            case "number":
                return parseInt(strval, 10);
                break;
            case "check":
            case "check0":
                //val = (Request.Form(formfields[i].frm).count==1)?1:0;
                //break;
            case "date":
            case "datetime":
                // Hier zouden we ook de ISO-dates moeten aankunnen?
                //val = getFParamDate(formfields[i].frm, null);
                //break;
            case "memo":
            case "varchar":
                return strval;
                break;
            default:
            {
                return strval;
            }
        }
        return strval;
      },
      qs2json: function _qs2json(model)
      {
          var filter = {};
          for (var i = 1; i <= Request.QueryString.Count; i++)
          {
              var name = Request.QueryString.key(i);
              var data = Request.QueryString(i);
              if (data.Count > 1)
              {
                filter[name] = [];
                for (var j = 1; j <= data.Count; j ++)
                    filter[name].push(shared._qssafe(model, name, String(data(j))));
              }
              else
                filter[name] = shared._qssafe(model, name, String(data));
          }
          return filter;
      },
      stripbbcodes: function (waarde)
      {
          var safepairs = "h1,h2,h3,h4,h5,h6,b,i,u,em,strong,small,big,th,td,tr,table,xmp".split(",");
          for (var i = 0; i < safepairs.length; i++)
          {
              var code = safepairs[i];
              var strre = "\\[{0}\\](.*?)\\[\\/{0}\\]".format(code); // De ? maakt de .* lazy (ipv greedy)
              var strnew = "$1";
              var re = new RegExp(strre, "g");
              var waarde = waarde.replace(re, strnew);
          }
          return waarde;
    }
};

// Check for parameter pName and return the value
// If not specified return defVal
// If defVal not specified pName a required parameter
function getQParam(pName, defVal)
{
    return _get_Param(Request.Querystring, pName, defVal);
}
function getFParam(pName, defVal)
{
    return _get_Param(Request.Form, pName, defVal);
}
function hasQParam(pName)
{
    return Request.Querystring(pName).Count > 0;
}
function hasFParam(pName)
{
    return Request.Form(pName).Count > 0;
}
// Met force wordt een aanwezige maar lege waarde ook afgekeurd
// Met name voor (integer) key's
function _get_Param(pColl, pName, defVal, force)
{
    var rq = pColl(pName);
    if (rq.count > 0 && (!force || rq(1) !== ""))
        return rq(1);
    else
    {
        if (typeof defVal != 'undefined')
            return defVal;
        else // Error message will get to client and/or IIS logfiles
        {
            if (String(Request.ServerVariables("REQUEST_METHOD")) == "HEAD")
            {
                __DoLogForm();
                __DoLog("Parameter '" + pName + "' is missing, probably because of unexpected HEAD request", "#FFFF00");
                __DoLog("Useragent: " + Request.ServerVariables("HTTP_USER_AGENT"));
                __DoLog("Referer: " + Request.ServerVariables("HTTP_REFERER"));
                Response.End;
            }
            else
            {
                __DoLogForm("#FF0000");
                eval("INTERNAL_ERROR_PARAMETER_" + pName + "_IS_MISSING");
            }
        }
    }
}

// Check for parameter pName and return the Integer value
// If not specified return defVal
// If defVal not specified pName is a required parameter
// If relaxed==true then return defVal on invalid number
function getQParamInt(pName, defVal, relaxed)
{
    return _get_ParamInt(Request.Querystring, pName, defVal, relaxed);
}

// only plain ASCII charachters are allowed: a-z, A-Z and digits
function getQParamSafe(pName, defVal)
{
    var txt = _get_Param(Request.Querystring, pName, defVal);
    if (txt.match(/[^a-zA-Z0-9]/))
        eval("INTERNAL_ERROR_PARAMETER_" + pName + "_IS_BAD");
    return txt;
}

// only plain ASCII charachters are allowed: a-z, A-Z and digits
function getFParamSafe(pName, defVal)
{
    var txt = _get_Param(Request.Form, pName, defVal);
    if (txt.match(/[^a-zA-Z0-9]/))
        eval("INTERNAL_ERROR_PARAMETER_" + pName + "_IS_BAD");
    return txt;
}

function getFParamInt(pName, defVal, relaxed)
{
    return _get_ParamInt(Request.Form, pName, defVal, relaxed);
}
function _get_ParamInt(pColl, pName, defVal, relaxed)
{
    var strval = _get_Param(pColl, pName, defVal, true); // force: een lege waarde wordt als afwezig beschouwd
    if (strval == "") return defVal;
    if (strval == "SELF") return user_key;
    if (strval == defVal) return defVal;
    if (relaxed)
        strval = String(strval).replace(/[^0-9]*/, ''); // strip leading non-digits
    if (String(strval).substr(0,2)=='0x')
        var val = parseInt(strval);
    else
        var val = parseInt(strval, 10);
    if (isNaN(val))
    {
        if (relaxed)
            return defVal;
        else
            // Error message will get to client and/or IIS logfiles
            eval("INTERNAL_ERROR_PARAMETER_" + pName + "_IS_NOT_INTEGER");
    }
    else
        return val;
}

// Check for parameter pName and return the Float value
// If not specified return defVal
// If defVal not specified pName is a required parameter
// If relaxed==true then return defVal on invalid number
function getQParamFloat(pName, defVal, relaxed)
{
    return _get_ParamFloat(Request.Querystring, pName, defVal, relaxed)
}
function getFParamFloat(pName, defVal, relaxed)
{
    return _get_ParamFloat(Request.Form, pName, defVal, relaxed)
}
function _get_ParamFloat(pColl, pName, defVal, relaxed)
{
    var strval = _get_Param(pColl, pName, defVal);
    if (strval == "") return defVal;
    if (strval == defVal) return defVal;
    var val = parseFloat(strval.replace(/,/g,"."));
    if (isNaN(val) || !isFinite(val))
    {
        if (relaxed)
            return defVal;
        else
            // Error message will get to client and/or IIS logfiles
            eval("INTERNAL_ERROR_PARAMETER_" + pName + "_IS_NOT_FLOAT");
    }
    else
        return val;
}

// Check for parameter pName and return the Date value
// If not specified return defVal
// If defVal not specified pName is a required parameter
// defVal can be a date object or null
function getQParamDate(pName, defVal)
{
    return _get_ParamDate(Request.Querystring, pName, defVal)
}
function getFParamDate(pName, defVal)
{
    return _get_ParamDate(Request.Form, pName, defVal)
}
function _get_ParamDate(pColl, pName, defVal)
{
    var val = _get_ParamInt(pColl, pName, -1);
    if (val>0)
        return new Date(val);

    if (defVal instanceof Date)
    {
        return defVal;
    }
    if (defVal === null) // bewust triple===
    {
        return null;
    }
    // Error message will get to client and/or IIS logfiles
    if (String(Request.ServerVariables("REQUEST_METHOD")) == "HEAD")
    {
        __DoLog("Parameter '" + pName + "' is not date, probably because of unexpected HEAD request", "#FFFF00");
        __DoLog("Useragent: " + Request.ServerVariables("HTTP_USER_AGENT"));
        __DoLog("Referer: " + Request.ServerVariables("HTTP_REFERER"));
        Response.End;
    }
    eval("INTERNAL_ERROR_PARAMETER_" + pName + "_IS_NOT_DATE");
}

// Check for parameter pName and return all values
// Levert een array op, eventueel leeg
// Controleert dat het Integers zijn
// Ondersteunt beide formaten: &key=1&key=2&key=3&key=4 etc.
//                     en ook: &key=1,2,3,4
function getQParamIntArray(pName, defVal, pAll)
{
    return _get_ParamIntArray(Request.Querystring, pName, defVal, pAll)
}
function getFParamIntArray(pName, defVal, pAll)
{
    return _get_ParamIntArray(Request.Form, pName, defVal, pAll)
}
function _get_ParamIntArray(pColl, pName, defVal, pAll)
{
    var rf = pColl(pName);
    var arr = [];
    if (rf.count > 0 && rf(1) !== "")
    {
        var i;
        for (i=1; i <= rf.count; i++)
        {
            if (rf(i).indexOf(",") >= 0)
            {
                arr = arr.concat(rf(i).split(","));
            }
            else if (rf(i) != "")
            {
                arr.push(rf(i));
            }
        }
        // Nu nog conversie/controle naar integers
        for (i in arr)
        {
            arr[i] = parseInt(arr[i], 10);
            if (isNaN(arr[i]))
            {
                eval("INTERNAL_ERROR_PARAMINTARRAY_" + pName + "_IS_NOT_ALL_INTEGER");
            }
        }
        if (arr.length == 1 && arr[0] == -1 && !pAll)
            arr = []; // *alleen* -1 betekent vaak dat iemand 'alle' gekozen heeft
                      // dat behandelen we effectief als een leeg array
                      // Met pAll=true wordt een array met alleen de waarde -1 wel toegestaan.
    }
    if (arr.length)
        return arr;
    else
    {
        if (typeof defVal != 'undefined')
            return defVal;
        else // Error message will get to client and/or IIS logfiles
            eval("INTERNAL_ERROR_PARAMINTARRAY_" + pName + "_IS_MISSING");
    }
}

// Check for parameter pName and return all values
// Levert een array op, eventueel leeg
// Als de xxxIntxxx variant maar dan zonder controle dat het Integers zijn
// Ondersteunt beide formaten: &key=E&key=I&key=L&key=A etc.
//                     en ook: &key=E,I,L,A
// Bij nosplit wordt alleen de eerste ondersteund
function getQParamArray(pName, defVal, nosplit)
{
    return _get_ParamArray(Request.Querystring, pName, defVal, nosplit)
}
function getFParamArray(pName, defVal, nosplit)
{
    return _get_ParamArray(Request.Form, pName, defVal, nosplit)
}
function _get_ParamArray(pColl, pName, defVal, nosplit)
{
    var rf = pColl(pName);
    if (rf.count > 0 && rf(1) !== "")
    {
        var arr = [];
        var i;
        for (i=1; i <= rf.count; i++)
        {
            if (!nosplit && rf(i).indexOf(",") >= 0)
            {
                arr = arr.concat(rf(i).split(","));
            }
            else
                arr.push(rf(i));
        }
        return arr;
    }
    else
    {
        if (typeof defVal != 'undefined')
            return defVal;
        else // Error message will get to client and/or IIS logfiles
            eval("INTERNAL_ERROR_PARAMARRAY_" + pName + "_IS_MISSING");
    }
}

// Check for parameter pName and return all values
// Levert een array op, eventueel leeg
// Controleert dat het Datums zijn
// Ondersteunt beide formaten: &key=1&key=2&key=3&key=4 etc.
//                     en ook: &key=1,2,3,4
function getQParamDateArray(pName, defVal)
{
    return _get_ParamDateArray(Request.Querystring, pName, defVal)
}
function getFParamDateArray(pName, defVal)
{
    return _get_ParamDateArray(Request.Form, pName, defVal)
}
function _get_ParamDateArray(pColl, pName, defVal)
{
    var rf = pColl(pName);
    if (rf.count > 0)
    {
        var arr = [];
        var i;
        for (i=1; i <= rf.count; i++)
        {
            if (rf(i).indexOf(",") >= 0)
            {
                arr = arr.concat(rf(i).split(","));
            }
            else if (rf(i) != "")
            {
                arr.push(rf(i));
            }
        }
        // Nu nog conversie/controle naar integers
        for (i in arr)
        {
            arr[i] = new Date(parseInt(arr[i], 10));
            if (!(arr[i] instanceof Date || arr[i] === null)) // bewust triple===
            {
              // Error message will get to client and/or IIS logfiles
              eval("INTERNAL_ERROR_PARAMETER_" + pName + "_IS_NOT_DATE");
            }
        }
        return arr;
    }
    else
    {
        if (typeof defVal != 'undefined')
            return defVal;
        else // Error message will get to client and/or IIS logfiles
            eval("INTERNAL_ERROR_PARAMINTARRAY_" + pName + "_IS_MISSING");
    }
}

// Usage: "Wilt U melding {0} afmelden?".format(12345)
// Localscripts/iface.js heeft de clientside versie
String.prototype.format = function()
{
    var formatted = this;
    for (var i = 0; i < arguments.length; i++)
    {
        var regexp = new RegExp('\\{'+i+'\\}', 'gi');
        formatted = formatted.replace(regexp, arguments[i]);
    }
    return formatted;
};


// lijst is een array van parameter namen ["loc_key", "geb_key", "test"]
// params is optioneel een hash { loc_key: 345, geb_key: 34 }
// (zonder params wordt QueryString genomen)
// resultaat: "&loc_key=345&geb_key=34&test=1&test=2"
function buildTransitParam(lijst, params)
{
  var result = "";
  if (typeof lijst == "string")
    lijst = lijst.split(",");
  for (var i = 0; i < lijst.length; i++)
  {
    var itm = lijst[i];
    if (params && params[itm]) // meegegeven
        result = result + "&" + itm + "=" + Server.URLencode(params[itm]);
    else
    {
      var urlparams = Request.QueryString(itm);

      if (urlparams.count > 0)
      { // Waarde is meegegeven, dan ook doorgeven.
        // Voeg elke waarde als aparte string waarde toe.
        for (var j = 1; j <= urlparams.count; j++)
        {
          result += "&" + itm + "=" + Server.URLencode(urlparams(j));
        }
      }
    }
  }
  return result;
}

// Op een XSS veilige manier de complete QueryString door kunnen geven
function transitQS()
{
  var result = "";
  for (var i = 1; i <= Request.QueryString.count; i++)
  {
    var itm = Request.QueryString.key(i);
    var urlparams = Request.QueryString.item(i);

    // Nu de waarde of waarden goed uitlezen.
    // urlparams kan uit meerdere waarden bestaan. Als string staan er dan extra spaties achter de komma's.
    // Voeg elke waarde als aparte string waarde toe.
    for (var j = 1; j <= urlparams.count; j++)
    {
      if (Request.QueryString("hmac").Count > 0)
          var safeitm = itm; // anders wordt een underscore in &mld_key een %5F en klopt de hmac niet meer
      else
          var safeitm = Server.URLencode(itm);
      result += "&" + safeitm + "=" + Server.URLencode(urlparams(j));
    }
  }
  return result;
}

function padout(number) { return (number < 10) ? "0" + number : number; }

// This function should only be used to format display,
// – never as part of a computation.
function toTimeString(jsDate, bWithSeconds)
{
    if (jsDate===null)
        return "";

    if (typeof jsDate == "object" && jsDate.type == 135/*adDBTimeStamp, oRs("datum")*/)
        jsDate = new Date(jsDate.value);
    if (typeof jsDate == "date") // een oRs("datum").value
        jsDate = new Date(jsDate);
    if (typeof jsDate == "number") // 8.5 voor 08:30
    {
        if (jsDate < 0 || jsDate > 24)
            return "";
        var hh = Math.floor(jsDate);
        var mm = (jsDate*60) % 60;
        jsDate = (new Date).setHours(hh,mm,0,0);
    }

    if (!jsDate)
        return "";

    jsDate = new Date(jsDate.valueOf() + 1); // rounding errors

    ret = padout(jsDate.getHours()) + ":" + padout(jsDate.getMinutes());
    if (bWithSeconds) ret += ":" + padout(jsDate.getSeconds());
    return ret;
}
function toDateString(jsDate, noDay, pretty)
{
    var resstr = "";
    var today = new Date().midnight();
    var yesterday = (new Date(today))
    yesterday.setDate(today.getDate()-1);
    var tomorrow = (new Date(today));
    tomorrow.setDate(today.getDate()+1);

    if (jsDate===null)
        return "";

    if (typeof jsDate == "object" && jsDate.type == 135/*adDBTimeStamp, oRs("datum")*/)
        jsDate = new Date(jsDate.value);

    if (typeof jsDate == "date") // een oRs("datum").value
        jsDate = new Date(jsDate);

    if (!jsDate)
        return "";

    // Ik wil graag, alleen als parameter friendly?, voor de datums gisteren, vandaag en morgen de tekst Gisteren, Vandaag en Morgen opleveren
    // Voor lijsten/sortering is dat vaak niet handig, maar soms is het veel begrijpelijker. Als de mogelijkheid er is, kunnen we
    // geleidelijk de toepassing uitbreiden. In Lopende zaken op de portal om mee te beginnen.

    if (pretty && jsDate.midnight().getTime() == today.getTime())
        resstr = L("lcl_date_today");
    else if (pretty && jsDate.midnight().getTime() == yesterday.getTime())
        resstr = L("lcl_date_yesterday");
    else if (pretty && jsDate.midnight().getTime() == tomorrow.getTime())
        resstr = L("lcl_date_tomorrow");
    else
        resstr = (noDay?"":calendar_names.daysMin[jsDate.getDay()] + " ") +
               padout(jsDate.getDate()) + "-" + padout(jsDate.getMonth() + 1) + "-" + padout(jsDate.getFullYear());

    return resstr;
}
function toDateTimeString(jsDate, bWithSeconds, noDay, prettyday)
{
    if (jsDate===null)
        return "";

    if (typeof jsDate == "object" && jsDate.type == 135/*adDBTimeStamp, oRs("datum")*/)
        jsDate = new Date(jsDate.value);

    if (typeof jsDate == "date") // een oRs("datum").value
        jsDate = new Date(jsDate);

    return toDateString(jsDate, noDay, prettyday) + " " + toTimeString(jsDate, bWithSeconds)
}

// Zie document"z:\Project\Sm44\DOC\Intern\Facilitor5i\Facilitor 5i Quotes en Escape.doc"
safe = {

    // getal 0 blijft 0
    nvl: function (x)
    {
        if (x == null)
            return "";
        else
            return x;
    },
    // Bijvoorbeeld <textarea>< % =safe.textarea(waarde)% ></textarea>
    textarea: function(waarde)
    {
        return Server.HTMLEncode(safe.nvl(waarde));
    },
    // Bijvoorbeeld <div class='fclthtml'>< % =safe.fclthtml(omschr)% ></div>
    // We ondersteunen bb-codes die 1-op-1 naar html zijn om te zetten
    // Let op: deze functie lijkt veel op default.xsl/safe.fclthtml
    //         Pas die eventueel ook aan
    fclthtml: function (waarde, mldlink)
    {
        var safepairs = "h1,h2,h3,h4,h5,h6,b,i,u,em,strong,small,big,th,td,tr,table,pre,code".split(",");
        var safehtml = safe.html(waarde); // Let op: vervangt *alle* \n door <br>. Dat gaan we deels later nog terugdraaien
                                          // We hebben het wel *nodig* omdat .* niet over meerdere regels werkt (hoewel dat
                                          // weer oplosbaar schijnt te zijn met [\s\S])
        for (var i = 0; i < safepairs.length; i++)
        {
            var code = safepairs[i];
            var strre = "\\[{0}\\](.*?)\\[\\/{0}\\]".format(code); // De ? maakt de .* lazy (ipv greedy)
            var strnew = "<" + code + ">$1</" + code + ">";
            var re = new RegExp(strre, "g");
            var safehtml = safehtml.replace(re, strnew);
        }
        if (mldlink)
            var safehtml = safe.mldlinkhtml(safehtml);

        // Nu de <br>'s binnen de tabel terugzetten. Let op dat ze *binnen* <td>'s weer wel moeten blijven
        // TODO: Kan dit beter?
        if (safehtml.indexOf("<table>") > -1)
        {
            var tablepairs = "th,td,tr,table".split(",");
            for (var i = 0; i < tablepairs.length; i++)
            {
                var code = tablepairs[i];
                var re = new RegExp("<{0}>(\r<br>)+".format(code), "g");
                var safehtml = safehtml.replace(re, "<" + code + ">");
                var re = new RegExp("</{0}>(\r<br>)+".format(code), "g");
                var safehtml = safehtml.replace(re, "</" + code + ">");
            }
        }

        // Nu alle <br>'s binnen <pre> terugzetten
        var matches = safehtml.match(/<pre(?:.*?)>(?:.*?)<\/pre>/g);
        if (!matches)
            matches = safehtml.match(/<code(?:.*?)>(?:.*?)<\/code>/g);
        if (matches)
        {
            var len = matches.length;
            var i;
            for (i = 0; i < len; i++)
            {
                var pre = matches[i];
                pre = pre.replace(/<br(?:.*?)>/g, '\n');
                safehtml = safehtml.replace(matches[i], pre);
            }
        }
        return safehtml;
    },
    mldlinkhtml: function (safehtml)
    {
        if (!S("mld_autolink_regexp")) /// (?:(?:melding [A-Z]*)|(?:[A-Z]*\#[A-Z]*))(\d*)
            return safehtml;

        var safehtml = safehtml.replace(new RegExp(S("mld_autolink_regexp"), "g"),
            "<span class='details' onclick='FcltMgr.openDetail(&quot;appl/mld/mld_melding.asp?mld_key=$1&amp;&quot;, { reuse: true })'>$&</span>")

        return safehtml;
    },
    // Bijvoorbeeld <div>< % =safe.html(omschr)% ></div>
    html: function (waarde)
    {
        return Server.HTMLEncode(safe.nvl(waarde)).replace(/\n/g, "<br>");
    },
    // bijvoorbeeld <input value="< % =safe.htmlattr(waarde) % >">
    htmlattr: function (waarde)
    {
        return Server.HTMLEncode(safe.nvl(waarde)).replace(/\'/g,'&#39;').replace(/\"/g,'&#34;');  // ' syntax highlight correctie
    },
    // bijvoorbeeld queuemail.asp?subject=" + safe.url(subject)
    url: function (waarde)
    {
        return Server.URLencode(safe.nvl(waarde));
    },
    // Altijd quotes er om, we verdubbelen interne
    // CR+LF binnen een waarde maken we alleen LF van
    csv: function (waarde)
    {
        if (waarde==null)
            return "";
        return "\"" + String(waarde).replace(/\x0D\x0A/g,'\x0A').replace(/\"/g,'""') + "\"";  // " syntax highlight correctie
    },
    // Levert een getal op als string met een komma of een punt, afhankelijk van de
    // decimaal scheider die de gebruiker op zijn eigen PC heeft ingesteld
    // Merk op: géén 1000-separator, dat is te lastig
    // Te gebruiken voor readonly presentatie van bedragen
    displayfloat: function (fnum, decimals, trimZeros)
    {
        if (fnum == null)
            return "";

        if (typeof decimals == "undefined")
            decimals = 2;
        fnum = fnum.toFixed(decimals);
        var waarde2 = (1.5).toLocaleString(); // Eventueel gewenste komma, dank zij LCID gezet in appl\FAC\facilitor.inc
        fnum = fnum.substring(0, fnum.length - decimals - 1) + waarde2.substring(2, 1) + fnum.substring(fnum.length - decimals);
        if (trimZeros)
        {
            while (fnum.substring(fnum.length-1) == "0")
                fnum = fnum.substring(0, fnum.length-1);
            if (fnum.substring(fnum.length-1) == waarde2.substring(2, 1))
                fnum = fnum.substring(0, fnum.length-1);
        }
        return fnum;
    },
    // luxe variant: gewenste punt/komma en ook duizendscheiders
    curr: function (waarde, nocents)
    {
        if (waarde == null)
            return "";
        if (waarde && !isNaN(waarde))
            waarde = waarde.toLocaleString();
        if (waarde && nocents)
            waarde = waarde.substring(0, waarde.length-3);
        return waarde;
    },
    // Editable willen we er echt geen duizend-scheiders in hebben
    // Die zijn namelijk heel moeilijk terug te converteren naar number
    // Te gebruiken voor bewerkbare velden met bedragen
    editablefloat: function(fnum, decimals)
    {
        return safe.displayfloat(fnum, decimals); // die heeft toch geen duizend-scheiders
    },
    curreditable: function (waarde)
    {
        return safe.editablefloat(waarde, 2);
    },
    // FcltMgr.alert("< % =safe.jsstring(waarde) % >")
    // </script> is ook funest!
    jsstring: function (waarde)
    {
        return (safe.nvl(waarde)).replace(/\\/g,'\\\\').replace(/\n/g,'\\n')
                           .replace(/\r/g,'').replace(/\'/g,'\\x27').replace(/\"/g,'\\x22')
                           .replace(/\</g, '\\x3C').replace(/\>/g, '\\x3E'); // ' syntax highlight correctie
    },
    // var S("res_t1")=< % =safe.jsfloat(S("res_t1"))% >
    jsfloat: function (fnum)
    {
         return(String(fnum)).replace(',','.');
    },
    quoted_sql: function (tekst, maxlen) // maxlen is optioneel
    {
        if (tekst == null)
            return "NULL";
        if (!maxlen)
            maxlen = 4000;
        // Haal alle lage bytes (muv TAB (x09), LF (x0A) en CR (x0D)) er uit. Die representeren
        // geen geldig karakter in Windows-1252 of iso-8859. Ook in UTF-8 staan ze niet voor
        // tekst, mochten we dat ooit willen gaan ondersteunen.
        // Voor ultieme veiligheid ook maar de hoge unicodes er uit? Neen, EURO is ten slotte
        // ook een 'hoge' unicode
        tekst = tekst.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]+/g, "?");
        tekst = tekst.substr(0, maxlen);
        // FSN#35288: '{' naar CHR(123) voorkomt Unspecified Error
        return "'" + tekst.replace(/\'/g,"''").replace(/\{/g,"'||CHR(123)||'") + "'"; // " syntax highlight correctie
    },
    qL: function (p_lcl, params)
    {
        return safe.quoted_sql(L(p_lcl, params));
    },

    quoted_sql_join: function (tekstarr, toupper) // Vooral voor IN () clausules met tekstwaardes
    {
        if (!tekstarr || !tekstarr.length)
            return "NULL";

        var safearr = [];
        for (var i = 0; i < tekstarr.length; i++)
        {
            safearr.push(toupper? safe.quoted_sql_upper(tekstarr[i]) : safe.quoted_sql(tekstarr[i]))
        }
        return safearr.join(", ");
    },

    quoted_sql_upper: function (tekst, maxlen)
    {
        if (tekst == null)
            return "NULL";
        return safe.quoted_sql(tekst.toUpperCase(), maxlen);
    },

    quoted_sql_wild: function (tekst, maxlen)
    {
        if (tekst == null)
            return "NULL";
        return safe.quoted_sql(tekst.toUpperCase().replace(/\*/g, "%"), maxlen);
    },
    // converteer alle waarden in arr naar integer.
    // waar dat niet lukt worden ze gewoon verwijderd(?)
    int_array: function (arr)
    {
        var safe_arr = [];
        for (var i = 0; i < arr.length; i++)
        {
            var v = parseInt(arr[i], 10);
            if (!isNaN(v))
                safe_arr.push(v);
        }
        return safe_arr;
    },

    filename: function (naam) // geen 'lage' karakters en geen (back)slashes, *,%,<,>, '"', ':', ';' '?' and '|' of '+'
    {
        return naam.replace(/[\x00-\x1F|\/|\\|\*|\%\<\>\"\:\;\?\|\+]+/g, "_"); // " syntax highlight correctie
    },

    UserContext: function ()
    {
        try
        {
            var _oAbout = Server.CreateObject('SLNKDWF.About');
            return "UserContext: " + _oAbout.UserContext;
        }
        catch (e)
        {
            return "UserContext: no SLNKDWF.About " + e.description;
        }
    }
}

function abort_with_warning(warning, code)
{
    if (Request.QueryString("api2").Count > 0)
    {
        code = code || 500;
        var data = { error: { code: code,
                              message: warning
                            }
                   };
        var format = Request.QueryString("format");
        if (format == "xml")
        {
            Response.ContentType = "text/xml";
            var xml_antwoord = api2_rest.json2xml([data.error], { records_name: "errors", record_name: "error"}, true);
            Response.Write(xml_antwoord.xml);
        }
        else if (format == "html")
        {
            Response.ContentType = "text/html";
            var str_antwoord = "<!DOCTYPE html><html><head></head><body><pre>"
                             +      Server.HTMLEncode(JSON.stringify(data, null, 2))
                             + "</pre></body></html>";
            Response.Write(str_antwoord);
        }
        else
        {
            Response.ContentType = "application/json";
            Response.Write(JSON.stringify(data, null, 2));
        }
        Response.Status = code;
        Response.End;
    }
    else if (JSON_Result && JSON)
    {
      Response.Write(JSON.stringify({ warning: warning, keepForm: true }));
    }
    else
    {
    %>
    <html>
      <head>
        <%  FCLTHeader.Generate() %>
        <script type='text/javascript'>
            FcltMgr.closeDetail(window, { warning: "<%=safe.jsstring(warning)%>",
                                          keepForm: true
                                        });
        </script>
      </head>
    <body>
    </body>
    </html>
    <%
    }
    Response.End
}

var protectHMAC =
{
    // Alleen geschikt als tijdelijk geheim, doorgaans wordt
    // IIS elke nacht gerecycled en dan zijn we deze kwijt
    getProtectSecret: function ()
    {
      var appsecret = Application(customerId + "_protectsecret");
      if (typeof appsecret == "undefined")
      {
        appsecret = shared.random(32);
        Application.Lock();
        Application(customerId + "_protectsecret") = appsecret;
        Application.UnLock();
      }
      return appsecret;
    },

    create: function(str, params)
    {
        params = params || {};
        params.sleutel = params.sleutel || protectHMAC.getProtectSecret();

        var ts = String(Math.round((new Date).getTime() / 1000));
        var data = ts + ":" + str
        __Log("hmacdata: " + data);
        //__Log("sleutel: "+ params.sleutel);
        var oCrypto = new ActiveXObject("SLNKDWF.Crypto");
        var sig = oCrypto.hex_hmac_sha1(params.sleutel, data);
        var hmac = oCrypto.hex2base64(sig, false, true); // no padding, urlsafe
        oCrypto = null; // Caching klinkt leuk maar Oracle sessies blijven langer hangen?
        return ts+":"+hmac;
    },

    verify: function (str, hmac, params)
    {
        params = params || {};
        params.expire = params.expire || 720;
        var expTime = new Date;
        expTime.setMinutes(expTime.getMinutes()-params.expire); // max expire in het verleden
        var future = new Date;
        future.setMinutes(future.getMinutes() + 3); // Over 3 minuten begint de toekomst :-)
                                                    // Voorkomt misbruik tijdens zomer/wintertijd wissel
        var hmacArr = hmac.split(":");
        var createTimeInt = parseInt(hmacArr[0],10);
        var createTime = new Date(createTimeInt*1000);
        if (!createTimeInt || !createTime || createTime < expTime || createTime > future)
        {
            createTime.setMinutes(createTime.getMinutes() + params.expire);
            __Log("HMAC: Te laat: je had voor {0} moeten zijn.".format(toDateTimeString(createTime, true)));
            if (params.relaxed)
                return false;
            else
            {
                __DoLog("HMAC: Te laat: je had voor {0} moeten zijn.".format(toDateTimeString(createTime, true)));
                shared.simpel_page(L("lcl_hmac_late"));
            }
        }

        __Log("testing hmacdata: " + str);
        params.sleutel = params.sleutel || protectHMAC.getProtectSecret();
        //__Log("testing sleutel: "+ params.sleutel);
        var oCrypto = new ActiveXObject("SLNKDWF.Crypto");
        var sig = oCrypto.hex_hmac_sha1(params.sleutel, hmacArr[0] + ":" + str);
        var should_hmac = oCrypto.hex2base64(sig, false, true); // no padding, urlsafe
        oCrypto = null;

        if (hmacArr[1] != should_hmac)
        {
            __Log("str: " + str);
            __Log("hmac: " + hmac);
            __Log("HMAC[1]: " + hmacArr[1]);
            __Log("SHOULD: " + should_hmac);
            if (params.relaxed)
            {
                __Log("(relaxed==true, will try hmac again with user_key==-1)");
                return false;
            }
            else
                INTERNAL_ERROR_HMAC_TAMPERING;
        }
        return true;
    }
}

var protectQS =
{
    // Bescherm de url tegen tampering door een hmac achteraan toe te voegen
    // Te controleren aan de ontvangende kant met protectQS.verify()
    // PAS OP: protectQS.wsc bevat voor Facmgt nog (even) hetzelfde algoritme
    create: function(url, params)
    {
        params = params || {};

        var splitter = url.split("?");
        var pad = splitter[0];
        var qs = splitter.length>1?splitter[1]:"x=x";
        var padsplitter = pad.split("/");
        var file = padsplitter[padsplitter.length-1]; // laatste component
        // Let op dat protectQS.wsc hetzelfde doet voor Facmgt
        var data = (params.no_user_key?"":(user_key + ":")) + file.toUpperCase() + "?" + qs;
        var hmacced = protectHMAC.create(data, params);
        return pad + "?" + qs + "&hmac="+Server.URLencode(hmacced);
    },

    // params: expire: in minuten. Default 720, een heel kritische functie kan zichzelf scherper beschermen
    //         allowparams []: bijv. 'outputmode'. Deze worden er voor de HMAC berekening uitgestript
    //         relaxed: doe niet al te moeilijk over errors (maar wel false)
    verify: function (params)
    {
        params = params || {};

        if (params.relaxed && Request.QueryString("hmac").Count == 0)
            return false;

        // vanuit API2 geven we checkqs en checkpath mee omdat die door IIS Rewrite
        // te veel veranderd zijn
        var checkurl = params.checkqs || String(Request.QueryString);

        if (params.allowparams)
        {
            var i;
            for (i in params.allowparams)
            {
                var param = params.allowparams[i];
                if (Request.QueryString(param).Count > 0)
                {    // die rekenen we niet mee in de HMAC
                    checkurl = checkurl.replace(param + "=" + Request.QueryString(param) + "&", "");
                    checkurl = checkurl.replace("&" + param + "=" + escape(Request.QueryString(param)), "");
                }
            }
        }
        var hmac = getQParam("hmac");
        var hmacArr = hmac.split(":");
        var indexHmac = String(checkurl).indexOf("&hmac=");

        if (checkurl.substring(indexHmac+1).indexOf("&") >=0)
        {
            if (params.relaxed)
                return false;
            else
                INTERNAL_ERROR_HMAC_URL_EXTENSION;
        }

        var qs = checkurl.substring(0,indexHmac);

        var pad = params.checkpath || String(Request.ServerVariables("SCRIPT_NAME"));
        var padsplitter = pad.split("/");
        var file = padsplitter[padsplitter.length-1]; // laatste component

        // Let op dat create dezelfde data hasht
        var data = user_key + ":" + file.toUpperCase() + "?" +qs;
        //__Log("testing hmacdata: " + data);
        //__Log("testing sleutel: "+ params.sleutel);
        if (params.allow_anonymous)
            params.relaxed = true;
        var is_ok = protectHMAC.verify(data, getQParam("hmac"), params); // klapt er uit bij hmac-tampering en relaxed==false
        if (!is_ok && params.allow_anonymous)
        {   // Testen met user_key == -1
            var data = "-1" + ":" + file.toUpperCase() + "?" +qs;
            params.relaxed = false; // Nu moet hij echt kloppen
            protectHMAC.verify(data, getQParam("hmac"), params);
        }
        return is_ok;
    }
}

// http://www.owasp.org/index.php/Session_Fixation_Protection
function setASPFIXATION()
{
    var FACSESSIONID = shared.random(32); // genereer grote random string.
    var ASPFIXATION = Session("customerId") + FACSESSIONID;
    Response.Cookies("ASPFIXATION") = ASPFIXATION; // deze controleren we weer in default.inc
    Response.Cookies("ASPFIXATION").Path = rooturl + "/"; // anders niet met ServerXMLHttp
    if (S("auto_https") & 2)
        Response.Cookies("ASPFIXATION").Secure= true;
    Session("ASPFIXATION") = ASPFIXATION; // deze controleren we weer in default.inc
    Session("FACSESSIONID") = FACSESSIONID;
}

var protectRequest =
{
    theToken: function () { return Session("ASPFIXATION") }, // Session ASPFIXATION token wordt gebruikt als cookie voor anti CSRF Cross Site
    theVar: "__RequestVerificationToken", // De form-name. Komt ook terug in FacmgtTools.asp
    inputToken: function () // Maak een hidden inputveld met token
    {
        %>
        <input name="<%=protectRequest.theVar%>" id="<%=protectRequest.theVar%>" type="hidden" value="<%=safe.htmlattr(protectRequest.theToken())%>">
        <%
    },
    // Van dataToken is ook een clientside variant in iface.js
    dataToken: function (dataName) // Voeg aan een data hash een input token toe
    {   %>
        <%=dataName%>.<%=protectRequest.theVar%> = "<%=safe.jsstring(protectRequest.theToken())%>";
        <%
    },

    validateToken: function (externtoken)
    { // De token van het hidden inputveld valideren met de token van de cookie.
      // Voorkeur gaat uit naar het gebruik van getFParam(), maar in Multipart/form-data zijn deze niet beschikbaar.
      // De form-parameters worden in upload.inc alsnog uitgelezen, zodat het token aan deze functie meegegeven kan worden.
      try // API's hebben vaak inputXML.load(Request); gedaan en dan werkt getFParam niet meer
      {
        var verificationToken = (externtoken ? externtoken : getFParam(protectRequest.theVar, ""));
      }
      catch (e)
      { // API's die buildInsert of buildUpdate doen moeten daar vaak { noValidateToken: true } bij doen
        __DoLog("INTERNAL_ERROR_TOKEN_VALIDATIE", "#FFFF00");
        INTERNAL_ERROR_TOKEN_VALIDATIE;
      };
      var cookieToken = protectRequest.theToken()||""; // is leeg bij self_register.asp als we nog niet zijn ingelogd.

      if (verificationToken != cookieToken)
      { // Is deze functie vanuit een post aangeroepen? Dan afhandeling door post functie af laten handelen.

        if (typeof DOCTYPE_Disable != "undefined" && DOCTYPE_Disable == 1 && typeof JSON != "undefined")
        {
          var result = {message: L("lcl_authentication_error")};
          Response.Write(JSON.stringify(result));
          Response.End;
        }
        else
            {
          abort_with_warning(L("lcl_authentication_error"));
          }
      }
    }
}

var cache = {
    whenTrue: function _whenTrue(b)
    {
        if (b)
        {
            var eTag = '"' + user.lang() + "_" + S("cache_changecounter") + "_" + Session("ASPFIXATION") + '"'; // Hoogstens zo lang je ingelogd bent
            Response.AddHeader("ETag", eTag);
            Response.AddHeader("Last-Modified", new Date().toGMTString());
            Response.CacheControl = "private";
            if (Request.ServerVariables("HTTP_IF_NONE_MATCH") == eTag)
            {  // We hebben een match!
                var tm = "";
                if (Request.ServerVariables("HTTP_IF_MODIFIED_SINCE").Count)
                    tm = " (" + toDateTimeString(new Date(Request.ServerVariables("HTTP_IF_MODIFIED_SINCE")), true) + ")";
                __Log("Geslaagde caching" + tm + ": we geven status 304 terug en stoppen.");
                Response.Clear();
                Response.Status = "304 Not modified";
                Response.End;
            }
        }
    },

    whenNoRecords: function _whenNorecords(sql)
    {
        var oRs = Oracle.Execute(sql);
        this.whenTrue(oRs.Eof);
        oRs.Close();
    }
}
// Datum functies
Date.prototype.toSQL = function toSQL(withTime)
{
    var year = this.getFullYear();
    if (year < 1000 || year > 9999)
        shared.internal_error("Invalid year " + this.toLocaleString()); // Net iets minder erg dan ORA-01830

    var str = padout(this.getDate()) + "-" + padout(this.getMonth() + 1) + "-" + padout(this.getFullYear())
    if (withTime)
    {
        str += " " + padout(this.getHours()) + ":" + padout(this.getMinutes());
        return "to_date(" + safe.quoted_sql(str) + ", 'DD-MM-YYYY HH24:MI')";
    }
    else
        return "to_date(" + safe.quoted_sql(str) + ", 'DD-MM-YYYY')";
}

Date.prototype.beginToSQL = function beginToSQL()
{
    var theDateStart = new Date(this);
    theDateStart.setHours(0, 0);
    return theDateStart.toSQL(true);
}

Date.prototype.endToSQL = function endToSQL()
{
    var theDateEnd = new Date(this);
    theDateEnd.setHours(23, 59);
    return theDateEnd.toSQL(true);
}

Date.prototype.midnight = function midnight()
{
    var midn = new Date(this);
    midn.setHours(0,0,0,0);
    return midn;
}

function inArray(needle, haystack) {
    var length = haystack.length;
    for(var i = 0; i < length; i++) {
        if(haystack[i] == needle) return true;
    }
    return false;
}

// Bepaalt de customerId (en rooturl)
// Na afloop is de globale variabele customerId gezet en opgeslagen in Session("customerId")
function determineCustomerId()
{
    var validCustId = /[A-Z0-9]/;
    // autodetect rooturl. Al onze pagina's beginnen met /cust of met /appl
    var pad = String(Request.ServerVariables("PATH_INFO"));
    var lowerpad = pad.toLowerCase();
    var i1 = lowerpad.indexOf("/cust/");
    if (i1 < 0)
        i1 = lowerpad.indexOf("/appl/");
    if (i1 < 0)
        i1 = lowerpad.indexOf("/default.asp"); // API's
    /* global */ rooturl = pad.substr(0, i1);

    var trycust = "";
    if ( typeof Session("customerId") == "undefined" ) // ASP-Sessie verlopen of nieuw binnen.
    {
        if (Request.QueryString("api").Count>0)
        {
            Session.Abandon(); // Voorkom dat een session ontstaat doordat we een API-call doen.
            // When the Abandon method is called, the current Session object is queued for deletion
            // but is not actually deleted until all of the script commands on the current page have
            // been processed. This means that you can access variables stored in the Session object
            // on the same page as the call to the Abandon method but not in any subsequent Web pages.
        }

        // is var customerId al gezet door /default.asp?
        if (typeof customerId != 'undefined' && customerId != "XXXX")
            trycust = customerId.toUpperCase();
        else if (String(Request.Cookies("fcltcust"))) // Remember me knop
            trycust = String(Request.Cookies("fcltcust"));
        else
        {   // URL formaat http://xxxx.5iwork/facilitor5iwork: pak eerste 4 letters
            var host = String(Request.ServerVariables("HTTP_HOST"));
            trycust = host.substring(0,4).toUpperCase();
            var fso = Server.CreateObject("Scripting.FileSystemObject");
            if (!fso.FileExists(Server.MapPath(rooturl + "/cust/" + trycust + "/Oracle.udl")))
            {
                trycust = host.split(".")[0].toUpperCase(); // Misschien hele prefix (C2930)
                if (!fso.FileExists(Server.MapPath(rooturl + "/cust/" + trycust + "/Oracle.udl")))
                    trycust = null;
            }
        }
        // TODO: is er maar één cust-folder met een Oracle.udl? Gebruik die
    }

    // Deze overruled alles:
    if (getQParamSafe("fac_id","") != "")
        var trycust = getQParamSafe("fac_id").toUpperCase();

    if (trycust && validCustId.test(trycust)) // Geen vreemde karakters en er is een udl?
    {
        var fso = Server.CreateObject("Scripting.FileSystemObject");
        if (fso.FileExists(Server.MapPath(rooturl + "/cust/" + trycust + "/Oracle.udl")))
            Session("customerId") = trycust;
        else
        {
            Response.write("Invalid customerId: " + trycust);
            Response.end;
        }
    }

    /* global */ customerId = Session("customerId");

    if (typeof customerId == "undefined")// Ik geef het op.
    {
        if (String(Request.ServerVariables("HTTP_ACCEPT")).match(/application\/json/) != null)
        {
            var msg = "Your session has expired.\nPlease restart FACILITOR."; // L("xxx") vertrouw ik ook niet meer
            Response.Write("FCLTExpired:" + msg); // FCLTExpired wordt zo opgepikt door FcltJquery.js ajaxSetup error handler
            Response.End;
        }
        Response.Redirect(rooturl + "/appl/shared/expired.asp");
    }

    if (!validCustId.test(customerId)) // paranoia
    {
        INTERNAL_ERROR_BAD_CUSTOMERID;
    }
}

// Merk op dat we een beetje browser capabilities en device capabilities
// op één hoop gooien. Goed genoeg vooralsnog
device =
    {   // lower 8 bits
        isSupported: 0x0001,
        isDesktop  : 0x0002,
        isTouch    : 0x0004,
        isMobile   : 0x0008,
        // next 8 bits
        canPrint   : 0x0100, // Show print buttons
        canExcel   : 0x0200, // Show Excel buttons
        canUpload  : 0x0400, // Can upload files
        canDownload: 0x0800, // Can download files
        // more
        supportsPlaceholder: 0x1000, // Support placeholder attribute of input

        _devicebits: 0xff01, // Default desktop, alle capabilities

        init: function ()
        {
            if (typeof Session("devicebits") != "undefined")
                this._devicebits = Session("devicebits")
            else
                Session("devicebits") = this._devicebits;
        },
        test: function (bits)
        {
            return ( (this._devicebits & bits) == bits);
        },
        testAll: function (bits)
        {
            return test(bits);
        },
        testAny: function (bits)
        {
            return ( (this._devicebits & bits) != 0);
        },
        set: function (bits, newval)
        {
            if (newval || typeof newval == "undefined")
                this._devicebits = this._devicebits | bits;
            else // reset
                this._devicebits = this._devicebits & (~bits);
            Session("devicebits") = this._devicebits;
        }
    }

myJSON =
{
    internal_parsedate: function (key, value)
    {
        var a;
        if (typeof value === 'string') {
            a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
            if (a) {
                return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6]));
            }
        }
        return value;
    },
    // myJSON.parse doet hetzelfde als JSON.parse maar herkent automatisch datum variabelen
    parse: function _parse(text)
    {
        return JSON.parse(text, myJSON.internal_parsedate);
    }
}

HTTP =
{   // params: type, data, headers, APIKEY (voor API2)
    getJSON: function _getJSON(url, params) // Serverside variant van jQuery $.getJSON
    {
        params = params || {};
        if (!params.type)
            params.type = params.data?"POST":"GET"; // Met data default POST, zonder data default GET

        var http_request = Server.CreateObject("WinHTTP.WinHTTPRequest.5.1");

        var vraag = JSON.stringify(params.data, null);
        __Log("HTTP url: " + url);
        if (vraag)
            __Log("data: " + vraag);
        http_request.open(params.type, url, false); // Synchroon
        // Het volgende lijkt weinig effect te hebben, post-body is nog steeds utf-8?
        // in api.inc/RequestJSON verwachten we dan ook maar utf-8 dus netto werkt het goed?
        http_request.setRequestHeader("Content-Type", "application/json; charset=windows-1252");
        if (params.APIKEY)
            http_request.setRequestHeader("X-FACILITOR-API-Key", params.APIKEY);
        for ( var i in params.headers )
        {
			http_request.setRequestHeader(i, params.headers[ i ]);
		}
        // Het mag niet te lang duren!
        var lResolve = 5 * 1000;
        var lConnect = 5 * 1000;
        var lSend = 15 * 1000;
        var lReceive = 15 * 1000;
        http_request.setTimeouts(lResolve, lConnect, lSend, lReceive);
        var errtxt = "";
        var errnum = 0;
        try
        {
            http_request.send(vraag);
        }
        catch (e)
        {
            errtxt = e.description;
            errnum = e.number;
        }
        if (!errtxt && http_request.status != 200)
        {
            // Hier HTML lijkt me misplaatst
            errtxt = "HTTP status " + http_request.status
                   + ": " + http_request.responseText;
        }
        if (errtxt)
        {
            //__DoLog(" url: " + url, "#FF0000");
            //__DoLog(" data: " + vraag);
            if (errnum == -2147012894) // "The operation timed out", dan is http_request niet te benaderen
                __DoLog(" http_request {0} mislukt: {1}".format(url, errtxt), "#FF0000");
            else if (http_request.status == 404)
                __Log(" http_request status = 404 {0}".format(url));
            else if (http_request.status == 401)
                __DoLog(" http_request status = 401 {0}".format(url));
            else
                __DoLog(" http_request {0} mislukt: {1}".format(url, errtxt), "#FF0000");
        }
        else
        {
            __Log(http_request.ResponseText);
            try
            {
                var result = myJSON.parse(http_request.ResponseText)
            }
            catch (e)
            {
                __DoLog("Parse error: " + e.message);
                __DoLog(http_request.ResponseText);
                result = { success: false, message: e.message };
            }
            return result;
        }
    },
    urlzelf: function ()
    {
        return HTTP.urlzelfnoroot() + rooturl;
    },
    urlzelfnoroot: function ()
    {
        var proto = (Request.ServerVariables("SERVER_PORT") == "443")? "https" : "http";
        var sitenoroot = proto + "://" + Request.ServerVariables("SERVER_NAME");
        return sitenoroot;
    }
}

// Alleen nog maar ipv4
// http://stackoverflow.com/a/503238/569090
IP =
{
    IPnumber: function(IPaddress) {
        var ip = IPaddress.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/);
        if(ip) {
            return (+ip[1]<<24) + (+ip[2]<<16) + (+ip[3]<<8) + (+ip[4]);
        }
        // else ... ?
        __DoLog("Invalid IPAddress: " + IPaddress);
        INTERNAL_ERROR_INVALID_IP;
    },

    IPmask: function (maskSize) {
        return -1<<(32-maskSize)
    },

    inSubnet: function(ip, subnet)
    {
        var long_ip = IP.IPnumber(ip);
        var bits = 32;
        if (subnet.indexOf("/") > 0)
        {
            bits = parseInt(subnet.split("/")[1], 10);
            subnet = subnet.split("/")[0];
        }
        var mask = IP.IPmask(bits);
        var long_subnet = IP.IPnumber(subnet);

        return (long_ip & mask) == (long_subnet & mask);
    },

    // subnets gescheiden door komma's
    inAnySubnet: function(ip, subnets)
    {
        if (!subnets)
            INTERNAL_ERROR_NOSUBNETS; // 'k weet niet of default veilig of onveilig gewenst is
        var arr = String(subnets).split(",");
        for (var i = 0; i < arr.length; i++)
        {
            if (IP.inSubnet(ip, arr[i]))
                return true;
        }
        return false;
    }

}
%>
