.
* ---------------------------------------------------------------------
*/
if (!defined('GLPI_ROOT')) {
die("Sorry. You can't access this file directly");
}
use LitEmoji\LitEmoji;
use Laminas\Mail\Address;
use Laminas\Mail\Header\AbstractAddressList;
use Laminas\Mail\Header\ContentDisposition;
use Laminas\Mail\Header\ContentType;
use Laminas\Mail\Storage\Message;
use Laminas\Mail\Storage;
/**
* MailCollector class
*
* Merge with collect GLPI system after big modification in it
*
* modif and debug by INDEPNET Development Team.
* Original class ReceiveMail 1.0 by Mitul Koradia Created: 01-03-2006
* Description: Reciving mail With Attechment
* Email: mitulkoradia@gmail.com
**/
class MailCollector extends CommonDBTM {
// Specific one
/**
* IMAP / POP connection
* @var Laminas\Mail\Storage\AbstractStorage
*/
private $storage;
/// UID of the current message
public $uid = -1;
/// structure used to store files attached to a mail
public $files;
/// structure used to store alt files attached to a mail
public $altfiles;
/// Tag used to recognize embedded images of a mail
public $tags;
/// Message to add to body to build ticket
public $addtobody;
/// Number of fetched emails
public $fetch_emails = 0;
/// Maximum number of emails to fetch : default to 10
public $maxfetch_emails = 10;
/// array of indexes -> uid for messages
public $messages_uid = [];
/// Max size for attached files
public $filesize_max = 0;
/**
* Flag that tells wheter the body is in HTML format or not.
* @var string
*/
private $body_is_html = false;
public $dohistory = true;
static $rightname = 'config';
// Destination folder
const REFUSED_FOLDER = 'refused';
const ACCEPTED_FOLDER = 'accepted';
// Values for requester_field
const REQUESTER_FIELD_FROM = 0;
const REQUESTER_FIELD_REPLY_TO = 1;
static $undisclosedFields = [
'passwd',
];
static function getTypeName($nb = 0) {
return _n('Receiver', 'Receivers', $nb);
}
static function canCreate() {
return static::canUpdate();
}
static function canPurge() {
return static::canUpdate();
}
static function getAdditionalMenuOptions() {
if (static::canView()) {
$options['options']['notimportedemail']['links']['search']
= '/front/notimportedemail.php';
return $options;
}
return false;
}
function post_getEmpty() {
global $CFG_GLPI;
$this->fields['filesize_max'] = $CFG_GLPI['default_mailcollector_filesize_max'];
$this->fields['is_active'] = 1;
}
public function prepareInput(array $input, $mode = 'add') :array {
if ('add' === $mode && !isset($input['name']) || empty($input['name'])) {
Session::addMessageAfterRedirect(__('Invalid email address'), false, ERROR);
}
if (isset($input["passwd"])) {
if (empty($input["passwd"])) {
unset($input["passwd"]);
} else {
$input["passwd"] = Toolbox::sodiumEncrypt(stripslashes($input["passwd"]));
}
}
if (isset($input['mail_server']) && !empty($input['mail_server'])) {
$input["host"] = Toolbox::constructMailServerConfig($input);
}
if (isset($input['name']) && !NotificationMailing::isUserAddressValid($input['name'])) {
Session::addMessageAfterRedirect(__('Invalid email address'), false, ERROR);
}
return $input;
}
function prepareInputForUpdate($input) {
$input = $this->prepareInput($input, 'update');
if (isset($input["_blank_passwd"]) && $input["_blank_passwd"]) {
$input['passwd'] = '';
}
return $input;
}
function prepareInputForAdd($input) {
$input = $this->prepareInput($input, 'add');
return $input;
}
function defineTabs($options = []) {
$ong = [];
$this->addDefaultFormTab($ong);
$this->addStandardTab(__CLASS__, $ong, $options);
$this->addImpactTab($ong, $options);
$this->addStandardTab('Log', $ong, $options);
return $ong;
}
function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) {
if (!$withtemplate) {
switch ($item->getType()) {
case __CLASS__ :
return _n('Action', 'Actions', Session::getPluralNumber());
}
}
return '';
}
/**
* @param $item CommonGLPI object
* @param $tabnum (default 1
* @param $withtemplate (default 0)
**/
static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) {
if ($item->getType() == __CLASS__) {
$item->showGetMessageForm($item->getID());
}
return true;
}
/**
* Print the mailgate form
*
* @param $ID integer Id of the item to print
* @param $options array
* - target filename : where to go when done.
*
* @return boolean item found
**/
function showForm($ID, $options = []) {
global $CFG_GLPI;
$this->initForm($ID, $options);
$options['colspan'] = 1;
$this->showFormHeader($options);
echo "
".sprintf(__('%1$s (%2$s)'), __('Name'), __('Email address')).
" ";
Html::autocompletionTextField($this, "name");
echo " ";
if ($this->fields['errors']) {
echo "".__('Connection errors')." ";
echo "".$this->fields['errors']." ";
echo " ";
}
echo "".__('Active')." ";
Dropdown::showYesNo("is_active", $this->fields["is_active"]);
echo " ";
$type = Toolbox::showMailServerConfig($this->fields["host"]);
echo "".__('Login')." ";
Html::autocompletionTextField($this, "login");
echo " ";
echo "".__('Password')." ";
echo " ";
if ($ID > 0) {
echo " ".__('Clear');
}
echo " ";
if ($type != "pop") {
echo "" . __('Accepted mail archive folder (optional)') . " ";
echo "";
echo " fields['accepted']."\">";
echo " ";
echo " \n";
echo "" . __('Refused mail archive folder (optional)') . " ";
echo "";
echo " fields['refused']."\">";
echo " ";
echo " \n";
}
echo "";
echo " ". __('Maximum size of each file imported by the mails receiver').
" ";
self::showMaxFilesize('filesize_max', $this->fields["filesize_max"]);
echo " ";
echo "" . __('Use mail date, instead of collect one') . " ";
echo "";
Dropdown::showYesNo("use_mail_date", $this->fields["use_mail_date"]);
echo " \n";
echo "" . __('Use Reply-To as requester (when available)') . " ";
echo "";
Dropdown::showFromArray("requester_field", [
self::REQUESTER_FIELD_FROM => __('No'),
self::REQUESTER_FIELD_REPLY_TO => __('Yes')
], ["value" => $this->fields['requester_field']]);
echo " \n";
echo "" . __('Add CC users as observer') . " ";
echo "";
Dropdown::showYesNo("add_cc_to_observer", $this->fields["add_cc_to_observer"]);
echo " \n";
echo "" . __('Collect only unread mail') . " ";
echo "";
Dropdown::showYesNo("collect_only_unread", $this->fields["collect_only_unread"]);
echo " \n";
echo "".__('Comments')." ";
echo "";
if ($ID > 0) {
echo " ";
//TRANS: %s is the datetime of update
printf(__('Last update on %s'), Html::convDateTime($this->fields["date_mod"]));
}
echo " ";
$this->showFormButtons($options);
if ($type != 'pop') {
echo "
";
echo Html::scriptBlock("$(function() {
$('#imap-folder')
.dialog(options = {
autoOpen: false,
autoResize:true,
width: 'auto',
modal: true,
});
$(document).on('click', '.get-imap-folder', function() {
var input = $(this).prev('input');
var data = 'action=getFoldersList';
data += '&input_id=' + input.attr('id');
// Get form values without server_mailbox value to prevent filtering
data += '&' + $(this).closest('form').find(':not([name=\"server_mailbox\"])').serialize();
// Force empty value for server_mailbox
data += '&server_mailbox=';
$('#imap-folder')
.html('')
.load('".$CFG_GLPI['root_doc']."/ajax/mailcollector.php', data)
.dialog('open');
});
$(document).on('click', '.select_folder li', function(event) {
event.stopPropagation();
var li = $(this);
var input_id = li.data('input-id');
var folder = li.children('.folder-name').html();
var _label = '';
var _parents = li.parents('li').children('.folder-name');
for (i = _parents.length -1 ; i >= 0; i--) {
_label += $(_parents[i]).html() + '/';
}
_label += folder;
$('#'+input_id).val(_label);
$('#imap-folder').dialog('close');
})
});");
}
return true;
}
/**
* Display the list of folder for current connections
*
* @since 9.3.1
*
* @param string $input_id dom id where to insert folder name
*
* @return void
*/
public function displayFoldersList($input_id = "") {
try {
$this->connect();
} catch (Throwable $e) {
Toolbox::logError(
'An error occured trying to connect to collector.',
$e->getMessage(),
"\n",
$e->getTraceAsString()
);
echo __('An error occured trying to connect to collector.');
return;
}
$folders = $this->storage->getFolders();
$hasFolders = false;
echo "";
foreach ($folders as $folder) {
$hasFolders = true;
$this->displayFolder($folder, $input_id);
}
if ($hasFolders === false && !empty($this->fields['server_mailbox'])) {
echo "";
echo sprintf(
__("No child found for folder '%s'."),
Html::entities_deep($this->fields['server_mailbox'])
);
echo " ";
}
echo " ";
}
/**
* Display recursively a folder and its children
*
* @param \Laminas\Mail\Storage\Folder $folder Current folder
* @param string $input_id Input ID
*
* @return void
*/
private function displayFolder($folder, $input_id) {
echo "";
$fname = mb_convert_encoding($folder->getLocalName(), "UTF-8", "UTF7-IMAP");
echo "
".$fname." ";
foreach ($folder as $sfolder) {
$this->displayFolder($sfolder, $input_id);
}
echo " ";
echo " ";
}
function showGetMessageForm($ID) {
echo " ";
}
function rawSearchOptions() {
$tab = [];
$tab[] = [
'id' => 'common',
'name' => __('Characteristics')
];
$tab[] = [
'id' => '1',
'table' => $this->getTable(),
'field' => 'name',
'name' => __('Name'),
'datatype' => 'itemlink',
'massiveaction' => false,
'autocomplete' => true,
];
$tab[] = [
'id' => '2',
'table' => $this->getTable(),
'field' => 'is_active',
'name' => __('Active'),
'datatype' => 'bool'
];
$tab[] = [
'id' => '3',
'table' => $this->getTable(),
'field' => 'host',
'name' => __('Connection string'),
'massiveaction' => false,
'datatype' => 'string'
];
$tab[] = [
'id' => '4',
'table' => $this->getTable(),
'field' => 'login',
'name' => __('Login'),
'massiveaction' => false,
'datatype' => 'string',
'autocomplete' => true,
];
$tab[] = [
'id' => '5',
'table' => $this->getTable(),
'field' => 'filesize_max',
'name' => __('Maximum size of each file imported by the mails receiver'),
'datatype' => 'integer'
];
$tab[] = [
'id' => '16',
'table' => $this->getTable(),
'field' => 'comment',
'name' => __('Comments'),
'datatype' => 'text'
];
$tab[] = [
'id' => '19',
'table' => $this->getTable(),
'field' => 'date_mod',
'name' => __('Last update'),
'datatype' => 'datetime',
'massiveaction' => false
];
$tab[] = [
'id' => '20',
'table' => $this->getTable(),
'field' => 'accepted',
'name' => __('Accepted mail archive folder (optional)'),
'datatype' => 'string'
];
$tab[] = [
'id' => '21',
'table' => $this->getTable(),
'field' => 'refused',
'name' => __('Refused mail archive folder (optional)'),
'datatype' => 'string'
];
$tab[] = [
'id' => '22',
'table' => $this->getTable(),
'field' => 'errors',
'name' => __('Connection errors'),
'datatype' => 'integer'
];
return $tab;
}
/**
* @param $emails_ids array
* @param $action (default 0)
* @param $entity (default 0)
**/
function deleteOrImportSeveralEmails($emails_ids = [], $action = 0, $entity = 0) {
global $DB;
$query = [
'FROM' => NotImportedEmail::getTable(),
'WHERE' => [
'id' => $emails_ids,
],
'ORDER' => 'mailcollectors_id'
];
$todelete = [];
foreach ($DB->request($query) as $data) {
$todelete[$data['mailcollectors_id']][$data['messageid']] = $data;
}
foreach ($todelete as $mailcollector_id => $rejected) {
$collector = new self();
if ($collector->getFromDB($mailcollector_id)) {
// Use refused folder in connection string
$connect_config = Toolbox::parseMailServerConnectString($collector->fields['host']);
$collector->fields['host'] = Toolbox::constructMailServerConfig(
[
'mail_server' => $connect_config['address'],
'server_port' => $connect_config['port'],
'server_type' => !empty($connect_config['type']) ? '/' . $connect_config['type'] : '',
'server_ssl' => $connect_config['ssl'] ? '/ssl' : '',
'server_cert' => $connect_config['validate-cert'] ? '/validate-cert' : '/novalidate-cert',
'server_tls' => $connect_config['tls'] ? '/tls' : '',
'server_rsh' => $connect_config['norsh'] ? '/norsh' : '',
'server_secure' => $connect_config['secure'] ? '/secure' : '',
'server_debug' => $connect_config['debug'] ? '/debug' : '',
'server_mailbox' => $collector->fields[self::REFUSED_FOLDER],
]
);
$collector->uid = -1;
//Connect to the Mail Box
try {
$collector->connect();
} catch (Throwable $e) {
Toolbox::logError(
'An error occured trying to connect to collector.',
$e->getMessage(),
"\n",
$e->getTraceAsString()
);
continue;
}
foreach ($collector->storage as $uid => $message) {
$head = $collector->getHeaders($message);
if (isset($rejected[$head['message_id']])) {
if ($action == 1) {
$tkt = $collector->buildTicket(
$uid,
$message,
[
'mailgates_id' => $mailcollector_id,
'play_rules' => false
]
);
$tkt['_users_id_requester'] = $rejected[$head['message_id']]['users_id'];
$tkt['entities_id'] = $entity;
if (!isset($tkt['tickets_id'])) {
// New ticket case
$ticket = new Ticket();
$ticket->add($tkt);
} else {
// Followup case
$fup = new ITILFollowup();
$fup_input = $tkt;
$fup_input['itemtype'] = Ticket::class;
$fup_input['items_id'] = $fup_input['tickets_id'];
$fup->add($fup_input);
}
$folder = self::ACCEPTED_FOLDER;
} else {
$folder = self::REFUSED_FOLDER;
}
//Delete email
if ($collector->deleteMails($uid, $folder)) {
$rejectedmail = new NotImportedEmail();
$rejectedmail->delete(['id' => $rejected[$head['message_id']]['id']]);
}
// Unset managed
unset($rejected[$head['message_id']]);
}
}
// Email not present in mailbox
if (count($rejected)) {
$clean = [
'<' => '',
'>' => ''
];
foreach ($rejected as $id => $data) {
if ($action == 1) {
Session::addMessageAfterRedirect(
sprintf(
__('Email %s not found. Impossible import.'),
strtr($id, $clean)
),
false,
ERROR
);
} else { // Delete data in notimportedemail table
$rejectedmail = new NotImportedEmail();
$rejectedmail->delete(['id' => $data['id']]);
}
}
}
}
}
}
/**
* Do collect
*
* @param integer $mailgateID ID of the mailgate
* @param boolean $display display messages in MessageAfterRedirect or just return error (default 0=)
*
* @return string|void
**/
function collect($mailgateID, $display = 0) {
global $CFG_GLPI;
if ($this->getFromDB($mailgateID)) {
$this->uid = -1;
$this->fetch_emails = 0;
//Connect to the Mail Box
try {
$this->connect();
} catch (Throwable $e) {
Toolbox::logError(
'An error occured trying to connect to collector.',
$e->getMessage(),
"\n",
$e->getTraceAsString()
);
Session::addMessageAfterRedirect(
__('An error occured trying to connect to collector.') . " " . $e->getMessage(),
false,
ERROR
);
return;
}
$rejected = new NotImportedEmail();
// Clean from previous collect (from GUI, cron already truncate the table)
$rejected->deleteByCriteria(['mailcollectors_id' => $this->fields['id']]);
if ($this->storage) {
$error = 0;
$refused = 0;
$alreadyseen = 0;
$blacklisted = 0;
// Get Total Number of Unread Email in mail box
$count_messages = $this->getTotalMails();
$delete = [];
$messages = [];
do {
$this->storage->next();
if (!$this->storage->valid()) {
break;
}
try {
$this->fetch_emails++;
$messages[$this->storage->key()] = $this->storage->current();
} catch (\Exception $e) {
Toolbox::logInFile('mailgate', sprintf(__('Message is invalid: %1$s').' ', $e->getMessage()));
++$error;
}
} while ($this->fetch_emails < $this->maxfetch_emails);
foreach ($messages as $uid => $message) {
$rejinput = [
'mailcollectors_id' => $mailgateID,
];
//prevent loop when message is read but when it's impossible to move / delete
//due to mailbox problem (ie: full)
if ($this->fields['collect_only_unread'] && $message->hasFlag(Storage::FLAG_SEEN)) {
++$alreadyseen;
continue;
}
try {
$tkt = $this->buildTicket(
$uid,
$message,
[
'mailgates_id' => $mailgateID,
'play_rules' => true
]
);
$headers = $this->getHeaders($message);
$requester = $this->getRequesterEmail($message);
if (!$tkt['_blacklisted']) {
global $DB;
$rejinput['from'] = $requester;
$rejinput['to'] = $headers['to'];
$rejinput['users_id'] = $tkt['_users_id_requester'];
$rejinput['subject'] = $DB->escape($this->cleanSubject($headers['subject']));
$rejinput['messageid'] = $headers['message_id'];
}
$rejinput['date'] = $_SESSION["glpi_currenttime"];
} catch (Throwable $e) {
$error++;
Toolbox::logInFile('mailgate', sprintf(__('Error during message parsing: %1$s').' ', $e->getMessage()));
$rejinput['reason'] = NotImportedEmail::FAILED_OPERATION;
$rejected->add($rejinput);
continue;
}
$is_user_anonymous = !(isset($tkt['_users_id_requester'])
&& ($tkt['_users_id_requester'] > 0));
$is_supplier_anonymous = !(isset($tkt['_supplier_email'])
&& $tkt['_supplier_email']);
// Keep track of the mail author so we can check his
// notifications preferences later (glpinotification_to_myself)
if ($tkt['users_id']) {
$_SESSION['mailcollector_user'] = $tkt['users_id'];
} else {
// Special case when we have no users_id (anonymous helpdesk)
// -> use the user email instead
$_SESSION['mailcollector_user'] = $tkt["_users_id_requester_notif"]['alternative_email'][0];
}
if (isset($tkt['_blacklisted']) && $tkt['_blacklisted']) {
$delete[$uid] = self::REFUSED_FOLDER;
$blacklisted++;
} else if (isset($tkt['_refuse_email_with_response'])) {
$delete[$uid] = self::REFUSED_FOLDER;
$refused++;
$this->sendMailRefusedResponse($requester, $tkt['name']);
} else if (isset($tkt['_refuse_email_no_response'])) {
$delete[$uid] = self::REFUSED_FOLDER;
$refused++;
} else if (isset($tkt['entities_id'])
&& !isset($tkt['tickets_id'])
&& ($CFG_GLPI["use_anonymous_helpdesk"]
|| !$is_user_anonymous
|| !$is_supplier_anonymous)) {
// New ticket case
$ticket = new Ticket();
if (!$CFG_GLPI["use_anonymous_helpdesk"]
&& !Profile::haveUserRight($tkt['_users_id_requester'],
Ticket::$rightname,
CREATE,
$tkt['entities_id'])) {
$delete[$uid] = self::REFUSED_FOLDER;
$refused++;
$rejinput['reason'] = NotImportedEmail::NOT_ENOUGH_RIGHTS;
$rejected->add($rejinput);
} else if ($ticket->add($tkt)) {
$delete[$uid] = self::ACCEPTED_FOLDER;
} else {
$error++;
$rejinput['reason'] = NotImportedEmail::FAILED_OPERATION;
$rejected->add($rejinput);
}
} else if (isset($tkt['tickets_id'])
&& ($CFG_GLPI['use_anonymous_followups'] || !$is_user_anonymous)) {
// Followup case
$ticket = new Ticket();
$ticketExist = $ticket->getFromDB($tkt['tickets_id']);
$fup = new ITILFollowup();
$fup_input = $tkt;
$fup_input['itemtype'] = Ticket::class;
$fup_input['items_id'] = $fup_input['tickets_id'];
unset($fup_input['tickets_id']);
if ($ticketExist && Entity::getUsedConfig(
'suppliers_as_private',
$ticket->fields['entities_id']
)) {
// Get suppliers matching the from email
$suppliers = Supplier::getSuppliersByEmail(
$rejinput['from']
);
foreach ($suppliers as $supplier) {
// If the supplier is assigned to this ticket then
// the followup must be private
if ($ticket->isSupplier(
CommonITILActor::ASSIGN,
$supplier['id'])
) {
$fup_input['is_private'] = true;
break;
}
}
}
if (!$ticketExist) {
$error++;
$rejinput['reason'] = NotImportedEmail::FAILED_OPERATION;
$rejected->add($rejinput);
} else if (!$CFG_GLPI['use_anonymous_followups']
&& !$ticket->canUserAddFollowups($tkt['_users_id_requester'])) {
$delete[$uid] = self::REFUSED_FOLDER;
$refused++;
$rejinput['reason'] = NotImportedEmail::NOT_ENOUGH_RIGHTS;
$rejected->add($rejinput);
} else if ($fup->add($fup_input)) {
$delete[$uid] = self::ACCEPTED_FOLDER;
} else {
$error++;
$rejinput['reason'] = NotImportedEmail::FAILED_OPERATION;
$rejected->add($rejinput);
}
} else {
if ($is_user_anonymous && !$CFG_GLPI["use_anonymous_helpdesk"]) {
$rejinput['reason'] = NotImportedEmail::USER_UNKNOWN;
} else {
$rejinput['reason'] = NotImportedEmail::MATCH_NO_RULE;
}
$refused++;
$rejected->add($rejinput);
$delete[$uid] = self::REFUSED_FOLDER;
}
// Clean mail author used for notification settings
unset($_SESSION['mailcollector_user']);
}
krsort($delete);
foreach ($delete as $uid => $folder) {
$this->deleteMails($uid, $folder);
}
//TRANS: %1$d, %2$d, %3$d, %4$d %5$d and %6$d are number of messages
$msg = sprintf(
__('Number of messages: available=%1$d, already imported=%2$d, retrieved=%3$d, refused=%4$d, errors=%5$d, blacklisted=%6$d'),
$count_messages,
$alreadyseen,
$this->fetch_emails - $alreadyseen,
$refused,
$error,
$blacklisted
);
if ($display) {
Session::addMessageAfterRedirect($msg, false, ($error ? ERROR : INFO));
} else {
return $msg;
}
} else {
$msg = __('Could not connect to mailgate server');
if ($display) {
Session::addMessageAfterRedirect($msg, false, ERROR);
GLPINetwork::addErrorMessageAfterRedirect();
} else {
return $msg;
}
}
} else {
//TRANS: %s is the ID of the mailgate
$msg = sprintf(__('Could not find mailgate %d'), $mailgateID);
if ($display) {
Session::addMessageAfterRedirect($msg, false, ERROR);
GLPINetwork::addErrorMessageAfterRedirect();
} else {
return $msg;
}
}
}
/**
* Builds and returns the main structure of the ticket to be created
*
* @param string $uid UID of the message
* @param \Laminas\Mail\Storage\Message $message Messge
* @param array $options Possible options
*
* @return array ticket fields
*/
function buildTicket($uid, \Laminas\Mail\Storage\Message $message, $options = []) {
global $CFG_GLPI;
$play_rules = (isset($options['play_rules']) && $options['play_rules']);
$headers = $this->getHeaders($message);
$tkt = [];
$tkt['_blacklisted'] = false;
// For RuleTickets
$tkt['_mailgate'] = $options['mailgates_id'];
// Use mail date if it's defined
if ($this->fields['use_mail_date'] && isset($headers['date'])) {
$tkt['date'] = $headers['date'];
}
// Detect if it is a mail reply
$glpi_message_match = "/GLPI-([0-9]+)\.[0-9]+\.[0-9]+@\w*/";
// Check if email not send by GLPI : if yes -> blacklist
if (!isset($headers['message_id'])
|| preg_match($glpi_message_match, $headers['message_id'], $match)) {
$tkt['_blacklisted'] = true;
return $tkt;
}
// manage blacklist
$blacklisted_emails = Blacklist::getEmails();
// Add name of the mailcollector as blacklisted
$blacklisted_emails[] = $this->fields['name'];
if (Toolbox::inArrayCaseCompare($headers['from'], $blacklisted_emails)) {
$tkt['_blacklisted'] = true;
return $tkt;
}
// max size = 0 : no import attachments
if ($this->fields['filesize_max'] > 0) {
if (is_writable(GLPI_TMP_DIR)) {
$tkt['_filename'] = $this->getAttached($message, GLPI_TMP_DIR."/", $this->fields['filesize_max']);
$tkt['_tag'] = $this->tags;
} else {
//TRANS: %s is a directory
Toolbox::logInFile('mailgate', sprintf(__('%s is not writable'), GLPI_TMP_DIR."/"));
}
}
// Who is the user ?
$requester = $this->getRequesterEmail($message);
$tkt['_users_id_requester'] = User::getOrImportByEmail($requester);
$tkt["_users_id_requester_notif"]['use_notification'][0] = 1;
// Set alternative email if user not found / used if anonymous mail creation is enable
if (!$tkt['_users_id_requester']) {
$tkt["_users_id_requester_notif"]['alternative_email'][0] = $requester;
}
// Fix author of attachment
// Move requester to author of followup
$tkt['users_id'] = $tkt['_users_id_requester'];
// Add to and cc as additional observer if user found
$ccs = $headers['ccs'];
if (is_array($ccs) && count($ccs) && $this->getField("add_cc_to_observer")) {
foreach ($ccs as $cc) {
if ($cc != $requester
&& !Toolbox::inArrayCaseCompare($cc, $blacklisted_emails) // not blacklisted emails
) {
// Skip if user is anonymous and anonymous users are not allowed
$user_id = User::getOrImportByEmail($cc);
if (!$user_id && !$CFG_GLPI['use_anonymous_helpdesk']) {
continue;
}
$nb = (isset($tkt['_users_id_observer']) ? count($tkt['_users_id_observer']) : 0);
$tkt['_users_id_observer'][$nb] = $user_id;
$tkt['_users_id_observer_notif']['use_notification'][$nb] = 1;
$tkt['_users_id_observer_notif']['alternative_email'][$nb] = $cc;
}
}
}
$tos = $headers['tos'];
if (is_array($tos) && count($tos)) {
foreach ($tos as $to) {
if ($to != $requester
&& !Toolbox::inArrayCaseCompare($to, $blacklisted_emails) // not blacklisted emails
) {
// Skip if user is anonymous and anonymous users are not allowed
$user_id = User::getOrImportByEmail($to);
if (!$user_id && !$CFG_GLPI['use_anonymous_helpdesk']) {
continue;
}
$nb = (isset($tkt['_users_id_observer']) ? count($tkt['_users_id_observer']) : 0);
$tkt['_users_id_observer'][$nb] = $user_id;
$tkt['_users_id_observer_notif']['use_notification'][$nb] = 1;
$tkt['_users_id_observer_notif']['alternative_email'][$nb] = $to;
}
}
}
// Auto_import
$tkt['_auto_import'] = 1;
// For followup : do not check users_id = login user
$tkt['_do_not_check_users_id'] = 1;
$body = $this->getBody($message);
try {
$subject = $message->getHeader('subject')->getFieldValue();
} catch (Laminas\Mail\Storage\Exception\InvalidArgumentException $e) {
$subject = null;
}
$tkt['_message'] = $message;
if (!Toolbox::seems_utf8($body)) {
$tkt['content'] = Toolbox::encodeInUtf8($body);
} else {
$tkt['content'] = $body;
}
// prepare match to find ticket id in headers
// header is added in all notifications using pattern: GLPI-{itemtype}-{items_id}
$ref_match = "/GLPI-Ticket-([0-9]+)/";
// See In-Reply-To field
if (isset($headers['in_reply_to'])) {
if (preg_match($ref_match, $headers['in_reply_to'], $match)) {
$tkt['tickets_id'] = intval($match[1]);
}
}
// See in References
if (!isset($tkt['tickets_id'])
&& isset($headers['references'])) {
if (preg_match($ref_match, $headers['references'], $match)) {
$tkt['tickets_id'] = intval($match[1]);
}
}
// See in title
if (!isset($tkt['tickets_id'])
&& preg_match('/\[.+#(\d+)\]/', $subject, $match)) {
$tkt['tickets_id'] = intval($match[1]);
}
$tkt['_supplier_email'] = false;
// Found ticket link
if (isset($tkt['tickets_id'])) {
// it's a reply to a previous ticket
$job = new Ticket();
$tu = new Ticket_User();
$st = new Supplier_Ticket();
// Check if ticket exists and users_id exists in GLPI
if ($job->getFromDB($tkt['tickets_id'])
&& ($job->fields['status'] != CommonITILObject::CLOSED)
&& ($CFG_GLPI['use_anonymous_followups']
|| ($tkt['_users_id_requester'] > 0)
|| $tu->isAlternateEmailForITILObject($tkt['tickets_id'], $requester)
|| ($tkt['_supplier_email'] = $st->isSupplierEmail($tkt['tickets_id'],
$requester)))) {
if ($tkt['_supplier_email']) {
$tkt['content'] = sprintf(__('From %s'), $requester)."\n\n".$tkt['content'];
}
$header_tag = NotificationTargetTicket::HEADERTAG;
$header_pattern = $header_tag . '.*' . $header_tag;
$footer_tag = NotificationTargetTicket::FOOTERTAG;
$footer_pattern = $footer_tag . '.*' . $footer_tag;
$has_header_line = preg_match('/' . $header_pattern . '/s', $tkt['content']);
$has_footer_line = preg_match('/' . $footer_pattern . '/s', $tkt['content']);
if ($has_header_line && $has_footer_line) {
// Strip all contents between header and footer line
$tkt['content'] = preg_replace(
'/' . $header_pattern . '.*' . $footer_pattern . '/s',
'',
$tkt['content']
);
} else if ($has_header_line) {
// Strip all contents between header line and end of message
$tkt['content'] = preg_replace(
'/' . $header_pattern . '.*$/s',
'',
$tkt['content']
);
} else if ($has_footer_line) {
// Strip all contents between begin of message and footer line
$tkt['content'] = preg_replace(
'/^.*' . $footer_pattern . '/s',
'',
$tkt['content']
);
}
} else {
// => to handle link in Ticket->post_addItem()
$tkt['_linkedto'] = $tkt['tickets_id'];
unset($tkt['tickets_id']);
}
}
// Add message from getAttached
if ($this->addtobody) {
$tkt['content'] .= $this->addtobody;
}
//If files are present and content is html
if (isset($this->files) && count($this->files) && $this->body_is_html) {
$tkt['content'] = Ticket::convertContentForTicket($tkt['content'],
$this->files + $this->altfiles,
$this->tags);
}
// Clean mail content
$tkt['content'] = $this->cleanContent($tkt['content']);
$tkt['name'] = $this->cleanSubject($subject);
if (!Toolbox::seems_utf8($tkt['name'])) {
$tkt['name'] = Toolbox::encodeInUtf8($tkt['name']);
}
if (!isset($tkt['tickets_id'])) {
// Which entity ?
//$tkt['entities_id']=$this->fields['entities_id'];
//$tkt['Subject']= $message->subject; // not use for the moment
// Medium
$tkt['urgency'] = "3";
// No hardware associated
$tkt['itemtype'] = "";
// Mail request type
} else {
// Reopen if needed
$tkt['add_reopen'] = 1;
}
$tkt['requesttypes_id'] = RequestType::getDefault('mail');
if ($play_rules) {
$rule_options['ticket'] = $tkt;
$rule_options['headers'] = $this->getHeaders($message);
$rule_options['mailcollector'] = $options['mailgates_id'];
$rule_options['_users_id_requester'] = $tkt['_users_id_requester'];
$rulecollection = new RuleMailCollectorCollection();
$output = $rulecollection->processAllRules([], [],
$rule_options);
// New ticket : compute all
if (!isset($tkt['tickets_id'])) {
foreach ($output as $key => $value) {
$tkt[$key] = $value;
}
} else { // Followup only copy refuse data
$tkt['requesttypes_id'] = RequestType::getDefault('mailfollowup');
$tobecopied = ['_refuse_email_no_response', '_refuse_email_with_response'];
foreach ($tobecopied as $val) {
if (isset($output[$val])) {
$tkt[$val] = $output[$val];
}
}
}
}
$tkt['content'] = LitEmoji::encodeShortcode($tkt['content']);
$tkt = Toolbox::addslashes_deep($tkt);
return $tkt;
}
/**
* Clean mail content : HTML + XSS + blacklisted content
*
* @since 0.85
*
* @param string $string text to clean
*
* @return string cleaned text
**/
function cleanContent($string) {
global $DB;
// Clean HTML
$string = Html::clean(Html::entities_deep($string), false, 2);
$br_marker = '==' . mt_rand() . '==';
// Replace HTML line breaks to marker
$string = preg_replace('/ /', $br_marker, $string);
// Replace plain text line breaks to marker if content is not html
// and rich text mode is enabled (otherwise remove them)
$string = str_replace(
["\r\n", "\n", "\r"],
$this->body_is_html ? ' ' : $br_marker,
$string
);
// Wrap content for blacklisted items
$itemstoclean = [];
foreach ($DB->request('glpi_blacklistedmailcontents') as $data) {
$toclean = trim($data['content']);
if (!empty($toclean)) {
$itemstoclean[] = str_replace(["\r\n", "\n", "\r"], $br_marker, $toclean);
}
}
if (count($itemstoclean)) {
$string = str_replace($itemstoclean, '', $string);
}
$string = str_replace($br_marker, " ", $string);
// Double encoding for > and < char to avoid misinterpretations
$string = str_replace(['<', '>'], ['<', '>'], $string);
// Prevent XSS
$string = Toolbox::clean_cross_side_scripting_deep($string);
return $string;
}
/**
* Strip out unwanted/unprintable characters from the subject
*
* @param string $text text to clean
*
* @return string clean text
**/
function cleanSubject($text) {
$text = str_replace("=20", "\n", $text);
$text = Toolbox::clean_cross_side_scripting_deep($text);
return $text;
}
///return supported encodings in lowercase.
function listEncodings() {
// Encoding not listed
static $enc = ['gb2312', 'gb18030'];
if (count($enc) == 2) {
foreach (mb_list_encodings() as $encoding) {
$enc[] = Toolbox::strtolower($encoding);
$aliases = mb_encoding_aliases($encoding);
foreach ($aliases as $e) {
$enc[] = Toolbox::strtolower($e);
}
}
}
return $enc;
}
/**
* Connect to the mail box
*
* @return void
*/
function connect() {
$config = Toolbox::parseMailServerConnectString($this->fields['host']);
$params = [
'host' => $config['address'],
'user' => $this->fields['login'],
'password' => Toolbox::sodiumDecrypt($this->fields['passwd']),
'port' => $config['port']
];
if ($config['ssl']) {
$params['ssl'] = 'SSL';
}
if ($config['tls']) {
$params['ssl'] = 'TLS';
}
if (!empty($config['mailbox'])) {
$params['folder'] = $config['mailbox'];
}
if ($config['validate-cert'] === false) {
$params['novalidatecert'] = true;
}
try {
$storage = Toolbox::getMailServerStorageInstance($config['type'], $params);
if ($storage === null) {
throw new \Exception(sprintf(__('Unsupported mail server type:%s.'), $config['type']));
}
$this->storage = $storage;
if ($this->fields['errors'] > 0) {
$this->update([
'id' => $this->getID(),
'errors' => 0
]);
}
} catch (\Exception $e) {
$this->update([
'id' => $this->getID(),
'errors' => ($this->fields['errors']+1)
]);
// Any errors will cause an Exception.
throw $e;
}
}
/**
* Get extra headers
*
* @param \Laminas\Mail\Storage\Message $message Message
*
* @return array
**/
function getAdditionnalHeaders(\Laminas\Mail\Storage\Message $message) {
$head = [];
$headers = $message->getHeaders();
foreach ($headers as $header) {
// is line with additional header?
$key = $header->getFieldName();
$value = $header->getFieldValue();
if (preg_match("/^X-/i", $key)
|| preg_match("/^Auto-Submitted/i", $key)
|| preg_match("/^Received/i", $key)) {
$key = Toolbox::strtolower($key);
if (!isset($head[$key])) {
$head[$key] = '';
} else {
$head[$key] .= "\n";
}
$head[$key] .= trim($value);
}
}
return $head;
}
/**
* Get full headers infos from particular mail
*
* @param \Laminas\Mail\Storage\Message $message Message
*
* @return array Associative array with following keys
* subject => Subject of Mail
* to => To Address of that mail
* toOth => Other To address of mail
* toNameOth => To Name of Mail
* from => From address of mail
* fromName => Form Name of Mail
**/
function getHeaders(\Laminas\Mail\Storage\Message $message) {
$sender_email = $this->getEmailFromHeader($message, 'from');
if (preg_match('/^(mailer-daemon|postmaster)@/i', $sender_email) === 1) {
return [];
}
$to = $this->getEmailFromHeader($message, 'to');
$reply_to_addr = Toolbox::strtolower($this->getEmailFromHeader($message, 'reply-to'));
$date = date("Y-m-d H:i:s", strtotime($message->date));
$mail_details = [];
// Construct to and cc arrays
$tos = [];
if (isset($message->to)) {
$h_tos = $message->getHeader('to');
foreach ($h_tos->getAddressList() as $address) {
$mailto = Toolbox::strtolower($address->getEmail());
if ($mailto === $this->fields['name']) {
$to = $mailto;
}
$tos[] = $mailto;
}
}
$ccs = [];
if (isset($message->cc)) {
$h_ccs = $message->getHeader('cc');
foreach ($h_ccs->getAddressList() as $address) {
$ccs[] = Toolbox::strtolower($address->getEmail());
}
}
// secu on subject setting
try {
$subject = $message->getHeader('subject')->getFieldValue();
} catch (Laminas\Mail\Storage\Exception\InvalidArgumentException $e) {
$subject = '';
}
$mail_details = [
'from' => Toolbox::strtolower($sender_email),
'subject' => $subject,
'reply-to' => $reply_to_addr,
'to' => Toolbox::strtolower($to),
'message_id' => $message->getHeader('message_id')->getFieldValue(),
'tos' => $tos,
'ccs' => $ccs,
'date' => $date
];
if (isset($message->references)) {
if ($reference = $message->getHeader('references')) {
$mail_details['references'] = $reference->getFieldValue();
}
}
if (isset($message->in_reply_to)) {
if ($inreplyto = $message->getHeader('in_reply_to')) {
$mail_details['in_reply_to'] = $inreplyto->getFieldValue();
}
}
//Add additional headers in X-
foreach ($this->getAdditionnalHeaders($message) as $header => $value) {
$mail_details[$header] = $value;
}
return $mail_details;
}
/**
* Number of entries in the mailbox
*
* @return integer
**/
function getTotalMails() {
return $this->storage->countMessages();
}
/**
* Recursivly get attached documents
* Result is stored in $this->files
*
* @param \Laminas\Mail\Storage\Part $part Message part
* @param string $path Temporary path
* @param integer $maxsize Maximum size of document to be retrieved
* @param string $subject Message ssubject
* @param \Laminas\Mail\Storage\Part $part Message part (for recursive ones)
*
* @return void
**/
private function getRecursiveAttached(\Laminas\Mail\Storage\Part $part, $path, $maxsize, $subject, $subpart = "") {
if ($part->isMultipart()) {
$index = 0;
foreach (new RecursiveIteratorIterator($part) as $mypart) {
$this->getRecursiveAttached(
$mypart,
$path,
$maxsize,
$subject,
($subpart ? $subpart.".".($index+1) : ($index+1))
);
}
} else {
if (!$part->getHeaders()->has('content-type')
|| !(($content_type_header = $part->getHeader('content-type')) instanceof ContentType)) {
return false; // Ignore attachements with no content-type
}
$content_type = $content_type_header->getType();
if (!$part->getHeaders()->has('content-disposition') && preg_match('/^text\/.+/', $content_type)) {
// Ignore attachements with no content-disposition only if they corresponds to a text part.
// Indeed, some mail clients (like some Outlook versions) does not set any content-disposition
// header on inlined images.
return false;
}
// fix monoparted mail
if ($subpart == "") {
$subpart = 1;
}
$filename = '';
// Try to get filename from Content-Disposition header
if (empty($filename)
&& $part->getHeaders()->has('content-disposition')
&& ($content_disp_header = $part->getHeader('content-disposition')) instanceof ContentDisposition) {
$filename = $content_disp_header->getParameter('filename') ?? '';
}
// Try to get filename from Content-Type header
if (empty($filename)) {
$filename = $content_type_header->getParameter('name') ?? '';
}
// part come without correct filename in headers - generate trivial one
// (inline images case for example)
if ((empty($filename) || !Document::isValidDoc($filename))) {
$tmp_filename = "doc_$subpart.".str_replace('image/', '', $content_type);
if (Document::isValidDoc($tmp_filename)) {
$filename = $tmp_filename;
}
}
// Embeded email comes without filename - try to get "Subject:" or generate trivial one
if (empty($filename)) {
if ($subject !== null) {
$filename = "msg_{$subpart}_" . Toolbox::slugify($subject) . ".EML";
} else {
$filename = "msg_$subpart.EML"; // default trivial one :)!
}
}
// if no filename found, ignore this part
if (empty($filename)) {
return false;
}
//try to avoid conflict between inline image and attachment
$i = 2;
while (in_array($filename, $this->files)) {
//replace filename with name_(num).EXT by name_(num+1).EXT
$new_filename = preg_replace("/(.*)_([0-9])*(\.[a-zA-Z0-9]*)$/", "$1_".$i."$3", $filename);
if ($new_filename !== $filename) {
$filename = $new_filename;
} else {
//the previous regex didn't found _num pattern, so add it with this one
$filename = preg_replace("/(.*)(\.[a-zA-Z0-9]*)$/", "$1_".$i."$2", $filename);
}
$i++;
}
if ($part->getSize() > $maxsize) {
$this->addtobody .= "\n\n".sprintf(__('%1$s: %2$s'), __('Too large attached file'),
sprintf(__('%1$s (%2$s)'), $filename,
Toolbox::getSize($part->getSize())));
return false;
}
if (!Document::isValidDoc($filename)) {
//TRANS: %1$s is the filename and %2$s its mime type
$this->addtobody .= "\n\n".sprintf(__('%1$s: %2$s'), __('Invalid attached file'),
sprintf(__('%1$s (%2$s)'), $filename,
$content_type));
return false;
}
$contents = $this->getDecodedContent($part);
if (file_put_contents($path.$filename, $contents)) {
$this->files[$filename] = $filename;
// If embeded image, we add a tag
if (preg_match('@image/.+@', $content_type)) {
end($this->files);
$tag = Rule::getUuid();
$this->tags[$filename] = $tag;
// Link file based on Content-ID header
if (isset($part->contentId)) {
$clean = ['<' => '',
'>' => ''];
$this->altfiles[strtr($part->contentId, $clean)] = $filename;
}
}
}
} // Single part
}
/**
* Get attached documents in a mail
*
* @param \Laminas\Mail\Storage\Message $message Message
* @param string $path Temporary path
* @param integer $maxsize Maximaum size of document to be retrieved
*
* @return array containing extracted filenames in file/_tmp
**/
public function getAttached(\Laminas\Mail\Storage\Message $message, $path, $maxsize) {
$this->files = [];
$this->altfiles = [];
$this->addtobody = "";
try {
$subject = $message->getHeader('subject')->getFieldValue();
} catch (Laminas\Mail\Storage\Exception\InvalidArgumentException $e) {
$subject = null;
}
$this->getRecursiveAttached($message, $path, $maxsize, $subject);
return $this->files;
}
/**
* Get The actual mail content from this mail
*
* @param \Laminas\Mail\Storage\Message $message Message
**/
function getBody(\Laminas\Mail\Storage\Message $message) {
$content = null;
//if message is not multipart, just return its content
if (!$message->isMultipart()) {
$content = $this->getDecodedContent($message);
} else {
//if message is multipart, check for html contents then text contents
foreach (new RecursiveIteratorIterator($message) as $part) {
if (!$part->getHeaders()->has('content-type')
|| !(($content_type = $part->getHeader('content-type')) instanceof ContentType)) {
continue;
}
if ($content_type->getType() == 'text/html') {
$this->body_is_html = true;
$content = $this->getDecodedContent($part);
//do not check for text part if we found html one.
break;
}
if ($content_type->getType() == 'text/plain' && $content === null) {
$this->body_is_html = false;
$content = $this->getDecodedContent($part);
}
}
}
return $content;
}
/**
* Delete mail from that mail box
*
* @param string $uid mail UID
* @param string $folder Folder to move (delete if empty) (default '')
*
* @return boolean
**/
function deleteMails($uid, $folder = '') {
// Disable move support, POP protocol only has the INBOX folder
if (strstr($this->fields['host'], "/pop")) {
$folder = '';
}
if (!empty($folder) && isset($this->fields[$folder]) && !empty($this->fields[$folder])) {
$name = mb_convert_encoding($this->fields[$folder], "UTF7-IMAP", "UTF-8");
try {
$this->storage->moveMessage($uid, $name);
return true;
} catch (\Exception $e) {
// raise an error and fallback to delete
trigger_error(
sprintf(
//TRANS: %1$s is the name of the folder, %2$s is the name of the receiver
__('Invalid configuration for %1$s folder in receiver %2$s'),
$folder,
$this->getName()
)
);
}
}
$this->storage->removeMessage($uid);
return true;
}
/**
* Cron action on mailgate : retrieve mail and create tickets
*
* @param $task
*
* @return -1 : done but not finish 1 : done with success
**/
public static function cronMailgate($task) {
global $DB;
NotImportedEmail::deleteLog();
$iterator = $DB->request([
'FROM' => 'glpi_mailcollectors',
'WHERE' => ['is_active' => 1]
]);
$max = $task->fields['param'];
if (count($iterator) > 0) {
$mc = new self();
while (($max > 0)
&& ($data = $iterator->next())) {
$mc->maxfetch_emails = $max;
$task->log("Collect mails from ".$data["name"]." (".$data["host"].")\n");
$message = $mc->collect($data["id"]);
$task->addVolume($mc->fetch_emails);
$task->log("$message\n");
$max -= $mc->fetch_emails;
}
}
if ($max == $task->fields['param']) {
return 0; // Nothin to do
} else if ($max > 0) {
return 1; // done
}
return -1; // still messages to retrieve
}
public static function cronInfo($name) {
switch ($name) {
case 'mailgate' :
return [
'description' => __('Retrieve email (Mails receivers)'),
'parameter' => __('Number of emails to retrieve')
];
case 'mailgateerror' :
return ['description' => __('Send alarms on receiver errors')];
}
}
/**
* Send Alarms on mailgate errors
*
* @since 0.85
*
* @param CronTask $task for log
**/
public static function cronMailgateError($task) {
global $DB, $CFG_GLPI;
if (!$CFG_GLPI["use_notifications"]) {
return 0;
}
$cron_status = 0;
$iterator = $DB->request([
'FROM' => 'glpi_mailcollectors',
'WHERE' => [
'errors' => ['>', 0],
'is_active' => 1
]
]);
$items = [];
while ($data = $iterator->next()) {
$items[$data['id']] = $data;
}
if (count($items)) {
if (NotificationEvent::raiseEvent('error', new self(), ['items' => $items])) {
$cron_status = 1;
if ($task) {
$task->setVolume(count($items));
}
}
}
return $cron_status;
}
function showSystemInformations($width) {
global $CFG_GLPI, $DB;
// No need to translate, this part always display in english (for copy/paste to forum)
echo "Notifications \n";
echo "\n \n";
$msg = 'Way of sending emails: ';
switch ($CFG_GLPI['smtp_mode']) {
case MAIL_MAIL :
$msg .= 'PHP';
break;
case MAIL_SMTP :
$msg .= 'SMTP';
break;
case MAIL_SMTPSSL :
$msg .= 'SMTP+SSL';
break;
case MAIL_SMTPTLS :
$msg .= 'SMTP+TLS';
break;
}
if ($CFG_GLPI['smtp_mode'] != MAIL_MAIL) {
$msg .= " (".(empty($CFG_GLPI['smtp_username']) ? 'anonymous' : $CFG_GLPI['smtp_username']).
"@".$CFG_GLPI['smtp_host'].")";
}
echo wordwrap($msg."\n", $width, "\n\t\t");
echo "\n ";
echo "Mails receivers \n";
echo "\n \n";
foreach ($DB->request('glpi_mailcollectors') as $mc) {
$msg = "Name: '".$mc['name']."'";
$msg .= " Active: " .($mc['is_active'] ? "Yes" : "No");
echo wordwrap($msg."\n", $width, "\n\t\t");
$msg = "\tServer: '". $mc['host']."'";
$msg .= " Login: '".$mc['login']."'";
$msg .= " Password: ".(empty($mc['passwd']) ? "No" : "Yes");
echo wordwrap($msg."\n", $width, "\n\t\t");
}
echo "\n ";
}
/**
* @param $to (default '')
* @param $subject (default '')
**/
function sendMailRefusedResponse($to = '', $subject = '') {
global $CFG_GLPI;
$mmail = new GLPIMailer();
$mmail->AddCustomHeader("Auto-Submitted: auto-replied");
$mmail->SetFrom($CFG_GLPI["admin_email"], $CFG_GLPI["admin_email_name"]);
$mmail->AddAddress($to);
// Normalized header, no translation
$mmail->Subject = 'Re: ' . $subject;
$mmail->Body = __("Your email could not be processed.\nIf the problem persists, contact the administrator").
"\n-- \n".$CFG_GLPI["mailing_signature"];
$mmail->Send();
}
function title() {
global $CFG_GLPI;
$buttons = [];
if (countElementsInTable($this->getTable())) {
$buttons["notimportedemail.php"] = __('List of not imported emails');
}
$errors = getAllDataFromTable($this->getTable(), ['errors' => ['>', 0]]);
$message = '';
if (count($errors)) {
$servers = [];
foreach ($errors as $data) {
$this->getFromDB($data['id']);
$servers[] = $this->getLink();
}
$message = sprintf(__('Receivers in error: %s'), implode(" ", $servers));
}
if (count($buttons)) {
Html::displayTitle($CFG_GLPI["root_doc"] . "/pics/users.png",
_n('Receiver', 'Receivers', Session::getPluralNumber()), $message, $buttons);
}
}
/**
* Count collectors
*
* @param boolean $active Count active only, defaults to false
*
* @return integer
*/
public static function countCollectors($active = false) {
global $DB;
$criteria = [
'COUNT' => 'cpt',
'FROM' => 'glpi_mailcollectors'
];
if (true === $active) {
$criteria['WHERE'] = ['is_active' => 1];
}
$result = $DB->request($criteria)->next();
return (int)$result['cpt'];
}
/**
* Count active collectors
*
* @return integer
*/
public static function countActiveCollectors() {
return self::countCollectors(true);
}
/**
* @param $name
* @param $value (default 0)
* @param $rand
**/
public static function showMaxFilesize($name, $value = 0, $rand = null) {
$sizes[0] = __('No import');
for ($index=1; $index<100; $index++) {
$sizes[$index*1048576] = sprintf(__('%s Mio'), $index);
}
if ($rand === null) {
$rand = mt_rand();
}
Dropdown::showFromArray($name, $sizes, ['value' => $value, 'rand' => $rand]);
}
function cleanDBonPurge() {
// mailcollector for RuleMailCollector, _mailgate for RuleTicket
Rule::cleanForItemCriteria($this, 'mailcollector');
Rule::cleanForItemCriteria($this, '_mailgate');
}
/**
* Get the requester email address.
*
* @param Message $message
*
* @return string|null
*/
private function getRequesterEmail(Message $message): ?string {
$email = null;
if ($this->fields['requester_field'] === self::REQUESTER_FIELD_REPLY_TO) {
// Try to find requester in "reply-to"
$email = $this->getEmailFromHeader($message, 'reply-to');
}
if ($email === null) {
// Fallback on default "from"
$email = $this->getEmailFromHeader($message, 'from');
}
return $email;
}
/**
* Get the email address from given header.
*
* @param Message $message
* @param string $header_name
*
* @return string|null
*/
private function getEmailFromHeader(Message $message, string $header_name): ?string {
if (!$message->getHeaders()->has($header_name)) {
return null;
}
$header = $message->getHeader($header_name);
$address = $header instanceof AbstractAddressList ? $header->getAddressList()->rewind() : null;
return $address instanceof Address ? $address->getEmail() : null;
}
/**
* Retrieve properly decoded content
*
* @param \Laminas\Mail\Storage\Message $part Message Part
*
* @return string
*/
public function getDecodedContent(\Laminas\Mail\Storage\Part $part) {
$contents = $part->getContent();
$encoding = null;
if (isset($part->contentTransferEncoding)) {
$encoding = $part->contentTransferEncoding;
}
switch ($encoding) {
case 'base64':
$contents = base64_decode($contents);
break;
case 'quoted-printable':
$contents = quoted_printable_decode($contents);
break;
case '7bit':
case '8bit':
case 'binary':
default:
// returned verbatim
break;
}
if (!$part->getHeaders()->has('content-type')
|| !(($content_type = $part->getHeader('content-type')) instanceof ContentType)
| preg_match('/^text\//', $content_type->getType()) !== 1) {
return $contents; // No charset conversion content type header is not set or content is not text/*
}
$charset = $content_type->getParameter('charset');
if (strtoupper($charset) != 'UTF-8') {
if (in_array($charset, array_map('strtoupper', mb_list_encodings()))) {
$contents = mb_convert_encoding($contents, 'UTF-8', $charset);
} else {
// Convert Windows charsets names
if (preg_match('/^WINDOWS-\d{4}$/', $charset)) {
$charset = preg_replace('/^WINDOWS-(\d{4})$/', 'CP$1', $charset);
}
if ($converted = iconv($charset, 'UTF-8//TRANSLIT', $contents)) {
$contents = $converted;
}
}
}
return $contents;
}
static function getIcon() {
return "fas fa-inbox";
}
}