import("DocxTemplater_lib"); import("Binary_lib"); import("Communication_lib"); import("system.neon"); import("Employee_lib"); import("KeywordRegistry_basic"); import("Document_lib"); import("KeywordData_lib"); import("Sql_lib"); import("Address_lib"); import("system.process"); import("system.vars"); import("system.db"); import("system.util"); import("system.pack"); import("system.fileIO"); import("system.translate"); import("system.datetime"); import("system.question"); import("system.text"); import("system.mail"); import("Keyword_lib"); import("Placeholder_lib"); import("Email_lib"); import("MimeType_lib"); import("Util_lib"); /** * Object for working with document templates, holds the content and type of the template. * Provides functions to replace placeholders in the content. * * @param {String} pTemplateContent content, as base64 string (except for DocumentTemplate.types.PLAIN, then it's a normal string) * @param {String} pType type of the template, use the DocumentTemplate.types constants here * @param {String} [pFilename=undefined] file name of the template * @param {Boolean} [pResolveSubtemplates=false] if true subtemplates are resolved (if the type fits) * @param {String} [pTemplateId=undefined] Provide it, if you have it, because this enables the template to load attachments associated by this ID * @param {String} [pMimeType=undefined] mimetype of the content. Only an additional information. Not mandatory. */ function DocumentTemplate (pTemplateContent, pType, pFilename, pResolveSubtemplates, pTemplateId, pMimeType) { this.content = pTemplateContent; this.type = pType; this.options = DocumentTemplate.types.getDefaultTypeOptions(pType); this.filename = pFilename; this.templateId = pTemplateId; this.mimeType = pMimeType; this._attachmentCache = null; this._subtemplateResolvedContent = null; //cache used for .toString this._stringCache = { onlyContent : null, withSubtemplatesResolved : null }; if (pResolveSubtemplates) this._resolveSubtemplates(); } /** * @return {String} the text of the content */ DocumentTemplate.prototype.toString = function (pWithSubtemplates) { var stringCachePosition = pWithSubtemplates ? "withSubtemplatesResolved" : "onlyContent"; if (this._stringCache[stringCachePosition] == null) { var content = pWithSubtemplates && this._subtemplateResolvedContent || this.content; if (this.type == DocumentTemplate.types.PLAIN) this._stringCache[stringCachePosition] = content; else this._stringCache[stringCachePosition] = text.parseDocument(content); } return this._stringCache[stringCachePosition]; } /** * Defines options for the DocumentTemplate, the given options will be appended to the existing options and options that * already exist will be overridden. The options that can be used may vary depending on the type of the DocumentTemplate, * that's why they are wrapped inside an object. * * @param {Object} pOptions The options to set for the object. Some options only affect special types, for example: * <ul> * <li>base64 (boolean): If the replaced content should be base 64 encoded (does not work for docx and odt, these are always encoded)</li> * <li>onlyBody (boolean): If set to true, only use the body of an eml (option only for eml)</li> * </ul> * @return {DocumentTemplate} current object */ DocumentTemplate.prototype.setOptions = function (pOptions) { Object.assign(this.options, pOptions); return this; } /** * resolves sub-template placeholders */ DocumentTemplate.prototype._resolveSubtemplates = function () { // currently we support only txt and html as others would need special caution. if (this.content != null && (this.type == DocumentTemplate.types.TXT || this.type == DocumentTemplate.types.HTML)) { var replacedContent = util.decodeBase64String(this.content); var templates = []; // then load the possible replacement names if (this.type == DocumentTemplate.types.HTML) { templates = templates.concat(newSelect("DOCUMENTTEMPLATEID, REPLACEMENTNAME").from("DOCUMENTTEMPLATE") .where("DOCUMENTTEMPLATE.KIND", $KeywordRegistry.documentTemplateType$textModular()) .and("DOCUMENTTEMPLATE.CLASSIFICATION", $KeywordRegistry.documentTemplateTypeCategory$htmlTemplate()) .table()); } var alias = SqlUtils.getBinariesAlias(); // We use callbacks which are called by pString.replace placeholders = templates.map(function(pTemplate) { // add function for each placeholder so that the db.getBinaryContent is only called, if the placeholder is replaced. return [pTemplate[1], function(matched, index, original) { var templateDocument = db.getBinaryMetadata("DOCUMENTTEMPLATE", "DOCUMENT", pTemplate[0], false, alias, null); var binaryId = templateDocument[0][db.BINARY_ID]; return util.decodeBase64String(db.getBinaryContent(binaryId, alias)); }]; }); // Note: some embedded templates in embedded templates may be replaced, but this is NOT SUPPORTED, as it only works if the second template is not already replaced at that time. placeholders.forEach(function(pPlaceholder) { replacedContent = StringUtils.replaceAll(replacedContent, PlaceholderUtils.formatPlaceholder(pPlaceholder[0]), pPlaceholder[1], "g"); }, this); this._subtemplatedContent = util.encodeBase64String(replacedContent); } } /** * @return {DocumentTemmplate[]} if the templateId exists, it returns all attachments associated by the id as DocumentTemplate array else it just returns an empty array. */ DocumentTemplate.prototype.getAttachments = function () { if (!this.templateId && this._attachmentCache == null) { return []; } if (this._attachmentCache == null) { var attachmentIds = newSelect("DOCUMENTTEMPLATE_ID_CHILD") .from("DOCUMENTTEMPLATELINK") .where("DOCUMENTTEMPLATELINK.DOCUMENTTEMPLATE_ID_PARENT", this.templateId) .arrayColumn(); this._attachmentCache = attachmentIds.map(function(pAttachmentId) { return DocumentTemplateUtils.getTemplate(pAttachmentId, false); }); } return this._attachmentCache; } /** * @param {DocumentTemplate[]} pAttachments array of templates which should be used as attachment */ DocumentTemplate.prototype.setAttachments = function (pAttachments) { this._attachmentCache = pAttachments; } /** * The types a DocumentTemplate can have. Depending on the type, * the correct method for replacing the placeholders can be chosen * * @enum {String} */ DocumentTemplate.types = { TXT : "txt", HTML : "html", EML : "eml", ODT : "odt", DOCX : "docx", DOCM : "docm", PLAIN : "plain", //for simple strings /** * chooses the type depending on the file extension */ fromFileExtension : function (pFileExtension) { switch (pFileExtension) { case "html": case "htm": return this.HTML; case "eml": return this.EML; case "odt": return this.ODT; case "docx": return this.DOCX; case "docm": return this.DOCM; case "txt": return this.TXT; case "msg": case "oft": default: return null; } }, /** * chooses the type depending on the mime type */ fromMimeType : function (pMimetype) { switch (pMimetype) { case MimeTypes.HTML(): return this.HTML; case MimeTypes.EML(): return this.EML; case MimeTypes.ODT(): return this.ODT; case MimeTypes.DOCX(): return this.DOCX; case MimeTypes.DOCM(): return this.DOCM; case MimeTypes.TXT(): return this.TXT; case MimeTypes.MSG(): default: return null; } }, /** * chooses the type depending on the extension in the metadata. If the extension doesn't work, try mimetype * @param {String[]} pBinaryMetadata the binary metadata from system.db */ fromBinaryMetadata : function (pBinaryMetadata) { let filename = pBinaryMetadata[db.BINARY_FILENAME].split("."); let type = DocumentTemplate.types.fromFileExtension(filename[filename.length - 1]); if (type == null) type = DocumentTemplate.types.fromMimeType(pBinaryMetadata[db.BINARY_MIMETYPE]); return type; }, /** * Returns the default options for the given type. * * @param {String} pType the type * @return {Object} object containing the default options */ getDefaultTypeOptions : function (pType) { switch (pType) { case this.EML: return { base64 : false, onlyBody : false, placeholderRegExp : /\{\s*(=\r?\n)?@(.(?!{@)|(\r?\n))+?@\s*(=\r?\n)?\}/gi, parsePlaceholderFn : function (pPlaceholder) { return pPlaceholder.replace(/\s*(=\r?\n)?/, ""); } }; case this.TXT: case this.HTML: return { base64 : false }; case this.DOCX: case this.DOCM: return { startDelimiter : "{@", endDelimiter : "@}" }; case this.ODT: default: return {}; } } }; /** * Loads the content of a document template and creates a new DocumentTemplate object with that. * * @param {String} pAssignmentRowId id of the assignment (in most cases the document template id) * @param {String} [pAssignmentTable="DOCUMENTTEMPLATE"] the LOB assignment table * @param {Boolean} [pResolveSubtemplates=true] if true subtemplates are resolved (if the type fits) * @return {DocumentTemplate} template object */ DocumentTemplate.loadTemplate = function (pAssignmentRowId, pAssignmentTable, pResolveSubtemplates) { var alias = SqlUtils.getBinariesAlias(); if (!pAssignmentTable) pAssignmentTable = "DOCUMENTTEMPLATE"; // if the templateId is accessable, use it, to enable the templateto load attachments var templateId; if (pAssignmentTable == "DOCUMENTTEMPLATE") templateId = pAssignmentRowId; if (templateId) { var template = DocumentTemplateUtils.getTemplate(templateId, pResolveSubtemplates); if (template == null) { if(vars.getString("$sys.isserver") == "true") throw new Error("loadTemplate: File from this template could not be found anymore. " + "Please go to the template and upload a new file. TemplateID: '" + templateId + "'") else question.showMessage(translate.text("File from this template could not be found anymore. " + "Please go to the template and upload a new file."), question.ERROR, translate.text("Error")); return null; } } var templateDocument; if(pAssignmentTable == "SERIALLETTER") { templateDocument = db.getBinaryMetadata(pAssignmentTable, "SERIALLETTERFILE", pAssignmentRowId, false, alias, null); } else { templateDocument = db.getBinaryMetadata(pAssignmentTable, "DOCUMENT", pAssignmentRowId, false, alias, null); } if (!templateDocument[0]) return new DocumentTemplate(undefined, undefined, undefined, undefined, templateId); var binaryId = templateDocument[0][db.BINARY_ID]; var filename = templateDocument[0][db.BINARY_FILENAME]; var mimeType = templateDocument[0][db.BINARY_MIMETYPE]; var type = DocumentTemplate.types.fromBinaryMetadata(templateDocument[0]); if (pResolveSubtemplates == undefined) pResolveSubtemplates = true; return new DocumentTemplate(db.getBinaryContent(binaryId, alias), type, filename, pResolveSubtemplates, templateId, mimeType); } /** * makes a DocumentTemplate from a upload value * * @param {FileUpload} pDocumentUpload FileUpload object * @return {DocumentTemplate} a document template */ DocumentTemplate.fromUpload = function (pDocumentUpload) { var type; //if the mimetype couldn't be determined, check the file extension if (pDocumentUpload.mimeType == MimeTypes.BIN()) type = DocumentTemplate.types.fromFileExtension(pDocumentUpload.fileExtension); else type = DocumentTemplate.types.fromMimeType(pDocumentUpload.mimeType); return new DocumentTemplate(pDocumentUpload.bindata, type, pDocumentUpload.filename, true); } /** * Function that helps to get the correct template when editing a bulk mail. * pUploadValue is preferred over pTemplateId, and if pEditedContent is provided, * it will overwrite the content of the template (but the type will remain the same as * defined by the upload or templateId, if both are empty, pDefaultType is used) * * @param {String} pTemplateId * @param {FileUpload} pDocumentUpload * @param {String} [pEditedContent] * @param {String} [pDefaultType] * @return {DocumentTemplate} the document template */ DocumentTemplate.getSelectedTemplate = function (pTemplateId, pDocumentUpload, pEditedContent, pDefaultType) { if (pDocumentUpload.isFilled()) template = DocumentTemplate.fromUpload(pDocumentUpload); else if (pTemplateId) { template = DocumentTemplate.loadTemplate(pTemplateId); if (!template) return null; } else template = new DocumentTemplate(null, pDefaultType || DocumentTemplate.types.TXT, null, true); if (pEditedContent) { if (template.type == DocumentTemplate.types.EML || template.type == DocumentTemplate.types.HTML) pEditedContent = "<html>" + pEditedContent + "</html>"; template.content = util.encodeBase64String(pEditedContent); } return template; } /** * Returns the template content with replaced placeholders by choosing the right * replace function for the type. * * @param {Object} pReplacements map, the structure is {placeholder : value} * * @return {String} the replaced content */ DocumentTemplate.prototype.getReplacedContent = function (pReplacements) { // if there exists a _subtemplatedContent we use it because then I assume that the replacements are already based on content + subtemplates var content = this._subtemplateResolvedContent || this.content; switch (this.type) { case DocumentTemplate.types.EML: let emlContent if (this.options.onlyBody) { // get only body and treat it as html (next case) var email = Email.fromRFC(content); content = util.encodeBase64String(email.body); } else { emlContent = util.decodeBase64String(content); emlContent = this._replaceText(emlContent, pReplacements); if (this.options.base64) emlContent = util.encodeBase64String(emlContent); return emlContent; } case DocumentTemplate.types.HTML: // replaces ä, ö, ü, ... with html escape signs for (let i in pReplacements) pReplacements[i] = text.text2html(pReplacements[i], false); case DocumentTemplate.types.TXT: let decodedContent = util.decodeBase64String(content); let encodedContent = this._replaceText(decodedContent, pReplacements); if (this.options.base64) encodedContent = util.encodeBase64String(encodedContent); return encodedContent; case DocumentTemplate.types.ODT: return this._getReplacedODT(pReplacements); case DocumentTemplate.types.DOCX: case DocumentTemplate.types.DOCM: return this._getReplacedDOCX(pReplacements); case DocumentTemplate.types.PLAIN: let plainText = this._replaceText(this.content, pReplacements); if (this.options.base64) plainText = util.encodeBase64String(plainText); return plainText; default: return null; } } /** * replaces the placeholders with data from one contact and returns the result * * @param {String} pContactId contact id * @param {Placeholder[]} pAdditionalPlaceholders Additional placeholders that should be used. You can use placeholders with the * types FIXEDVALUE and CALLBACKFUNCTION if you want to calculate the replacement values yourself. * * @return {String} replaced content */ DocumentTemplate.prototype.getReplacedContentByContactId = function (pContactId, pAdditionalPlaceholders) { var replacements = this.getReplacementsByContactIds([pContactId], pAdditionalPlaceholders)[pContactId]; var content = this.getReplacedContent(replacements); return content; } /** * replaces the placeholders with data from the contacts and returns the result * * @param {Array} pContactIds contact ids * @param {Placeholder[]} pAdditionalPlaceholders Additional placeholders that should be used. You can use placeholders with the * types FIXEDVALUE and CALLBACKFUNCTION if you want to calculate the replacement values yourself. * * @return {Object} replaced content for every contactId */ DocumentTemplate.prototype.getReplacedContentByContactIds = function (pContactIds, pAdditionalPlaceholders) { var replacements = this.getReplacementsByContactIds(pContactIds, pAdditionalPlaceholders); var contents = {}; for (let contactId in replacements) { contents[contactId] = this.getReplacedContent(replacements[contactId]); } return contents; } /** * Replaces the placeholders with data from the contacts and returns a serial letter, works * only for ODT * * @param {Array} pContactIds contact ids * @param {Object[][][]} pTableData Table data for the document, as a three-dimensional array * of objects (dimensions are: document, table in that document, rows of the table). For the format, see example. * @param {Placeholder[]} pAdditionalPlaceholders Additional placeholders that should be used. You can use placeholders with the * types FIXEDVALUE and CALLBACKFUNCTION if you want to calculate the replacement values yourself. * * @example * var contacts = newSelect("FIRSTNAME, LASTNAME") * .from("CONTACT") * .join("PERSON", "CONTACT.PERSON_ID = PERSON.PERSONID") * .where("CONTACT.ORGANISATION_ID", orgId) * .table(); * * var tblRows = []; * fnamePlaceholder = PlaceholderUtils.formatPlaceholder("fname"); * lnamePlaceholder = PlaceholderUtils.formatPlaceholder("lname"); * * for (let i = 0; i < contacts.length; i++) * { * let names = {}; * names[fnamePlaceholder] = contacts[i][0]; * names[lnamePlaceholder] = contacts[i][1]; * tblRows.push(names); * } * * var tables = [tblRows]; * var template = DocumentTemplate.loadTEmplate(templateId); * var letter = template.getSerialLetterByContactIds([contactId], [tables]); * * @return {Object} the content of the replaced ODT */ DocumentTemplate.prototype.getSerialLetterByContactIds = function (pContactIds, pTableData, pAdditionalPlaceholders) { if (this.type == DocumentTemplate.types.ODT) { let replacements = this.getReplacementsByContactIds(pContactIds, pAdditionalPlaceholders); let replaceArray = pContactIds.map(function (contactId) { return replacements[contactId]; }); return this._getReplacedODT(replaceArray, pTableData); } question.showMessage(DocumentTemplate.getSerialLetterODTOnlyMessage(), question.INFORMATION, translate.text("Action not supported")) return null; } DocumentTemplate.getSerialLetterODTOnlyMessage = function() { return translate.text("Only .odt files are supported for bulkletters."); } /** * Replaces the placeholders with data from the contacts and returns the resulting Emails. * * @param {Array} pContactIds contact ids * @param {Placeholder[]} pAdditionalPlaceholders Additional placeholders that should be used. You can use placeholders with the * types FIXEDVALUE and CALLBACKFUNCTION if you want to calculate the replacement values yourself. * * @return {Object} Object containing the contact ids as keys and the corresponding Email * objects as values */ DocumentTemplate.prototype.getReplacedEmailsByContactIds = function (pContactIds, pAdditionalPlaceholders) { var emailObjects = {}; var isEML = this.type == DocumentTemplate.types.EML; this.setOptions({base64 : isEML}); var emailContents = this.getReplacedContentByContactIds(pContactIds, pAdditionalPlaceholders); for (let contactId in emailContents) { if (isEML) { emailObjects[contactId] = Email.fromRFC(emailContents[contactId]); } else { // convert to HTML if needed if (this.type == DocumentTemplate.types.TXT || this.type == DocumentTemplate.types.PLAIN) emailContents[contactId] = text.text2html(emailContents[contactId], false); emailObjects[contactId] = new Email(emailContents[contactId]); } // adding the templates to each mail should be no memory-problem as it is only a reference to pTemplate._attachmentCache, so no problem here emailObjects[contactId].attachmentTemplates = this.getAttachments(); } return emailObjects; } /** * replaces placeholders in the given string */ DocumentTemplate.prototype._replaceText = function (pText, pReplacements) { var placeholderRegExp = this.options.placeholderRegExp || PlaceholderUtils.getRegexpMatchAll(); var that = this; pText = pText.replace(placeholderRegExp, function (pFound) { let foundFiltered = typeof that.options.parsePlaceholderFn === "function" ? that.options.parsePlaceholderFn(pFound) : pFound; return pReplacements[foundFiltered] ? pReplacements[foundFiltered] : pFound; }); return pText; } /** * @param {String[]} pForcedPlaceholders these placeholders are always loaded * @param {Placeholder[]} pAdditionalPlaceholders Additional placeholders that should be used. You can use placeholders with the * types FIXEDVALUE and CALLBACKFUNCTION if you want to calculate the replacement values yourself. * @return {Object[]} all placeholders needed in this template * @private */ DocumentTemplate.prototype._getRequiredPlaceholders = function (pForcedPlaceholders, pAdditionalPlaceholders) { var content = this.toString(true); var placeholders = PlaceholderUtils.getPlaceholders(); if (pAdditionalPlaceholders) placeholders = placeholders.concat(pAdditionalPlaceholders); // get all placeholders which matches the placeholder pattern var foundPlaceholders = content.match(this.options.placeholderRegExp || PlaceholderUtils.getRegexpMatchAll()); if (foundPlaceholders == null) foundPlaceholders = []; // clean placeholder from the spechial strings (e.g. to filter '=' in emls) if (typeof this.options.parsePlaceholderFn === "function") foundPlaceholders = foundPlaceholders.map(this.options.parsePlaceholderFn); // filter the possible placeholders by all placeholders found placeholders = placeholders.filter(function(placeholder) { return foundPlaceholders.includes(placeholder.getFormattedName()) || pForcedPlaceholders.includes(placeholder.getFormattedName()); }); return placeholders; } /** * Builds an object with the placeholder replacement data for multiple contacts * * @param {Array} pContactIds contact ids * @param {Placeholder[]} pAdditionalPlaceholders Additional placeholders that should be used. You can use placeholders with the * types FIXEDVALUE and CALLBACKFUNCTION if you want to calculate the replacement values yourself. * * @return {Object} Object containing the data. The structure is like {contactId : {placeholderName : replacementValue, ...}, ...} */ DocumentTemplate.prototype.getReplacementsByContactIds = function (pContactIds, pAdditionalPlaceholders) { var placeholders = this._getRequiredPlaceholders(["{@firstname@}", "{@lastname@}"], pAdditionalPlaceholders); var contactPlaceholders = []; var additionalPlaceholders = {}; var isUserRequired = false; placeholders.forEach(function (placeholder) { switch (placeholder.type) { case Placeholder.types.ADDRESSFORMAT: case Placeholder.types.SQLPART: case Placeholder.types.SQLPARTFUNCTION: contactPlaceholders.push(placeholder); break; case Placeholder.types.FIXEDVALUE: case Placeholder.types.CALLBACKFUNCTION: additionalPlaceholders[placeholder.getFormattedName()] = placeholder; } }); var contactIdPlaceholder = new Placeholder("contactId", Placeholder.types.SQLPART, "CONTACT.CONTACTID"); contactPlaceholders = [contactIdPlaceholder].concat(contactPlaceholders); var addressData = getAddressesData(pContactIds, contactPlaceholders, EmployeeUtils.getCurrentContactId()); //TODO: add sender selection var replacements = {}; var placeholderNames = addressData[0]; var contactIdIndex = placeholderNames.indexOf(contactIdPlaceholder.toString()); for (let i = 1; i < addressData.length; i++) { let contactId = addressData[i][contactIdIndex]; if (!(contactId in replacements)) replacements[contactId] = {}; placeholderNames.forEach(function (placeholderName, ii) { replacements[contactId][placeholderName] = addressData[i][ii]; }); for (let placeholderName in additionalPlaceholders) { var placeholder = additionalPlaceholders[placeholderName]; if (placeholder.type === Placeholder.types.FIXEDVALUE) replacements[contactId][placeholderName] = placeholder.valueDefinition; else if (placeholder.type === Placeholder.types.CALLBACKFUNCTION) replacements[contactId][placeholderName] = placeholder.valueDefinition.call(this, contactId); } } return replacements; } /* * replaces a given Odt-File on the server and returns the replaced base64-file * * @param {Object} pReplacements map of placeholders and replacements * @param {Array} pTableData * * @return {String} base64-encoded replaced file * * @private */ DocumentTemplate.prototype._getReplacedODT = function (pReplacements, pTableData) { var filename = this.filename; if (!filename) filename = "dummyname.odt"; var that = this; //save the file on the server so it can be unzipped via pack.getFromZip var serverFilePath = vars.get("$sys.servertemp") + "/clientid_" + (vars.exists("$sys.clientid") ? vars.get("$sys.clientid") : 0) + "/" + util.getNewUUID() + "/" + filename.replace(/\\/g, "/"); fileIO.storeData(serverFilePath, this.content, util.DATA_BINARY, false); var replacedFileData = null; try { if (!_replaceODTFile(pReplacements, serverFilePath, pTableData)) return null; replacedFileData = fileIO.getData(serverFilePath, util.DATA_BINARY); } finally { fileIO.remove(serverFilePath); } return replacedFileData; /** * replaces placeholders in a odt file * * @param {Object} pReplacements replacement object * @param {String} pODTFileName filename of the odt * @param {Array} pTableData * * @return {Boolean} */ function _replaceODTFile (pReplacements, pODTFileName, pTableData) { var senderRelId = EmployeeUtils.getCurrentContactId(); if (senderRelId == null) return false; if (!Array.isArray(pReplacements)) pReplacements = [pReplacements]; if (!pTableData) pTableData = []; var tablePlaceholders = []; if (pTableData.length > 0) { //pTableData[0] = first document tablePlaceholders = pTableData[0].map(function (tblData) { if (tblData && tblData.length > 0) return new Set(Object.keys(tblData[0])); //tblData[0] = first row return new Set(); }); } if (pReplacements.length !== 0) { //replace placeholders in content.xml var contentXml = util.decodeBase64String(pack.getFromZip(pODTFileName, "content.xml")); var bodybegin = contentXml.indexOf("<office:body>"); var bodyend = contentXml.indexOf("</office:body>") + 14; var bodyTemplate = contentXml.slice(bodybegin, bodyend); var fullBody = ""; //body that contains all pages (required when the replacing is done for several contacts) var beforeBody = contentXml.slice(0, bodybegin); var afterBody = contentXml.slice(bodyend); for (let i = 0, l = pReplacements.length; i < l; i++) { let replacements = pReplacements[i]; let currentBody = bodyTemplate; /* This only works if the text of the placeholders in the odt were not edited since they were written. * If you edit the odt and change a placeholder (for example: you change '{@addres@}' to '{@address@}'), * the text is saved in different XML tags and won't be replaced correctly. */ for (let placeholder in replacements) { currentBody = StringUtils.replaceAll(currentBody, placeholder, replacements[placeholder].replace(/\n/ig, "<text:line-break/>").replace(/&/ig, "&"), "ig"); } let tables = pTableData[i] || []; let tableEnd = 0; //for (let tblIndex = 0; tblIndex < tables.length; tblIndex++) //iterate over all tables in the document if (tables.length > 0) { let hasMoreTables = currentBody.includes("</table:table>"); for (let tblI = 0; tblI < 10 && hasMoreTables; tblI++) { tableEnd = currentBody.indexOf("</table:table>", tableEnd); if (tableEnd !== -1) //stop if there is no table { tableEnd += 14; let rowBegin = currentBody.slice(0, tableEnd).lastIndexOf("<table:table-row"); let rowEnd = currentBody.indexOf("</table:table-row>", rowBegin) + 18; let tableRow = currentBody.slice(rowBegin, rowEnd); let rowPlaceholders = tableRow.match(PlaceholderUtils.getRegexpMatchAll()); //find the table data that contains all required placeholders let tableDataIndex = !rowPlaceholders ? -1 : tablePlaceholders.findIndex(function (placeholderSet) { return rowPlaceholders.every(function (placeholderName) { return placeholderSet.has(placeholderName); }); }); if (tableDataIndex !== -1) { let afterTable = currentBody.slice(rowEnd); currentBody = currentBody.slice(0, rowBegin); tableEnd -= tableRow.length; let tableData = tables[tableDataIndex]; for (let rowIndex = 0; rowIndex < tableData.length; rowIndex++) { let tableRowData = tableData[rowIndex]; let replacedRow = that._replaceText(tableRow, tableRowData); currentBody += replacedRow; tableEnd += replacedRow.length; } currentBody += afterTable; } } else hasMoreTables = false; } } fullBody += currentBody; } contentXml = beforeBody + fullBody + afterBody; pack.addToZip(pODTFileName, "content.xml", util.encodeBase64String(contentXml)); //replace placeholders in styles.xml var styles = util.decodeBase64String(pack.getFromZip(pODTFileName, "styles.xml")); for (let placeholder in pReplacements[0]) { styles = StringUtils.replaceAll(styles, placeholder, pReplacements[0][placeholder].replace(/\n/ig, "<text:line-break/>").replace(/&/ig, "&"), "ig"); } pack.addToZip(pODTFileName, "styles.xml", util.encodeBase64String(styles)); return true; } return false; } } /* * This function is used to replace placeholders via DocXTemplater * * @param {Object} pReplacements - Must contain an object, which holds the placeholders * * @return {String} returns the modified document in a BASE64 coded string * * @private */ DocumentTemplate.prototype._getReplacedDOCX = function (pReplacements) { var replacements = {}; var startDelimiter = this.options.startDelimiter; var endDelimiter = this.options.endDelimiter; for (let placeholder in pReplacements) //removes the prefix and postfix, the process needs it like this replacements[placeholder.slice(startDelimiter.length, -endDelimiter.length)] = pReplacements[placeholder]; var documentData = DocxtemplaterUtils.generateDocument(this.content, replacements, startDelimiter, endDelimiter); return documentData; } /** * functions for working with letters (mails) */ function LetterUtils () {} /** * opens a new letter * * @param {String} pContactId id of the contact to fetch the data from * @param {String} pComingFrom source from where you started (e.g. "PERSON", "ORGANISATION" ) */ LetterUtils.openNewLetter = function (pContactId, pComingFrom) { var params = { "ContactId_param" : pContactId, "ComingFrom_param" : pComingFrom }; neon.openContext("Letter", "LetterEdit_view", null, neon.OPERATINGSTATE_VIEW, params); } /** * utility functions for the DocumentTemplate_entity */ function DocumentTemplateUtils () {} /** * if pText is provided, it is used as template, otherwise pFileUpload * * @param {FileUpload} pFileUpload upload value * @param {String} pKind kind of template * @param {String} pText text input * @param {String} pClassification the classification type. Used if pText is not empty. Defines if it is saved as txt or html. * @param {String} pTemplateName name of the template * @return {FileUpload} a FileUpload object with the data */ DocumentTemplateUtils.chooseSuppliedTemplate = function (pFileUpload, pKind, pText, pClassification, pTemplateName) { if (pFileUpload.isFilled() && pText != "") { // use fileUpload but use the custom text as bindata pFileUpload.bindata = util.encodeBase64String(pText); } else if (!pFileUpload.isFilled() && pText != "" && pKind == $KeywordRegistry.documentTemplateType$textModular() || pKind == $KeywordRegistry.documentTemplateType$mail()) // edit is only allowed in modular templates { pFileUpload.filename = pTemplateName; // if it is a htmlTemplate save it with the html extension if (pClassification == $KeywordRegistry.documentTemplateTypeCategory$htmlTemplate()) pFileUpload.fileExtension = "html"; else pFileUpload.fileExtension = "txt"; pFileUpload.bindata = util.encodeBase64String(pText); } var fileUploadType = DocumentTemplate.types.fromMimeType(pFileUpload.mimeType); // treat txt as html for emails if (pFileUpload.fileExtension == "txt" && pKind == $KeywordRegistry.documentTemplateType$mail()) { pFileUpload.fileExtension = "html"; // convert text to html pFileUpload.bindata = util.encodeBase64String(text.text2html(util.decodeBase64String(pFileUpload.bindata), true)); } return pFileUpload; } /** * inserts a template from a document template into ASYS_BINARIES */ DocumentTemplateUtils.insertTemplateData = function (pTemplateId, pFileUpload, pKind, pText, pClassification, pTemplateName) { pFileUpload = DocumentTemplateUtils.chooseSuppliedTemplate(pFileUpload, pKind, pText, pClassification, pTemplateName) if (pFileUpload.isFilled()) { db.insertBinary("DOCUMENTTEMPLATE", "DOCUMENT", pTemplateId, "", pFileUpload.bindata, pFileUpload.filename, "", "TEMPLATE", SqlUtils.getBinariesAlias()); } } /** * updates a template from a document template in ASYS_BINARIES */ DocumentTemplateUtils.updateTemplateData = function (pTemplateId, pFileUpload, pKind, pText, pClassification, pTemplateName) { pFileUpload = DocumentTemplateUtils.chooseSuppliedTemplate(pFileUpload, pKind, pText, pClassification, pTemplateName) if (pFileUpload.isFilled()) { var assignmentTable = "DOCUMENTTEMPLATE"; var assignmentName= "DOCUMENT"; var keyword = "TEMPLATE"; var binMeta = db.getBinaryMetadata(assignmentTable, assignmentName, pTemplateId, false, SqlUtils.getBinariesAlias(), keyword); if (binMeta.length == 0) SingleBinaryUtils.insert(assignmentTable, assignmentName, pTemplateId, pFileUpload.bindata, pFileUpload.filename, null, "TEMPLATE", SqlUtils.getBinariesAlias()); else db.updateBinary(binMeta[0][db.BINARY_ID], "", pFileUpload.bindata, pFileUpload.filename, "", keyword, SqlUtils.getBinariesAlias()); } } /** * loads content from a fileUpload or if it's empty from a template in ASYS_BINARIES * @param {String} pTemplateId the id of the template * @param {FileUpload} pFileUpload upload object * * @return {String[]} [content, type] or ["", type] or ["", null] * Content is only set, if it is HTML, TXT or EML */ DocumentTemplateUtils.getTemplateContent = function (pTemplateId, pFileUpload) { var type; var bindata; if (pFileUpload.isFilled()) { type = DocumentTemplate.types.fromFileExtension(pFileUpload.fileExtension); bindata = pFileUpload.bindata } else { var template = DocumentTemplateUtils.getTemplate(pTemplateId, false); if (template != null) { bindata = template.content; type = template.type; } } if (type == DocumentTemplate.types.HTML || type == DocumentTemplate.types.TXT || type == DocumentTemplate.types.EML) { return [util.decodeBase64String(bindata), type]; } return ["", type]; } /** * loads a template * @param {String} pTemplateId the id of the template * @param {Boolean} pResolveSubtemplates true, if subtemplates should be resolved * * @return {DocumentTemplate} */ DocumentTemplateUtils.getTemplate = function (pTemplateId, pResolveSubtemplates) { var assignmentTable = "DOCUMENTTEMPLATE"; var assignmentName= "DOCUMENT"; var keyword = "TEMPLATE"; var binMeta = db.getBinaryMetadata(assignmentTable, assignmentName, pTemplateId, false, SqlUtils.getBinariesAlias(), keyword); if (binMeta.length != 0) { return new DocumentTemplate( db.getBinaryContent(binMeta[0][db.BINARY_ID], SqlUtils.getBinariesAlias()), DocumentTemplate.types.fromBinaryMetadata(binMeta[0]), binMeta[0][db.BINARY_FILENAME], pResolveSubtemplates, pTemplateId); } return null; } /** * loads the mimetype from a fileUpload or if it's empty from a template in ASYS_BINARIES * @param {String} pTemplateId the id of the template * @param {FileUpload} pFileUpload upload object * * @return {String} mimetype */ DocumentTemplateUtils.getMimeType = function (pTemplateId, pFileUpload) { var type; if (pFileUpload && pFileUpload.isFilled()) { type = pFileUpload.mimeType; } else { var assignmentTable = "DOCUMENTTEMPLATE"; var assignmentName= "DOCUMENT"; var keyword = "TEMPLATE"; var binMeta = db.getBinaryMetadata(assignmentTable, assignmentName, pTemplateId, false, SqlUtils.getBinariesAlias(), keyword); if (binMeta.length != 0) { let binMeta = db.getBinaryMetadata(assignmentTable, assignmentName, pTemplateId, false, SqlUtils.getBinariesAlias(), keyword); type = binMeta[0][db.BINARY_MIMETYPE]; } } return type; } /** * loads the type from a fileUpload or if it's empty from a template in ASYS_BINARIES * @param {String} pTemplateId the id of the template * @param {FileUpload} pFileUpload upload object * * @return {String} type via DocumentTemplate.types or null */ DocumentTemplateUtils.getContentType = function (pTemplateId, pFileUpload) { var type; if (pFileUpload.isFilled()) { type = DocumentTemplate.types.fromFileExtension(pFileUpload.fileExtension); } else { var assignmentTable = "DOCUMENTTEMPLATE"; var assignmentName= "DOCUMENT"; var keyword = "TEMPLATE"; var binMeta = db.getBinaryMetadata(assignmentTable, assignmentName, pTemplateId, false, SqlUtils.getBinariesAlias(), keyword); if (binMeta.length != 0) { let binMeta = db.getBinaryMetadata(assignmentTable, assignmentName, pTemplateId, false, SqlUtils.getBinariesAlias(), keyword); type = DocumentTemplate.types.fromBinaryMetadata(binMeta[0]); } } return type; }