From 875ebe8c4aaf958b4fbb579b0f186ad5fae4e166 Mon Sep 17 00:00:00 2001
From: "S.Listl" <S.Listl@SLISTL.aditosoftware.local>
Date: Tue, 28 May 2019 15:04:40 +0200
Subject: [PATCH] Document Template placeholder

---
 .../DocuTemplatePlaceholder_Keywords.xml      |  57 ++
 process/DocumentTemplate_lib/process.js       | 585 +++++++-----------
 process/Email_lib/process.js                  |   9 +-
 process/KeywordRegistry_basic/process.js      |   4 +-
 4 files changed, 285 insertions(+), 370 deletions(-)
 create mode 100644 .liquibase/Data_alias/basic/2019.2/DocuTemplatePlaceholder_Keywords.xml

diff --git a/.liquibase/Data_alias/basic/2019.2/DocuTemplatePlaceholder_Keywords.xml b/.liquibase/Data_alias/basic/2019.2/DocuTemplatePlaceholder_Keywords.xml
new file mode 100644
index 00000000000..6c892fd2a03
--- /dev/null
+++ b/.liquibase/Data_alias/basic/2019.2/DocuTemplatePlaceholder_Keywords.xml
@@ -0,0 +1,57 @@
+<?xml version="1.1" encoding="UTF-8" standalone="no"?>
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
+                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">
+    <changeSet author="s.listl" id="f236804e-e3f2-43e4-8e5a-1ba48522d8c5">
+        <insert tableName="AB_KEYWORD_ENTRY">
+            <column name="AB_KEYWORD_ENTRYID" value="bdecb990-19ce-4710-afd7-321ed5bae93d"/>
+            <column name="KEYID" value="letterSalutation"/>
+            <column name="TITLE" value="Letter salutation"/>
+            <column name="CONTAINER" value="TextPlaceholder"/>
+            <column name="SORTING" valueNumeric="0"/>
+            <column name="ISACTIVE" valueNumeric="1"/>
+            <column name="ISESSENTIAL" valueNumeric="1"/>
+        </insert>
+        <insert tableName="AB_KEYWORD_ENTRY">
+            <column name="AB_KEYWORD_ENTRYID" value="5f31c2cd-dce9-4eeb-872c-1a3005ea3210"/>
+            <column name="KEYID" value="country"/>
+            <column name="TITLE" value="Country"/>
+            <column name="CONTAINER" value="TextPlaceholder"/>
+            <column name="SORTING" valueNumeric="1"/>
+            <column name="ISACTIVE" valueNumeric="1"/>
+            <column name="ISESSENTIAL" valueNumeric="1"/>
+        </insert>
+        <insert tableName="AB_KEYWORD_ENTRY">
+            <column name="AB_KEYWORD_ENTRYID" value="fd9dba2b-92aa-4957-841b-4ec85dc92817"/>
+            <column name="KEYID" value="zipCode"/>
+            <column name="TITLE" value="ZIP"/>
+            <column name="CONTAINER" value="TextPlaceholder"/>
+            <column name="SORTING" valueNumeric="2"/>
+            <column name="ISACTIVE" valueNumeric="1"/>
+            <column name="ISESSENTIAL" valueNumeric="1"/>
+        </insert>
+        <insert tableName="AB_KEYWORD_ATTRIBUTE">
+                <column name="AB_KEYWORD_ATTRIBUTEID" value="ebf7de02-1873-4068-b551-c5348bab4fc6"/>
+                <column name="NAME" value="addressFormat"/>
+                <column name="CONTAINER" value="TextPlaceholder"/>
+                <column name="TYPE" value="CHAR_VALUE"/>
+        </insert>
+        <insert tableName="AB_KEYWORD_ATTRIBUTERELATION">
+                <column name="AB_KEYWORD_ATTRIBUTERELATIONID" value="4cd39532-0cd1-4828-961d-76165af2f4c3"/>
+                <column name="AB_KEYWORD_ENTRY_ID" value="bdecb990-19ce-4710-afd7-321ed5bae93d"/>
+                <column name="AB_KEYWORD_ATTRIBUTE_ID" value="ebf7de02-1873-4068-b551-c5348bab4fc6"/>
+                <column name="CHAR_VALUE" value=""/>
+        </insert>
+        <insert tableName="AB_KEYWORD_ATTRIBUTERELATION">
+                <column name="AB_KEYWORD_ATTRIBUTERELATIONID" value="dd5279a9-920e-4ed3-9438-56fac9c68f31"/>
+                <column name="AB_KEYWORD_ENTRY_ID" value="5f31c2cd-dce9-4eeb-872c-1a3005ea3210"/>
+                <column name="AB_KEYWORD_ATTRIBUTE_ID" value="ebf7de02-1873-4068-b551-c5348bab4fc6"/>
+                <column name="CHAR_VALUE" value="{cc}"/>
+        </insert>
+        <insert tableName="AB_KEYWORD_ATTRIBUTERELATION">
+                <column name="AB_KEYWORD_ATTRIBUTERELATIONID" value="54b34ffe-178e-410b-a32e-3519ebf5f550"/>
+                <column name="AB_KEYWORD_ENTRY_ID" value="fd9dba2b-92aa-4957-841b-4ec85dc92817"/>
+                <column name="AB_KEYWORD_ATTRIBUTE_ID" value="ebf7de02-1873-4068-b551-c5348bab4fc6"/>
+                <column name="CHAR_VALUE" value="{zc}"/>
+        </insert>
+    </changeSet>
+</databaseChangeLog>
diff --git a/process/DocumentTemplate_lib/process.js b/process/DocumentTemplate_lib/process.js
index e92069a69a6..e764b2a2d93 100644
--- a/process/DocumentTemplate_lib/process.js
+++ b/process/DocumentTemplate_lib/process.js
@@ -1,3 +1,7 @@
+import("KeywordRegistry_basic");
+import("Document_lib");
+import("KeywordData_lib");
+import("Sql_lib");
 import("Address_lib");
 import("system.process");
 import("system.vars");
