Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 162 additions & 27 deletions datamodel.itop-standard-email-synchro.xml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@
<default_value/>
<is_null_allowed>true</is_null_allowed>
</field>
<field id="unknown_caller_rejection_reply_subject" xsi:type="AttributeString">
<sql>unknown_caller_rejection_reply_subject</sql>
<default_value/>
<is_null_allowed>true</is_null_allowed>
</field>
<field id="caller_default_values" xsi:type="AttributeText">
<sql>caller_default_values</sql>
<default_value/>
Expand Down Expand Up @@ -151,6 +156,27 @@
<default_value/>
<is_null_allowed>true</is_null_allowed>
</field>
<field id="closed_ticket_behavior" xsi:type="AttributeEnum">
<sql>closed_ticket_behavior</sql>
<values>
<value>error</value>
<value>process</value>
<value>new_ticket</value>
<value>update_ticket</value>
</values>
<default_value>new_ticket</default_value>
<is_null_allowed>false</is_null_allowed>
</field>
<field id="closed_ticket_reply_subject" xsi:type="AttributeString">
<sql>closed_ticket_reply_subject</sql>
<default_value/>
<is_null_allowed>true</is_null_allowed>
</field>
<field id="closed_ticket_reply" xsi:type="AttributeText">
<sql>closed_ticket_reply</sql>
<default_value/>
<is_null_allowed>true</is_null_allowed>
</field>
</fields>
<methods>
<method id="DisplayBareRelations">
Expand Down Expand Up @@ -251,7 +277,7 @@ EOF
<type>Overload-DBObject</type>
<code><![CDATA[ public function ProcessNewEmail(EmailSource $oSource, $index, EmailMessage $oEmail)
{
$this->sLastError = null;
$this->sLastError = null;
$this->Trace("Processing new eMail (index = $index)");
$oTicket = null;
if ($this->IsUndesired($oEmail))
Expand Down Expand Up @@ -427,15 +453,8 @@ EOF
if (is_null($oTicket)) {
$this->Trace("iTop Simple Email Synchro: WARNING the message ({$oEmail->sUIDL}) got a EmailReplica#{$oEmailReplica->GetKey()}
with the ticket_id={$oEmailReplica->Get('ticket_id')} but the ticket does not exist.");
} elseif (MetaModel::IsValidAttCode(get_class($oTicket), "operational_status") && $oTicket->Get("operational_status") === "closed") {
$this->Trace("iTop Simple Email Synchro: WARNING the message ({$oEmail->sUIDL}) is related to the CLOSED (operational_status) ticket {$oTicket->Get('id')}");
return null;
} elseif (MetaModel::IsValidAttCode(get_class($oTicket), "status") && $oTicket->Get("status") === "closed") {
$this->Trace("iTop Simple Email Synchro: WARNING the message ({$oEmail->sUIDL}) is related to the CLOSED (status) ticket {$oTicket->Get('id')}");
return null;
}
else {
return $oTicket;
} else {
return $this->CheckTicketStatus($oTicket);
}
}
}
Expand All @@ -444,27 +463,77 @@ EOF
// No associated ticket found by parsing the headers, check
// if the subject does not match a specific pattern
$sPattern = $this->FixPattern($this->Get('title_pattern'));
if(($sPattern != '') && (preg_match($sPattern, $oEmail->sSubject, $aMatches)))
{
if(($sPattern != '') && (preg_match($sPattern, $oEmail->sSubject, $aMatches))) {
$oFilter = DBSearch::FromOQL('SELECT Ticket WHERE ref = :ref');
foreach ($aMatches as $sMatch)
{
if (!empty($sMatch))
{
foreach ($aMatches as $sMatch) {
if (!empty($sMatch)) {
$this->Trace("iTop Simple Email Synchro: Retrieving ticket $sMatch (match by subject pattern)...");
$oSet = new DBObjectSet($oFilter, array(), array('ref' => $sMatch));
$oTicket = $oSet->Fetch();
if ($oTicket)
{
break;
if ($oTicket) {
return $this->CheckTicketStatus($oTicket,$oEmail);
}
}
}
}
}
}

return $oTicket;
}]]></code>
</method>
<method id="CheckTicketStatus">
<comment><![CDATA[/**
* Check id ticket is closed and apply the appropriate behavior
* @param $oTicket
* @return string
*/]]></comment>
<static>false</static>
<access>protected</access>
<type>Overload-DBObject</type>
<code><![CDATA[ protected function CheckTicketStatus($oTicket, $oEmail)
{
if ((MetaModel::IsValidAttCode(get_class($oTicket), "operational_status") && $oTicket->Get("operational_status") === "closed") ||
(MetaModel::IsValidAttCode(get_class($oTicket), "status") && $oTicket->Get("status") === "closed")) {
//send mail if configured to do so
$sReplyBody = $this->Get('closed_ticket_reply');
$sReplySubject = $this->Get('closed_ticket_reply_subject');
if (utils::IsNotNullOrEmptyString($sReplyBody)|| utils::IsNotNullOrEmptyString($sReplySubject) ) {
$sContactQuery = 'SELECT Person WHERE email = :email';
$oSet = new DBObjectSet(DBObjectSearch::FromOQL($sContactQuery), array(), array('email' => $oEmail->sCallerEmail));
$oPerson = $oSet->Fetch();
$aPlaceholders = [
'$subject$',
'$senderEmail$',
'$senderName$',
'$senderFirstName$'
];
$aReplacement = [
$oEmail->sSubject,
$oEmail->sCallerEmail,
$oPerson->Get('name'),
$oPerson->Get('first_name'),
];
//replace the only autorized placeholder $subject$
if (utils::IsNullOrEmptyString($sReplySubject)){
$sReplySubject = 'Re: '.$oEmail->sSubject;
} else {
$sReplySubject = str_replace($aPlaceholders,$aReplacement, $sReplySubject);
}
$sReplyBody = str_replace($aPlaceholders, $aReplacement, $sReplyBody);
$sReplyBody = str_replace("\n", "<br/>", $sReplyBody);
$sReplyTo = $oEmail->sCallerEmail;
$sReplyFrom = $oEmail->aTos[0]['email'];
$this->Trace("From: ".$sReplyFrom."\nTo: ".$sReplyTo."\n".$sReplySubject."\n\n".$sReplyBody);
$oEmail->oRawEmail->SendAsAttachment($sReplyTo, $sReplyFrom, $sReplySubject, $sReplyBody);
}

if ($this->Get('closed_ticket_behavior') === 'new_ticket') {
$this->Trace("iTop Simple Email Synchro: WARNING the message ({$oEmail->sUIDL}) is related to the CLOSED (operational_status) ticket {$oTicket->Get('id')}");
return null;
}
}
return $oTicket;
}]]></code>
</method>
<method id="CreateTicketFromEmail">
<comment><![CDATA[/**
* Actual creation of the ticket from the incoming email. Overload this method
Expand Down Expand Up @@ -536,7 +605,7 @@ EOF

// Insert the remaining attachments so that we know their ID and can reference them in the message's body
$aAddedAttachments = $this->AddAttachments($oTicket, $oEmail, true, $aIgnoredAttachments, $oCaller, $oUser); // Cannot insert them for real since the Ticket is not saved yet (we don't know its ID)
// we'll have to call UpdateAttachments once the ticket is properly saved
// we'll have to call UpdateAttachments once the ticket is properly saved
$oTicketDescriptionAttDef = MetaModel::GetAttributeDef(get_class($oTicket), 'description');
$bForPlainText = true; // Target format is plain text (by default)
if ($oTicketDescriptionAttDef instanceof AttributeHTML)
Expand Down Expand Up @@ -798,6 +867,31 @@ EOF
$this->SetNextAction(EmailProcessor::MARK_MESSAGE_AS_ERROR); // Keep the message in the mailbox, but marked as error
return;
}
if($this->Get('closed_ticket_behavior') === 'error'){
if ((MetaModel::IsValidAttCode(get_class($oTicket), "operational_status") && $oTicket->Get('operational_status') =='closed') || (MetaModel::IsValidAttCode(get_class($oTicket), "status") && $oTicket->Get("status") === "closed")) {
$this->sLastError='Ticket '.$oTicket->GetName().' is closed. Cannot update a closed ticket.';
return null;
}
}
if($this->Get('closed_ticket_behavior') === 'process'){
if ((MetaModel::IsValidAttCode(get_class($oTicket), "operational_status") && $oTicket->Get('operational_status') =='closed') || (MetaModel::IsValidAttCode(get_class($oTicket), "status") && $oTicket->Get("status") === "closed")) {
$this->Trace("iTop Simple Email Synchro: WARNING the message ({$oEmail->sUIDL}) is related to the CLOSED (operational_status) ticket {$oTicket->Get('id')}");
// process the mail as usual but do not update the ticket
if ($this->Get('email_storage') == 'delete') {
// Remove the processed message from the mailbox
$this->Trace("Ticket updated, deleting the source eMail '".$oEmail->sSubject."'");
$this->SetNextAction(EmailProcessor::DELETE_MESSAGE);
} else if ($this->Get('email_storage') == 'move') {
// Remove the processed message from the mailbox
$this->Trace("Ticket created, move the source eMail '".$oEmail->sSubject."'");
$this->SetNextAction(EmailProcessor::MOVE_MESSAGE);
} else {
// Keep the message in the mailbox
$this->SetNextAction(EmailProcessor::NO_ACTION);
}
return $oTicket;
}
}

// Try to extract what's new from the message's body
$this->Trace("iTop Simple Email Synchro: Updating the iTop ticket ".$oTicket->GetName()." from eMail '".$oEmail->sSubject."'");
Expand Down Expand Up @@ -1204,6 +1298,7 @@ EOF
<type>Overload-DBObject</type>
<code><![CDATA[ public function HandleError($oEmail, $sErrorCode, $oRawEmail = null, $sAdditionalErrorMessage = '')
{
$this->Trace(json_encode(\MyHelpers::get_callstack(1)));
$sTo = $this->Get('notify_errors_to');
$sFrom = $this->Get('notify_errors_from');
// The behavior is overriden in case of undesired_message
Expand All @@ -1226,15 +1321,29 @@ EOF

// if rejection reply
$sRejectionReply = $this->Get('unknown_caller_rejection_reply');
if (!empty($sRejectionReply) && $oRawEmail)
$sRejectionReplySubject = $this->Get('unknown_caller_rejection_reply_subject');
if ((utils::IsNotNullOrEmptyString($sRejectionReply) || utils::IsNotNullOrEmptyString($sRejectionReplySubject)) && $oRawEmail)
{
$sReplySubject = '[iTop] '.$oEmail->sSubject.' - '.$this->sLastError;
$aPlaceholders = [
'$subject$',
'$senderEmail$'
];
$aReplacement = [
$oEmail->sSubject,
$oEmail->sCallerEmail
];
if (utils::IsNullOrEmptyString($sRejectionReplySubject)){
$sRejectionReplySubject = '[iTop] '.$oEmail->sSubject.' - '.$this->sLastError;
} else {
$sRejectionReplySubject = str_replace($aPlaceholders, $aReplacement, $sRejectionReplySubject);
}
$sRejectionReply = str_replace($aPlaceholders, $aReplacement, $sRejectionReply);
$sReplyBody = str_replace("\n", "<br/>", $sRejectionReply);
$sReplyTo = $oEmail->sCallerEmail;
$aTo = $oRawEmail->GetTo();
$sReplyFrom = $aTo[0]['email'];
$this->Trace("From: ".$sReplyFrom."\nTo: ".$sReplyTo."\n".$sReplySubject."\n\n".$sReplyBody);
$oRawEmail->SendAsAttachment($sReplyTo, $sReplyFrom, $sReplySubject, $sReplyBody);
$this->Trace("From: ".$sReplyFrom."\nTo: ".$sReplyTo."\n".$sRejectionReplySubject."\n\n".$sReplyBody);
$oRawEmail->SendAsAttachment($sReplyTo, $sReplyFrom, $sRejectionReplySubject, $sReplyBody);

$this->sLastError .= " - Replied to sender on ".date('r');
}
Expand Down Expand Up @@ -1469,6 +1578,11 @@ EOF
<item id="target_folder">
<rank>30</rank>
</item>
</items>
</item>
<item id="fieldset:MailInbox:TicketProcessing">
<rank>20</rank>
<items>
<item id="target_class">
<rank>40</rank>
</item>
Expand All @@ -1486,12 +1600,33 @@ EOF
</item>
</items>
</item>
<item id="fieldset:MailInbox:ClosedTickets">
<rank>30</rank>
<items>
<item id="closed_ticket_behavior">
<rank>40</rank>
</item>
<item id="closed_ticket_reply_subject">
<rank>50</rank>
</item>
<item id="closed_ticket_reply">
<rank>60</rank>
</item>
</items>
</item>
</items>
</item>
<item id="col:col2">
<items>
<item id="fieldset:MailInbox:Caller">
<rank>20</rank>
<rank>10</rank>
<items>
<item id="unknown_caller_behavior">
<rank>10</rank>
</item>
<item id="unknown_caller_rejection_reply_subject">
<rank>14</rank>
</item>
<item id="unknown_caller_rejection_reply">
<rank>15</rank>
</item>
Expand Down
27 changes: 25 additions & 2 deletions dictionaries/cs.dict.itop-standard-email-synchro.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,22 @@
'Class:MailInboxStandard/Attribute:behavior/Value:both' => 'Create or Update Tickets~~',
'Class:MailInboxStandard/Attribute:behavior/Value:create_only' => 'Create new Tickets~~',
'Class:MailInboxStandard/Attribute:behavior/Value:update_only' => 'Update existing Tickets~~',
'Class:MailInboxStandard/Attribute:closed_ticket_behavior' => 'Behavior in case of closed ticket~~',
'Class:MailInboxStandard/Attribute:closed_ticket_behavior+' => 'In case of an update, it provides the behavior if the ticket is closed:
- Mark the mail as in error
- Mark the mail as processed, without updating the closed ticket
- Create a new ticket
- Update existing ticket. In this case, it\'s preferable to configure a "stimulus to apply" to reopen ticket.~~',
'Class:MailInboxStandard/Attribute:closed_ticket_behavior/Value:error' => 'Mark the mail as in error~~',
'Class:MailInboxStandard/Attribute:closed_ticket_behavior/Value:process' => 'Mark the mail as processed~~',
'Class:MailInboxStandard/Attribute:closed_ticket_behavior/Value:new_ticket' => 'Create a new Ticket~~',
'Class:MailInboxStandard/Attribute:closed_ticket_behavior/Value:update_ticket' => 'Update the Ticket~~',

'Class:MailInboxStandard/Attribute:closed_ticket_reply_subject' => 'Closed ticket autoreply subject~~',
'Class:MailInboxStandard/Attribute:closed_ticket_reply_subject+' => 'If no subject is provided, then the default subject will be: "Re:$subject$"
You can use the following placeholders : $subject$, $senderName$, $senderFirstName$ and $senderEmail$ in the email subject.~~',


'Class:MailInboxStandard/Attribute:caller_default_values' => 'New Person\'s Default Values~~',
'Class:MailInboxStandard/Attribute:caller_default_values+' => 'Provide a value for all mandatory fields of a Person, except email.
Use one field initialization per line, in the format: <field_code>:<value>~~',
Expand Down Expand Up @@ -97,12 +113,19 @@
- Reject the eMail: flag the eMail in error and reply to the sender with the content of "Unknown senders rejection reply"~~',
'Class:MailInboxStandard/Attribute:unknown_caller_behavior/Value:create_contact' => 'Create a new Person~~',
'Class:MailInboxStandard/Attribute:unknown_caller_behavior/Value:reject_email' => 'Reject the eMail~~',
'Class:MailInboxStandard/Attribute:unknown_caller_rejection_reply' => 'Unknown senders rejection reply~~',
'Class:MailInboxStandard/Attribute:unknown_caller_rejection_reply_subject' => 'Unknown senders autoreply subject',
'Class:MailInboxStandard/Attribute:unknown_caller_rejection_reply_subject+' => 'If no subject is provided, then the default subject will be: "[iTop]$subject$ - Unknown caller ($senderEmail$)"
You can use the following placeholders : $subject$ and $senderEmail$ in the email subject.',

'Class:MailInboxStandard/Attribute:unknown_caller_rejection_reply' => 'Unknown senders rejection reply message',
'Class:MailInboxStandard/Attribute:unknown_caller_rejection_reply+' => 'Optional reply to sender used with option “Reject the eMail”.
Unknown senders are email addresses which do not correspond to any Person in '.ITOP_APPLICATION_SHORT.'.
If this field is left empty, then no message is sent to unknown senders~~',
If this field is left empty, then no message is sent to unknown senders
You can use the following placeholders : $subject$ and $senderEmail$ in the message.~~',
'MailInbox:Behavior' => 'Behavior on Incoming eMails~~',
'MailInbox:Caller' => 'Unknown Senders~~',
'MailInbox:TicketProcessing' => 'Ticket Processing~~',
'MailInbox:ClosedTickets' => 'Closed Tickets~~',
'MailInbox:Errors' => 'Emails in Error~~',
'MailInbox:NoSubject' => 'No subject~~',
'MailInbox:OtherContacts' => 'Behavior for Additional Contacts~~',
Expand Down
Loading