Something went wrong on our end
-
Sebastian Pongratz authored
[Projekt: xRM-Sales][TicketNr.: 1082084][Exception bei "Show Classification" wenn keine Klassifizierung angelegt]
Sebastian Pongratz authored[Projekt: xRM-Sales][TicketNr.: 1082084][Exception bei "Show Classification" wenn keine Klassifizierung angelegt]
process.js 21.01 KiB
import("system.logging");
import("system.db");
import("system.mail");
import("system.text");
import("system.vars");
import("system.util");
import("system.tools");
import("system.question");
import("system.datetime");
import("system.translate");
import("Sql_lib");
import("Util_lib");
import("Contact_lib");
import("Keyword_lib");
import("Employee_lib");
import("EmailUtil_lib");
import("ActivityTask_lib");
import("KeywordRegistry_basic");
/**
* Class to handle incoming emails.
*
* @param {Object} pMail <p>
* The mail object.
* @return {IncomingEmailExecutor} <p>
* Returns an IncomingEmailExecutor object.
*/
function IncomingEmailExecutor(pMail)
{
//whenver this function is called it may not be in a context where a alias is given: the mail-importing-entity for example has no alias.
//therefore set it here manually
this._alias = null;
this.locale = null;//this is maybe set later when determining an affected user for history
this.setAlias();
this.rawMail = pMail;
this.mailSubject = this.rawMail[mail.MAIL_SUBJECT] || "";
this.mailSender = this.rawMail[mail.MAIL_SENDER];
this.mailSentDate = this.rawMail[mail.MAIL_SENTDATE];
this.filename = this.rawMail.filename;
var mailRecipientsTo = this.rawMail[mail.RECIPIENT_TO].split(";");
var mailRecipientsCc = this.rawMail[mail.RECIPIENT_CC].split(";");
var mailRecipientsBcc = this.rawMail[mail.RECIPIENT_BCC].split(";");
this.mailRecipients = mailRecipientsTo.concat(mailRecipientsCc, mailRecipientsBcc).filter(Utils.isNotNullOrEmptyString);
this._senderInfo = null;
//activity data and failbackActivityData will be merged later to get all the data we need
//we always want to prefer contacts that are active to those who are inactive (that applies to the sender and to the recipients)
this.activityData = {
links: []
};
this.failbackActivityData = {
employeeContactId: "",
direction: $KeywordRegistry.activityDirection$incoming()
};
this._emailProcessors = [];
}
IncomingEmailExecutor.prototype.attachProcessor = function (pEmailProcessor)
{
this._emailProcessors.push(pEmailProcessor);
}
/**
* Sets the contact which will later used as activity responsible.
*
* @param {String} pContactId <p>
* The contac id.
* @param {String} pLanguageIso3 <p>
* The contac id.
* @param {Boolean} pSetAsFailback <p>
* In case its true, then .
* @return {Void} <p>
*/
IncomingEmailExecutor.prototype.setActivityEmployeeContact = function(pContactId, pLanguageIso3, pSetAsFailback)
{
//autodetect language if not given
if (pLanguageIso3 == undefined)
{
var lang = newSelect("CONTACT.ISOLANGUAGE", this._alias)
.from("CONTACT")
.where("CONTACT.CONTACTID", pContactId)
.cell();
if (lang)
{
pLanguageIso3 = lang;
}
}
if (pSetAsFailback)
{
this.failbackActivityData.employeeContactId = pContactId;
this.failbackActivityData.employeeContactLanguage = pLanguageIso3;
}
else
{
this.activityData.employeeContactId = pContactId;
this.activityData.links.push(["Person", pContactId]);
this.activityData.employeeContactLanguage = pLanguageIso3;
}
}
/**
* Sets the database alias.
*
* @param {String} pAlias <p>
* The name of database alias (e.g.: Data_alias).
* @return {Void} <p>
*/
IncomingEmailExecutor.prototype.setAlias = function(pAlias)
{
this._alias = pAlias || db.getCurrentAlias();
}
/**
* Returns the mail-text as html.
*
* @return {String} <p>
*/
IncomingEmailExecutor.prototype.getMailtextAsHtml = function()
{
var textInfos = [
translate.withArguments("Sender: %0", [this.rawMail[mail.MAIL_SENDER]], this.locale),
translate.withArguments("Recipients: %0", [this.mailRecipients.join(", ")], this.locale)
];
var attachmentInfos = mail.getAttachmentInfos(this.rawMail);
var attachmentCount = attachmentInfos.length;
if (attachmentCount == 0)
{
textInfos.push(translate.text("no attachments", this.locale));
}
else
{
if (attachmentCount == 1)
textInfos.push(translate.withArguments("%0 attachment:", [attachmentCount], this.locale));
else
textInfos.push(translate.withArguments("%0 attachments:", [attachmentCount], this.locale));
var attachmentHtml = "";
var fileName;
for (var i = 0; i < attachmentCount; i++)
{
//don't use a <ul><li.......</ul> here since it does not look good in the client
[fileName] = text.decodeMS(attachmentInfos[i]);
attachmentHtml += "\n" + (i > 0 ? "<br/>" : "") + "● " + fileName;
}
textInfos.push(attachmentHtml);
}
textInfos = textInfos.map(function (el)
{
return "<p>" + el + "</p><br>";
});
//since the activity has always and only a HTML-content-field we need to ensure that there will be always a HTML-content
if (this.rawMail[mail.MAIL_HTMLTEXT])
textInfos.push("<br/>\n" + this.rawMail[mail.MAIL_HTMLTEXT]);
else
textInfos.push("<br/>\n" + text.text2html(this.rawMail[mail.MAIL_TEXT], true));
var res = textInfos.join("\n");
return res;
}
/**
* Returns the senders info. <br>
* <u><i>(contact-id, status, person-id, iso-language)</i></u>
*
* @return {Array} <p>
* Returns an object containing the senders info.
*/
IncomingEmailExecutor.prototype.getSenderInfo = function()
{
if (this._senderInfo == null)
{
this._senderInfo = this.mailSender ? IncomingEmailExecutor.getContactDataByEmail(this.mailSender, this._alias) : [];
}
return this._senderInfo;
}
/**
* Inserts the mail as unlinked mail into AB_UNLINKEDMAIL.
*
* @return {Object} <p>
* Returns an object containing the uuid (unlinkedMailId: unlinkedMailId).
*/
IncomingEmailExecutor.prototype.insertUnlinkedMail = function()
{
var unlinkedMailId = util.getNewUUID();
var cols = ["AB_UNLINKEDMAILID", "SUBJECT", "SENTDATE", "SENDER", "RECIPIENTS", "MAIL", "USER_NEW", "DATE_NEW"];
var vals = [unlinkedMailId, this.mailSubject, this.mailSentDate, this.mailSender, this.mailRecipients.join(", "), mail.toRFC(this.rawMail),
vars.get("$sys.user"), datetime.date()];
db.insertData("AB_UNLINKEDMAIL", cols, null, vals, this._alias);
return {
unlinkedMailId: unlinkedMailId
};
}
/**
* Returns whether the mail is linkable or not.<p>
* <i><u>(a e-mail is always then linkable when the sender has an contact in the system)</u></i>
*
* @return {Boolean|Null} <p>
* True in case it's unlinkable, otherwise false and in case an error occured
* null will be returned.
*/
IncomingEmailExecutor.prototype.isUnlinkable = function()
{
var isUnlinkable;
if (this.getSenderInfo())
{
isUnlinkable = this.getSenderInfo().length == 0;
}
else
{
isUnlinkable = null;
}
return isUnlinkable;
}
/**
* In case the given e-mail has an corresponding contact in the system, it will be returned
* <u><i>otherwise</i></u> an empty array will be returned.
*
* @param {String} pMailAddress <p>
* The mail address which will be used to look up.
* @param {String} pAlias <p>
* The alias which will be used for db interaction.
* @return {Array|Null} <p>
* In case the contact was found, the contact id, status, person id, isolang will be returned.
* If not an empty array will be returned and in case an error occured null will be returned.
*/
IncomingEmailExecutor.getContactDataByEmail = function(pMailAddress, pAlias)
{
var mailAddress = null;
try
{
mailAddress = EmailUtils.extractAddress(pMailAddress).toUpperCase();
mailAddress = mailAddress ? newSelect("CONTACT.CONTACTID, CONTACT.STATUS, CONTACT.PERSON_ID, CONTACT.ISOLANGUAGE", pAlias)
.from("COMMUNICATION")
.join("CONTACT", "COMMUNICATION.CONTACT_ID = CONTACT.CONTACTID")
.where("COMMUNICATION.ADDR", mailAddress, "upper(#) = ?")
.table()
: [];
}
catch(pException)
{
logging.log(translate.text("An error occured during getting contact data."), logging.ERROR);
logging.log(pException, logging.ERROR);
}
return mailAddress;
}
/**
* Creates an corresponding activity, matching to the processed mail.
*
* @param {Array[]} [pAdditionalLinks] <p>
* 2d-array which contains arrays which represents links.<br>
* <u>(0: contextname, 1: contactId)</u>
* @param {Boolean} pIsError <p>
* This variable is used, when importing mails trough the
* dashlet, for detecting whether an error is occured or not.
* If so, in the end the a corresponding message will appear
* in the webclient.
* @return {Array} <p>
* Array which contains the uuids of the newly created activities.
*/
IncomingEmailExecutor.prototype.createActivity = function(pAdditionalLinks, pIsError)
{
var recipient;
try
{
var sendersMail = mail.extractAddress(this.mailSender);
var isSenderInternalUser = tools.getUsersByAttribute(tools.EMAIL, [sendersMail]);
}
catch(pException)
{
logging.log(translate.text("An error occured during checking whether the sender is an internal user or not."),
logging.ERROR);
logging.log(pException, logging.ERROR);
}
var isRecipientInternalUser = this.mailRecipients.some(function(pRecipient){
var isRecipientInternal = false;
try
{
if (!Utils.isNullOrEmpty(tools.getUsersByAttribute(tools.EMAIL, [mail.extractAddress(pRecipient)]))
&& mail.extractAddress(pRecipient).indexOf("mailbridge") == -1)
{
recipient = mail.extractAddress(pRecipient);
isRecipientInternal = true;
}
}
catch(pException)
{
logging.log(translate.text("An error occured during checking whether at least one of the recipients is an internal user or not."),
logging.ERROR);
logging.log(pException, logging.ERROR);
}
return isRecipientInternal;
});
var senderContacts = {
prefered: [],
failback: []
};
if (Utils.toBoolean(vars.get("$sys.isserver")))
{
this.getSenderInfo().forEach(this._getProcessingFunction(true, senderContacts), this);
this.activityData.links = this.activityData.links.concat(senderContacts.prefered.length > 0 ? senderContacts.prefered :
senderContacts.failback);
}
for (var i = 0, l = this.mailRecipients.length; i < l; i++)
{
try
{
var recipientsMail = mail.extractAddress(this.mailRecipients[i]);
var isRecipientInternal = tools.getUsersByAttribute(tools.EMAIL, [recipientsMail]);
}
catch(pException)
{
logging.log(translate.text("An error occured during checking whether recipient (recipient: " + recipientsMail + ") is \n\
an internal user or not."), logging.ERROR);
logging.log(pException, logging.ERROR);
}
if (Utils.isNullOrEmpty(isRecipientInternal) && recipientsMail.indexOf("mailbridge") == -1)
{
try
{
var recipientsInfo = IncomingEmailExecutor.getContactDataByEmail(this.mailRecipients[i], this._alias);
}
catch(pException)
{
logging.log(translate.text("An error occured during getting recipients contact data."),
logging.ERROR);
logging.log(pException, logging.ERROR);
}
var recipientContacts = {
prefered: [],
failback: []
};
recipientsInfo.forEach(this._getProcessingFunction(false, recipientContacts), this);
this.activityData.links = this.activityData.links.concat(recipientContacts.prefered.length > 0 ? recipientContacts.prefered
: recipientContacts.failback);
}
}
var langIso3 = this.activityData.employeeContactLanguage || this.failbackActivityData.employeeContactLanguage;
var langIso2;
if (langIso3)
{
langIso2 = LanguageKeywordUtils.Iso2FromIso3(langIso3, this._alias);
if (langIso2)
{
this.locale = langIso2;
}
}
//collecting all the information and combine it for the creation
var activityDataForInsert = {
subject: this.mailSubject,
entrydate: this.mailSentDate,
content: this.getMailtextAsHtml(),
categoryKeywordId: $KeywordRegistry.activityCategory$mail(),
directionKeywordId: this.activityData.direction || this.failbackActivityData.direction
};
if (vars.get("$sys.isclient") == "true")
{
activityDataForInsert.responsibleContactId = EmployeeUtils.getCurrentContactId();
}
else
{
if (!Utils.isNullOrEmpty(isSenderInternalUser))
{
activityDataForInsert.responsibleContactId = ContactUtils.getContactIdByEmail(sendersMail) || "";
}
else if(isRecipientInternalUser)
{
activityDataForInsert.responsibleContactId = ContactUtils.getContactIdByEmail(recipient) || "";
}
else
{
activityDataForInsert.responsibleContactId = "";
}
}
var activityLinks = this.activityData.links || this.failbackActivityData.links;
if (pAdditionalLinks)
{
activityLinks = activityLinks.concat(pAdditionalLinks);
}
activityLinks = ArrayUtils.distinct2d(activityLinks);//TODO: better check before adding the elements into the array if it already exists there
var activityDocs = null;
try
{
activityDocs = [["Mail.eml", util.encodeBase64String(mail.toRFC(this.rawMail)), true]];
}
catch(pException)
{
logging.log(translate.text("An error occured during getting recipients contact data."),
logging.ERROR);
logging.log(pException, logging.ERROR);
}
var activityRes = ActivityUtils.insertNewActivity(activityDataForInsert, activityLinks, activityDocs, this._alias, this.mailSentDate);
return activityRes;
}
/**
* Deletes the unlinked mail with the given uuid.
*
* @param {String} pUnlinkedMailId <p>
* The uuid of the record which shall be deleted.<br>
* @return {Void} <p>
*/
IncomingEmailExecutor.prototype.deleteUnlinkedMail = function (pUnlinkedMailId)
{
newWhereIfSet("AB_UNLINKEDMAIL.AB_UNLINKEDMAILID", pUnlinkedMailId, undefined, undefined, this._alias)
.deleteData(true, "AB_UNLINKEDMAIL");
}
/**
* Process an incoming email (e.g.: from mailbridge, e-mail import dashlet..)
*
* @param {String} pUnlinkedMailId <p>
* The uuid of the record which shall be deleted.<br>
* @return {Void} <p>
*/
IncomingEmailExecutor.prototype.autoProcess = function(pUnlinkedMailId)
{
let tempResult = {
isUnlinkedMail: false,
isError: false
};
if (this.isUnlinkable())
{
tempResult.isUnlinkedMail = true;
if (pUnlinkedMailId)
{
return {
unlinkedMailId: pUnlinkedMailId
};
}
}
else if(this.isUnlinkable() == null)
{
tempResult.isError = true;
}
if (!tempResult.isError)
{
tempResult.activityId = this.createActivity().activityId;
this.deleteUnlinkedMail(pUnlinkedMailId);
this._emailProcessors.forEach(function (emailProcessor)
{
emailProcessor.process(this.rawMail);
}, this);
}
return tempResult;
}
/**
* Returns an anonymous function which processes ?
*
* @param {Boolean} pIsSender <p>
* Whether the contact is the sender or not.<br>
* @param {Array} pTargetArray <p>
* The uuid of the record which shall be deleted.<br>
* @return {Void} <p>
*/
IncomingEmailExecutor.prototype._getProcessingFunction = function(pIsSender, pTargetArray)
{
return function(contactInfoRow){
var [contactId, contactStatus, contactPersonId, languageIso3] = contactInfoRow;
//there *should* only exist no or one user per contactid, never two or more - so getUser (not getUsers) should be fine
var user = tools.getUserByAttribute(tools.CONTACTID, [contactId]);
var isEmployee = user != null;
var isContactActive = contactStatus == $KeywordRegistry.contactStatus$active();
//if a user was already found we can skip determining the correct user since only one user can be set as "RESPONSIBLE" in the activity
if (isEmployee && !this.activityData.employeeContactId)
{
var direction = pIsSender ? $KeywordRegistry.activityDirection$outgoing() : $KeywordRegistry.activityDirection$incoming();
if (isContactActive)
{
this.setActivityEmployeeContact(contactId, languageIso3);
this.activityData.direction = direction;
}
else
{
//if the user is inactive, we may find a better (=active) user later
this.setActivityEmployeeContact(contactId, languageIso3, true);
this.failbackActivityData.direction = direction;
}
}
else
{
var context = contactPersonId == "" ? "Organisation" : "Person"
if(context == "Person")//add Organisation to the Activity as Link
{
var orgData = newSelect(["CONTACT.CONTACTID", "CONTACT.STATUS"], this._alias)
.from("CONTACT")
.join("CONTACT", "anyContact.ORGANISATION_ID = CONTACT.ORGANISATION_ID and CONTACT.PERSON_ID is null", "anyContact")
.whereIfSet(["CONTACT", "CONTACTID", "anyContact"], contactId)
.arrayRow()
orgData[0] = orgData[0].replace(/\s/g, '');
if(orgData[0] != '0' && orgData[0] != '1')
{
if (orgData[1] == $KeywordRegistry.contactStatus$active())
{
pTargetArray["prefered"].push(["Organisation", orgData[0]]);
}
else
{
pTargetArray["failback"].push(["Organisation", orgData[0]]);
}
}
}
var link = [context, contactId];
if (isContactActive)
{
pTargetArray["prefered"].push(link);
}
else
{
pTargetArray["failback"].push(link);
}
}
};
}