@@ -13,383 +17,88 @@ import("system.logging");
 import("system.text");
 import("system.eMath");
 import("system.mail");
-
+import("Keyword_lib");
 
 /**
- * Provides functions for document templates
+ * Object for working with document templates
  */
-function DocumentTemplateUtils () {}
-
-
-
-
-/*
-* Liefert ausgewählte DocumentID zurück
-*
-* @param {Integer []} pType req DocumentenTypes
-* @param {String} pLanguage opt Sprache
-* @param {String} pTemplateName opt Name der Vorlage
-* @param {Boolean} pOnlyHTML opt default: false; sollen nur Vorlagen zur Auswahl stehen, die nach der neuen Vorlagen-Methode einen HTML Text hinterlegt haben
-*
-* @return {Obj} { id, name, language, attachments:[[Name, Data]], template:{filename, data},
-*                 exportoption:{fieldids, open, file, fieldseperator, fieldlimit, recordseperator} };
-*       oder {Boolean:false} wenn keine Vorlage vorhanden ist oder ausgewählt wurde
-*/
-DocumentTemplateUtils.chooseTemplate = function (pType, pLanguage, pTemplateName, pOnlyHTML)
+function DocumentTemplate (pTemplateContent, pType)
 {
-    var langcondition = "";
-    var templatelist;
-    var templatename = ""
-    var condition = "AOTYPE in (" + pType.join(", ") + ")";
-    if ( pLanguage != undefined && pLanguage != "" )
-    {
-        langcondition = " and LANG = " + pLanguage;
-    }
-    if ( pTemplateName != undefined && pTemplateName != "" )
-    {
-        condition += " and NAME like '" + pTemplateName + "%'";
-    }
-    // die zum Frame gehörigen und die Vorlagen ohne Zugehörigkeit anzuzeigen
-    if ( vars.exists("$image.Frame") )
-    {
-        var fd = new FrameData();
-        var frame = fd.getData("id", vars.get("$image.Frame").Id, ["name"]);
-        condition += 	" and ( DOCUMENTID in (select ROW_ID from ATTRLINK join ATTR on ATTRID = VALUE_ID and OBJECT_ID = 9 and ATTRNAME = '" + frame + "')"
-        + " or DOCUMENTID not in (select ROW_ID from ATTRLINK join ATTR on ATTRID = ATTRLINK.ATTR_ID and OBJECT_ID = 9 and  ATTRNAME = 'Doku Vorlagen Verwendung')) ";
-    }
-    condition = getGrantCondition( "DOCUMENT", condition);
-    templatelist = db.array(db.COLUMN, "select NAME from DOCUMENT where " + (pOnlyHTML != undefined && pOnlyHTML == true ? " HTMLTEXT is not null and " : "") + condition + langcondition + " order by NAME");
-     var questionText = "";
-    if ( templatelist[0] == undefined || pLanguage == "" ) // keine passende Sprache beim Kontakt hinterlegt
-    {
-        // Benutzer wählt sich jetzt seine gewünschte Sprache aus...
-        questionText = translate.text("Keine Vorlage in der gewünschten Sprache gefunden.\nBitte eine Sprache auswählen.");
-        var availableLangs =  db.array(db.COLUMN, "select distinct(keyname1) from keyword join document on document.lang = keyword.keyvalue where" + getKeyTypeSQL("SPRACHE"));
-        if(vars.get("$sys.scope") == "vaadin")
-        {
-            // Liest die verfügbaren Sprachen aus, die in den Dokumenten vorhanden sind, und zwar ausgeschrieben
-            availableLangs = db.table("select distinct(keyname1), keyname1 from keyword join document on document.lang = keyword.keyvalue where" + getKeyTypeSQL("SPRACHE"));
-            //@TODO evtl noch ein Label in den Dialog zur Textanzeige?
-            pLanguage = openCMBDialog( questionText, availableLangs).cmb_Data;
-        }
-        else
-        {
-            pLanguage = swing.askQuestion(questionText, swing.QUESTION_COMBOBOX, "|" + availableLangs.join("|"));
-        }
-        if (pLanguage == null ) return false;
-        // ...und JDito setzt die ausgeschriebene Sprache wieder in das Keyword um, z.B. 'de'
-        pLanguage = db.cell("select distinct(lang) from document join keyword on keyword.keyname1 = '" + pLanguage + "' and keyword.keyvalue = document.lang where" + getKeyTypeSQL("SPRACHE"));
-        langcondition = " and LANG = " + pLanguage;
-
-        templatelist = db.array(db.COLUMN, "select NAME from DOCUMENT where " + condition + langcondition + " order by NAME");
-    }
+    this.content = pTemplateContent;
+    this.type = pType;
+}
 
-    if (templatelist.length > 1)
-    {
-        questionText = translate.text("Vorlage wählen");
-        if(vars.get("$sys.scope") == "vaadin")
-        {
-            templatelistChoose = db.table("select NAME, NAME from DOCUMENT where "
-                + (pOnlyHTML != undefined && pOnlyHTML == true ? " HTMLTEXT is not null and " : "")
-                + condition + langcondition + " order by NAME");
-            templatename =  openCMBDialog(questionText, templatelistChoose).cmb_Data;
-        }
-        else
-        {
-            templatename = swing.askQuestion(questionText, swing.QUESTION_COMBOBOX, "|" + templatelist.join("|"));
-        }
-        if (templatename == null)return false;
-    }
-    else templatename = templatelist[0];
-                                       // 0     1      2         3            4             5            6             7              8               9               10          11
-    var data = db.array(db.ROW, "select NAME, LANG, HTMLTEXT, FIELDIDS, OPENWITHEXPORT, EXPORTFILE, FIELDSEPERATOR, FIELDLIMIT, RECORDSEPERATOR, DOCUMENTID, MODULEEXPORTQUERY, AOTYPE "
-        + " from DOCUMENT where NAME = '" + templatename + "' and " + condition + langcondition);
+/**
+ * The types a DocumentTemplate can have. Depending on the type,
+ * the correct method for replacing the placeholders can be chosen
+ */
+DocumentTemplate.types = {
+    TXT : "TXT",
+    HTML : "HTML",
+    ODT : "ODT",
+    DOCX : "DOCX"
+};
 
-    if (data.length > 0)
-    {   
-        // if type is 7, it uses exportfields. They use the table DOCUMENTLINK and not the column FIELDIDS
-        if (data[11] == 7)
-        {
-            data[3] = getLinkedKeywords(data[9]);
-        }
-        
-        var template = false;
-        // get metadata for attachments
-        var metadata = db.getBinaryMetadata("DOCUMENT", "Vorlage", data[9], false, null);
-        var attachments = [];
-        // fill rows of [ filname, bindata ] into the array
-        for( var i = 0; i < metadata.length; i++ )
-        {
-            attachments.push( [ metadata[i].filename, db.getBinaryContent(metadata[i].id, null) ] );
-        }
-        
-        if ( data[2] != "" )  // Vorlage aus HTML-Text
-        {
-            template =
-            {
-                data:data[2]
-            };
-        }
-        else if ( attachments.length > 0 )   // Vorlage aus Anhang
-        {
-            template =
-            {
-                filename: attachments[0][0],
-                data: attachments[0][1]
-            };
-            //do not add the documents text to the attachments
-            attachments = attachments.filter(function (row){
-                //use filename and the b64-date because the user is able to control whether the message-text should be in the attachments or not,
-                //by simply adding the message-text in another document
-                return row[0] + "/" + row[1] != template.filename + "/" + template.data;
-            });
-        }
-        var exportoption = {
-            fieldids:data[3],
-            open:data[4],
-            file:data[5],
-            fieldseperator:data[6],
-            fieldlimit:data[7],
-            recordseperator: data[8],
-            exportquery: data[10]
-        };
-        return {
-            id:data[9],
-            aotype:data[11],
-            name:data[0],
-            language:data[1],
-            template:template,
-            attachments:attachments,
-            exportoption:exportoption
-        };
-    }
-    else return false;
+DocumentTemplate.loadTemplate = function (pTemplateId)
+{
+    var alias = "_____SYSTEMALIAS";
+    var templateDocument = db.getBinaryMetadata("DOCUMENTTEMPLATE", "DOCUMENT", pTemplateId, false, alias, null);
+    if (!templateDocument[0])
+        return null;
+    var binaryId = templateDocument[0][db.BINARY_ID];
+    var type = DocumentUtil.getFileExtensionFromUpload(templateDocument[0][db.BINARY_FILENAME]);
+    var typeMap = {
+        txt : DocumentTemplate.types.TXT,
+        html : DocumentTemplate.types.HTML,
+        odt : DocumentTemplate.types.ODT,
+        docx : DocumentTemplate.types.DOCX
+    };
+    type = typeMap[type]; //choose the document type depending on the file extension
+    if (binaryId)
+        return new DocumentTemplate(util.decodeBase64String(db.getBinaryContent(binaryId, alias)), type);
+    return null;
 }
 
-/*
-* ersetzt die Platzhalter in ODT-Datei
-*
-* @param {String} pCondition req Condition
-* @param {String} pODTFile req Filename des odt-Datei
-* @param {String} pAddressID opt ID von der die Adressdaten geholt werden
-* @param {[]} pAdditionalData opt zusätzliche Daten
-*
-* pAdditionalData =  {Fields: ["RELID", "Platzhalter1","Platzhalter2"],
-*                     SQLStr: "select RELATION_ID, ADDR, MEDIUM_ID from COMM",
-*     statt SQLStr    Data: [[]],
-*		      ID: "RELATION_ID" };
-*
-* @param {[]} pTableData opt Tabellendaten
-*
-* pTableData = [{  Table: "ADDR",
-*                  Fields: ["RELID", "Type","Strasse","PLZ","Ort","Staat","Land"],
-*                  SQLStr: "select RELATION_ID, ADDR_TYPE, " + concat(["ADDRESS", "BUILDINGNO"]) + ", ZIP, CITY, STATE, NAME_DE from ADDRESS join COUNTRYINFO on COUNTRY = ISO2",
-*     statt SQLStr Data: [[]],
-*     optional     SQLOrder: "STATE, ZIP",
-*                  ID: "RELATION_ID" }];
-*
-* @return {void}
-*/
-DocumentTemplateUtils.replaceODTFile = function ( pCondition, pODTFile, pAddressID, pAdditionalData, pTableData )
+DocumentTemplate.prototype.toString = function ()
 {
-    var i;
-    var textS;
-    var ti;
-    var z;
-    if ( pTableData == undefined ) pTableData = [];
-    if ( pAdditionalData == undefined ) pAdditionalData = {};
-    // Configuration für die Platzhalter
-    var config = [["RELATIONID","fieldname","RELATION.RELATIONID"]]
-    config = config.concat( db.table("select KEYNAME1, KEYNAME2, KEYDETAIL from KEYWORD where " + getKeyTypeSQL("EXPORTFIELDS")));
-    var senderRelId = getSendRelID();
-    if (senderRelId == null)
-        return false;
-    var addrdata = getAddressesData( pCondition, config, senderRelId, pAddressID );
-    if ( addrdata.length > 1 )
-    {
-        var relationids = [];
-        for (i=1; i < addrdata.length; i++ )	relationids.push(addrdata[i][0]);
-        // pTableData aufbereiten
-        for (ti = 0; ti < pTableData.length; ti++)
-        {
-            if( pTableData[ti].SQLStr != undefined )
-            {
-                if ( pTableData[ti].ID != undefined ) pTableData[ti].SQLStr += " where " + pTableData[ti].ID + " in ('" + relationids.join("','") + "')";
-                if ( pTableData[ti].SQLOrder != undefined ) pTableData[ti].SQLStr += " order by " + pTableData[ti].SQLOrder;
-                pTableData[ti].Data = db.table(pTableData[ti].SQLStr );
-            }
-            pTableData[ti].TableData =  [];
-            for (i=0; i < pTableData[ti].Data.length; i++ )
-            {
-                if ( pTableData[ti].TableData[pTableData[ti].Data[i][0]] == undefined )  pTableData[ti].TableData[pTableData[ti].Data[i][0]] = [];
-                pTableData[ti].TableData[pTableData[ti].Data[i][0]].push( pTableData[ti].Data[i] );
-            }
-        }
-        // pAdditionalData aufbereiten
-        if ( pAdditionalData.SQLStr != undefined )
-        {
-            if ( pAdditionalData.ID != undefined ) pAdditionalData.SQLStr += " where " + pAdditionalData.ID + " in ('" + relationids.join("','") + "')";
-            pAdditionalData.Data = db.table( pAdditionalData.SQLStr );
-        }
-        pAdditionalData.AddData =  [];
-        if ( pAdditionalData.Data != undefined )
-            for (i=0; i < pAdditionalData.Data.length; i++ )	pAdditionalData.AddData[pAdditionalData.Data[i][0]] = pAdditionalData.Data[i];
-
-        // ersetzen Platzhalter in content.xml
-
-        textS = util.decodeBase64String(pack.getFromZip(pODTFile, "content.xml"));
-        var bodybegin = textS.indexOf("<office:body>");
-        var bodyend =  textS.indexOf("</office:body>") + 14;
-        var body = textS.substring( bodybegin, bodyend );
-        var lastbody = textS.substr( bodyend );
-        textS = textS.substring( 0, bodybegin );
-        for (i = 1; i < addrdata.length; i++)
-        {
-            var bulkbody = body;
-            for (z = 0; z < addrdata[0].length; z++)
-            {
-                bulkbody = bulkbody.replace( new RegExp( getDefaultODTplaceholer(addrdata[0][z]), "ig"),
-                    addrdata[i][z].replace( new RegExp( "\n", "ig"), "<text:line-break/>").replace( new RegExp( "&", "ig"), "&amp;") );
-            }
-            if ( pAdditionalData.AddData[addrdata[i][0]] != undefined )
-            {
-                bulkbody = relaceAdditionValues( bulkbody, pAdditionalData.Fields, pAdditionalData.AddData[addrdata[i][0]], "" );
-            }
-            // Tabellen füllen
-            for (ti = 0; ti < pTableData.length; ti++)
-            {
-                var tablepos = bulkbody.indexOf( getDefaultODTplaceholer(pTableData[ti].Table, true));
-                if ( tablepos != -1 )
-                {
-                    var tablebegin = bulkbody.lastIndexOf("<table:table-row", tablepos);
-                    var tableend =  bulkbody.indexOf("</table:table-row>", tablepos ) + 18;
-                    var lasttable =  bulkbody.substr( tableend );
-                    var tablerow = bulkbody.substring( tablebegin, tableend );
-                    bulkbody = bulkbody.substring( 0, tablebegin );
-                    var tabledata = pTableData[ti].TableData[addrdata[i][0]];
-                    if ( tabledata != undefined )
-                    {
-                        for (var tz = 0; tz < tabledata.length; tz++)
-                        {
-                            var table = tablerow;
-                            bulkbody += relaceAdditionValues( table, pTableData[ti].Fields, tabledata[tz], pTableData[ti].Table + "." );
-                        }
-                    }
-                    bulkbody += lasttable;
-                }
-            }
-            textS += bulkbody;
-        }
-        textS +=  lastbody;
-        pack.addToZip(pODTFile, "content.xml", util.encodeBase64String(textS))
-        // ersetzen Platzhalter in styles.xml
-        var styles = util.decodeBase64String(pack.getFromZip(pODTFile, "styles.xml"));
-        for (z = 0; z < addrdata[0].length; z++)
-        {
-            styles = styles.replace( new RegExp( getDefaultODTplaceholer(addrdata[0][z]), "ig"),
-                addrdata[1][z].replace( new RegExp( "\n", "ig"), "<text:line-break/>").replace( new RegExp( "&", "ig"), "&amp;") );
-        }
-        if ( pAdditionalData.AddData[addrdata[1][0]] != undefined )
-        {
-            styles = relaceAdditionValues( styles, pAdditionalData.Fields, pAdditionalData.AddData[addrdata[1][0]], "" );
-        }
-        pack.addToZip(pODTFile, "styles.xml", util.encodeBase64String(styles));
-        return true;
-    }
-    return false;
-
-    function relaceAdditionValues( pText, pFields, pValues, pTable )
-    {
-        for (var sp = 0; sp < pFields.length; sp++)
-        {
-            var ph = pFields[sp];
-            var value = pValues[sp].toString();
-            if ( typeof( ph ) == "object")
-            {
-                ph = pFields[sp][0];
-                if ( value != "" )
-                    switch( pFields[sp][1] )  // Formatierung
-                    {
-                        case "date":
-                            value = datetime.toDate(value, pFields[sp][2]);
-                            break;
-                        case "long":
-                            value = text.formatLong(value, pFields[sp][2]);
-                            break;
-                        case "double":
-                            value = text.formatDouble(value, translate.text(pFields[sp][2]), true);
-                            break;
-                    }
-            }
-            pText = pText.replace( new RegExp(getDefaultODTplaceholer(pTable + ph) , "ig"),
-                value.replace( new RegExp( "\n", "ig"), "<text:line-break/>").replace( new RegExp( "&", "ig"), "&amp;") );
-        }
-        return pText;
-    }
+    return this.templateText;
 }
 
-/*
-* Liefert Vorlage mit ersetzen Platzhalter durch den jeweiligen Text.
-*
-* @param {String} pContactId req RELATIONID der relation, von der die Adressdaten geholt werden - SQL where condition für getAddressesData()
-* @param {Integer[]} pDocuType req OATYPE der Vorlage
-* @param {String} pLanguage opt Sprache
-* @param {String} pAddressID opt pAddressID
-* @param {String} pSenderID opt UserRelationID
-* @param {String} pTemplateName opt Name der Vorlage
-* @param {[]} pAdditionalValues opt Weitere Platzhalter mit Werten
-*
-* @return {Obj} { id, name, language, attachments:[[Name, Data]], template:{filename, data},
-*                 exportoption:{fieldids, open, file, fieldseperator, fieldlimit, recordseperator} }
-*       oder {Boolean:false} wenn keine Vorlage vorhanden ist oder ausgewählt wurde
-*/
-DocumentTemplateUtils.getTextTemplate = function ( pContactId, pDocuType, pLanguage, pAddressID, pSenderID, pTemplateName, pAdditionalValues )
+/**
+ * returns the template text with replaced placeholders
+ * 
+ * @param {Object} pReplacements map, the structure is {placeholder : value} (the placeholder should have the prefix @@)
+ */
+DocumentTemplate.prototype.getReplacedText = function (pReplacements)
 {
-    var value = "";
-    var document = chooseTemplate( pDocuType, pLanguage, pTemplateName );
-    if ( !document )  return false;
-
-    if ( pAdditionalValues == undefined )  pAdditionalValues = "";
-    var html = document.template.data.substr(0, 6) == "<html>"
-    if ( document.template.filename != undefined )
-        document.template.data = decode64(document.template.data);
-
-    // Configuration für die Platzhalter
-    var config = db.table("select KEYNAME1, KEYNAME2, KEYDETAIL from KEYWORD where " + getKeyTypeSQL("EXPORTFIELDS"));
-    var addrdata = getAddressesData( [pContactId], config, pSenderID, pAddressID );
-    for (var i = 0; i < addrdata[0].length; i++)
-    {
-        if (html) value = addrdata[1][i].replace( new RegExp( "\n", "ig"), "<br>" );
-        else 	value = addrdata[1][i];
-        document.template.data = document.template.data.replace( new RegExp( "@@" + addrdata[0][i], "ig"), value );
-    }
-    for (i = 0; i < pAdditionalValues.length; i++)
+    switch (this.type)
     {
-        document.template.data = document.template.data.replace( new RegExp( "@@" + pAdditionalValues[i][0], "ig"), pAdditionalValues[i][1] );
+        case DocumentTemplate.types.HTML:
+            for (let i in pReplacements)
+                pReplacements[i] = text.replaceAll(pReplacements[i], {"\n" : "<br>"});
+        case DocumentTemplate.types.TXT:
+            return text.replaceAll(this.content, pReplacements);
+        case DocumentTemplate.types.ODT:
+            return this._getReplacedODT(pReplacements);
+        case DocumentTemplate.types.DOCX:
+            return this._getReplacedDOCX(pReplacements);
+        default:
+            return null;
     }
-    return document;
 }
 
-
-
-/*
- * transforms a given placeholerformat into the ODT-placeholer thats in the ODT
- * if you have to change this (e.g. to @@) you can do this at this 1 position
- *
- * @param {String} pPlaceholderName req name of the placeholer, e.g. "Anrede"
- * @param {bool} pOnlyStart opt if set to true only the leading-symbols are added
- *
- * @return {String} placeholder with placeholder-symbols, e.g. "{@Anrede@}"
- */
-DocumentTemplateUtils.getDefaultODTplaceholer = function (pPlaceholderName, pOnlyStart)
+DocumentTemplate.prototype.getReplacedTextByContactId = function (pContactId)
 {
-    if (pOnlyStart)
-        return "{@" + pPlaceholderName;
-    return "{@" + pPlaceholderName + "@}";
+    var config = DocumentTemplate._getPlaceholderConfig();
+    var replacements = getAddressesData([pContactId], config, pSenderID, pAddressID);
+    //TODO: build the replacement map here
+    return this.getReplacedText(replacements);
 }
 
 /*
  * replaces a given Odt-File on the server and returns the replaced base64-file
+ * 
+ * @deprecated needs refactoring
  *
  * @param {String} pTemplateData req base64-encoded input file with placeholders
  * @param {String} pTemplateName req name of the input file
@@ -469,20 +178,174 @@ DocumentTemplateUtils.getReplacedODT = function (pTemplateData, pTemplateName, p
     return replacedFileData;
 }
 
+DocumentTemplate.prototype._getReplacedODT = function (pReplacements)
+{
+    
+}
+
 /*
  * This function is used to replace placeholders via DocXTemplater
- * @param {String} pInputB64 - BASE64 coded String of the document, which holds the placeholders
- * @param {Object} pPlaceholder - Must contain an object, which holds the placeholders
+ * 
+ * @param {Object} pReplacements - Must contain an object, which holds the placeholders
  * 
  * @return {String} returns the modified document in a BASE64 coded string
  */
-DocumentTemplateUtils.getReplacedDocXDocumentB64 = function (pInputB64, pPlaceholder)
+DocumentTemplate.prototype._getReplacedDOCX = function (pReplacements)
 {
     //this is executed as a process because of better performance
     var documentData = process.execute("getDocxDocument_serverProcess", {
-        templateb64: pInputB64
-       ,placeholderConfig: JSON.stringify(pPlaceholder)//process.execute is only able to handle strings
+        templateb64: this.templateText,
+        placeholderConfig: JSON.stringify(pPlaceholder) //process.execute is only able to handle strings
     });
 
     return documentData;
 }
+
+/**
+ * Provides functions for document templates
+ */
+function DocumentTemplateUtils () {}
+
+/**
+ * loads a document template and replaces the placeholders
+ */
+DocumentTemplateUtils.getSingleReplacedDocument = function (pTemplateId, pContactId)
+{
+    var template = DocumentTemplate.loadTemplate(pTemplateId);
+    return template.getReplacedTextByContactId(pContactId);
+}
+
+/**
+ * @return {Object} map with placeholders and their definition
+ */
+DocumentTemplateUtils._getPlaceholderConfig = function ()
+{
+    return KeywordData.getKeywordAttributeRelations($KeywordRegistry.textPlaceholder());
+}
+
+
+/*
+* ersetzt die Platzhalter in ODT-Datei
+* 
+* @deprecated needs refactoring
+*
+* @param {String} pCondition req Condition
+* @param {String} pODTFile req Filename des odt-Datei
+* @param {String} pAddressID opt ID von der die Adressdaten geholt werden
+*
+* @return {void}
+*/
+DocumentTemplateUtils.replaceODTFile = function (pCondition, pODTFile, pAddressID)
+{
+    // Configuration für die Platzhalter
+    var config = [["RELATIONID","fieldname","RELATION.RELATIONID"]]
+    config = config.concat(DocumentTemplateUtils._getPlaceholderConfig());
+    var senderRelId = getSendRelID();
+    if (senderRelId == null)
+        return false;
+    var addrdata = getAddressesData(pCondition, config, senderRelId, pAddressID);
+    if (addrdata.length > 1)
+    {
+        var relationids = [];
+        for (let i = 1; i < addrdata.length; i++ )	
+            relationids.push(addrdata[i][0]);
+        
+
+        // ersetzen Platzhalter in content.xml
+
+        var textS = util.decodeBase64String(pack.getFromZip(pODTFile, "content.xml"));
+        var bodybegin = textS.indexOf("<office:body>");
+        var bodyend =  textS.indexOf("</office:body>") + 14;
+        var body = textS.substring( bodybegin, bodyend );
+        var lastbody = textS.substr( bodyend );
+        textS = textS.substring( 0, bodybegin );
+        for (let i = 1; i < addrdata.length; i++)
+        {
+            var bulkbody = body;
+            for (let ii = 0; ii < addrdata[0].length; ii++)
+            {
+                bulkbody = bulkbody.replace( new RegExp( getDefaultODTplaceholer(addrdata[0][ii]), "ig"),
+                    addrdata[i][ii].replace( new RegExp( "\n", "ig"), "<text:line-break/>").replace( new RegExp( "&", "ig"), "&amp;") );
+            }
+            textS += bulkbody;
+        }
+        textS +=  lastbody;
+        pack.addToZip(pODTFile, "content.xml", util.encodeBase64String(textS))
+        // ersetzen Platzhalter in styles.xml
+        var styles = util.decodeBase64String(pack.getFromZip(pODTFile, "styles.xml"));
+        for (let i = 0; i < addrdata[0].length; i++)
+        {
+            styles = styles.replace( new RegExp( getDefaultODTplaceholer(addrdata[0][i]), "ig"),
+                addrdata[1][i].replace( new RegExp( "\n", "ig"), "<text:line-break/>").replace( new RegExp( "&", "ig"), "&amp;") );
+        }
+        pack.addToZip(pODTFile, "styles.xml", util.encodeBase64String(styles));
+        return true;
+    }
+    return false;
+}
+
+/*
+* Liefert Vorlage mit ersetzen Platzhalter durch den jeweiligen Text.
+* 
+* @deprecated needs refactoring
+*
+* @param {String} pContactId req RELATIONID der relation, von der die Adressdaten geholt werden - SQL where condition für getAddressesData()
+* @param {Integer[]} pDocuType req OATYPE der Vorlage
+* @param {String} pLanguage opt Sprache
+* @param {String} pAddressID opt pAddressID
+* @param {String} pSenderID opt UserRelationID
+* @param {String} pTemplateName opt Name der Vorlage
+*
+* @return {Obj} { id, name, language, attachments:[[Name, Data]], template:{filename, data},
+*                 exportoption:{fieldids, open, file, fieldseperator, fieldlimit, recordseperator} }
+*       oder {Boolean:false} wenn keine Vorlage vorhanden ist oder ausgewählt wurde
+*/
+DocumentTemplateUtils.getReplacedText = function (pContactId, pDocuType, pLanguage, pAddressID, pSenderID, pTemplateName)
+{
+    var document = chooseTemplate( pDocuType, pLanguage, pTemplateName);
+    if (!document)  return null;
+
+    var isHtml = document.template.data.substr(0, 6) == "<html>"
+    if (document.template.filename != undefined)
+        document.template.data = decode64(document.template.data);
+
+    // Configuration für die Platzhalter
+    var config = KeywordData.getKeywordAttributeRelations("TextPlaceholder"); //->keyword registry
+    var addrdata = getAddressesData( [pContactId], config, pSenderID, pAddressID );
+    var value;
+    var prefix = "@@";
+    for (let i = 0, l = addrdata[0].length; i < l; i++)
+    {
+        if (isHtml) 
+            value = addrdata[1][i].replace(new RegExp( "\n", "ig"), "<br>");
+        else 	
+            value = addrdata[1][i];
+        document.template.data = document.template.data.replace(new RegExp(prefix + addrdata[0][i], "ig"), value);
+    }
+    
+    return document;
+}
+
+
+
+/*
+ * transforms a given placeholerformat into the ODT-placeholer thats in the ODT
+ * if you have to change this (e.g. to @@) you can do this at this 1 position
+ *
+ * @deprecated needs refactoring
+ *
+ * @param {String} pPlaceholderName req name of the placeholer, e.g. "Anrede"
+ * @param {bool} pOnlyStart opt if set to true only the leading-symbols are added
+ *
+ * @return {String} placeholder with placeholder-symbols, e.g. "{@Anrede@}"
+ */
+DocumentTemplateUtils.getDefaultODTplaceholer = function (pPlaceholderName, pOnlyStart)
+{
+    if (pOnlyStart)
+        return "{@" + pPlaceholderName;
+    return "{@" + pPlaceholderName + "@}";
+}
+
+
+
+
diff --git a/process/Email_lib/process.js b/process/Email_lib/process.js
index c1e2613524f..7a75d85055b 100644
--- a/process/Email_lib/process.js
+++ b/process/Email_lib/process.js
@@ -73,14 +73,7 @@ function Email (pToRecipients, pSender, pSubject, pBody, pCcRecipients, pBccReci
  */
 Email.prototype.setTemplate = function (pTemplateId)
 {
-    var alias = "_____SYSTEMALIAS";
-    var templateDocument = db.getBinaryMetadata("DOCUMENTTEMPLATE", "DOCUMENT", pTemplateId, false, alias, null);
-    if (!templateDocument[0])
-        return null;
-    var binaryId = templateDocument[0][db.BINARY_ID];
-    if (binaryId)
-        this.body = util.decodeBase64String(db.getBinaryContent(binaryId, alias));
-    return null;
+    this.body = DocumentTemplateUtils.getTemplateText(pTemplateId);
 }
 
 /**
diff --git a/process/KeywordRegistry_basic/process.js b/process/KeywordRegistry_basic/process.js
index 807d14d1482..a7c6e865b3f 100644
--- a/process/KeywordRegistry_basic/process.js
+++ b/process/KeywordRegistry_basic/process.js
@@ -101,4 +101,6 @@ $KeywordRegistry.classificationType$salesproject = function(){return "968eafa3-3
 $KeywordRegistry.personGender = function(){return "PersonGender";};
 $KeywordRegistry.personGender$other = function(){return "o";};
 $KeywordRegistry.permissionCondType = function(){return "PermissionCondType";};
-$KeywordRegistry.permissionAccessType = function(){return "PermissionAccessType";};
\ No newline at end of file
+$KeywordRegistry.permissionAccessType = function(){return "PermissionAccessType";};
+
+$KeywordRegistry.textPlaceholder = function(){return "textPlaceholder";};
\ No newline at end of file
-- 
GitLab