mailer = new MailerHelper();
$this->ruleClass = new RuleClass();
$this->userClass = new UserClass();
$this->mailClass = new MailClass();
$this->historyClass = new HistoryClass();
$this->encodingHelper = new EncodingHelper();
$this->mailer->report = false;
$this->mailer->alreadyCheckedAddresses = true;
$charsetType = new CharsetType();
$this->allCharsets = $charsetType->charsets;
$this->allowed_extensions = explode(',', $this->config->get('allowed_files', ''));
$this->detectEmail = '/'.acym_getEmailRegex(false, true).'/i';
}
public function init(array $config = []): bool
{
if (empty($config)) {
$config = [
'server' => $this->config->get('bounce_server'),
'username' => $this->config->get('bounce_username'),
'password' => $this->config->get('bounce_password'),
'port' => $this->config->get('bounce_port'),
'connect_method' => $this->config->get('bounce_connection'),
'secure_method' => $this->config->get('bounce_secured'),
'self_signed' => $this->config->get('bounce_certif', false),
'timeout' => $this->config->get('bounce_timeout', 10),
'bounce_token' => $this->config->get('bounce_token'),
'imap_connection_method' => $this->config->get('imap_connection_method', 'classic'),
];
}
$this->server = $config['server'];
$this->username = $config['username'];
$this->password = $config['password'];
$this->port = $config['port'];
$this->connectMethod = $config['connect_method'];
$this->secureMethod = $config['secure_method'];
$this->selfSigned = $config['self_signed'];
$this->timeout = intval($config['timeout']);
$this->oAuthToken = str_replace('Bearer ', '', $config['bounce_token']);
$this->imapConnectionMethod = $config['imap_connection_method'];
if ($this->connectMethod === 'pear') {
$this->usePear = true;
include_once ACYM_LIBRARIES.'Pear'.DS.'Pop3.php';
return true;
}
if (extension_loaded('imap') || function_exists('imap_open')) {
return true;
}
$prefix = PHP_SHLIB_SUFFIX === 'dll' ? 'php_' : '';
$EXTENSION = $prefix.'imap.'.PHP_SHLIB_SUFFIX;
if (function_exists('dl')) {
$fatalMessage = 'The system tried to load dynamically the '.$EXTENSION.' extension';
$fatalMessage .= '
If you see this message, that means the system could not load this PHP extension';
$fatalMessage .= '
Please enable the PHP Extension '.$EXTENSION;
ob_start();
echo $fatalMessage;
dl($EXTENSION);
$warnings = str_replace($fatalMessage, '', ob_get_clean());
if (extension_loaded('imap') || function_exists('imap_open')) {
return true;
}
}
if ($this->report) {
acym_display(
'The extension "'.$EXTENSION.'" could not be loaded, please change your PHP configuration to enable it or use the pop3 method without imap extension',
'error'
);
if (!empty($warnings)) {
acym_display($warnings, 'warning');
}
}
return false;
}
public function connect(): bool
{
if ($this->usePear) {
return $this->connectPear();
}
return $this->connectImap();
}
private function connectPear(): bool
{
ob_start();
$this->mailbox = new Pop3();
$timeout = $this->timeout;
if (!empty($timeout)) {
$this->mailbox->_timeout = $timeout;
}
$port = intval($this->port);
$secure = $this->secureMethod;
if (empty($port)) {
if ($secure == 'ssl') {
$port = '995';
} else {
$port = '110/pop3/notls';
}
}
$serverName = trim($this->server);
if (!empty($secure) && !strpos($serverName, '://')) {
$serverName = $secure.'://'.$serverName;
}
if (!$this->mailbox->connect($serverName, $port)) {
$warnings = ob_get_clean();
if ($this->report) {
acym_enqueueMessage(acym_translationSprintf('ACYM_ERROR_CONNECTING', $this->server.' : '.$port), 'error');
}
if (!empty($warnings) && $this->report) {
acym_display($warnings, 'warning');
}
return false;
}
$login = $this->mailbox->login(trim($this->username), trim($this->password), 'USER');
if (empty($login) || isset($login->code)) {
$warnings = ob_get_clean();
if ($this->report) {
acym_enqueueMessage(acym_translationSprintf('ACYM_ERROR_LOGIN', $this->username.':'.$this->password), 'error');
}
if (!empty($warnings) && $this->report) {
acym_display($warnings, 'warning');
}
return false;
}
ob_end_clean();
return true;
}
private function connectImap(): bool
{
if (empty($this->server)) {
acym_enqueueMessage(acym_translation('ACYM_CONFIGURE_BOUNCE'), 'warning');
return false;
}
ob_start();
$buff = $this->callImapFunction('imap_alerts', []);
$buff = $this->callImapFunction('imap_errors', []);
$timeout = intval($this->timeout);
if (!empty($timeout)) {
$this->callImapFunction('imap_timeout', [IMAP_OPENTIMEOUT, $timeout]);
}
$port = intval($this->port);
$secure = $this->secureMethod;
$protocol = $this->connectMethod;
$serverName = '{'.trim($this->server);
if (empty($port)) {
if ($protocol === 'imap') {
$port = $secure === 'ssl' ? '993' : '143';
} elseif ($protocol === 'pop3') {
$port = $secure === 'ssl' ? '995' : '110';
}
}
if (!empty($port)) {
$serverName .= ':'.$port;
}
if ($serverName === '{outlook.office365.com:993') {
$serverName .= '/imap';
}
if (!empty($secure)) {
$serverName .= '/'.$secure;
}
$email = trim($this->username);
if (strpos($email, '\\') !== false) {
[$user, $authuser] = explode('\\', $email);
[$x, $domain] = explode('@', $user);
$serverName .= '/authuser='.$user.'/user='.$authuser.'@'.$domain;
}
if ($this->selfSigned) {
$serverName .= '/novalidate-cert';
}
if (!empty($protocol)) {
$serverName .= '/service='.$protocol;
}
$serverName .= '}';
if ($this->imapConnectionMethod === 'oauth') {
$this->refreshToken();
$this->mailbox = imap2_open($serverName, trim($this->username), $this->oAuthToken, OP_XOAUTH2);
} else {
$this->mailbox = imap_open($serverName, trim($this->username), trim($this->password), OP_SILENT);
}
$warnings = ob_get_clean();
if ($this->report) {
if (!$this->mailbox) {
acym_enqueueMessage(acym_translationSprintf('ACYM_ERROR_CONNECTING', $serverName), 'error');
}
if (!empty($warnings)) {
acym_enqueueMessage($warnings, 'warning');
}
}
return (bool)$this->mailbox;
}
public function getNBMessages(): int
{
if ($this->usePear) {
$this->nbMessages = intval($this->mailbox->numMsg());
} else {
$this->nbMessages = intval($this->callImapFunction('imap_num_msg', [$this->mailbox]));
}
return $this->nbMessages;
}
protected function getMessage(int $msgNB)
{
if ($this->usePear) {
$message = new \stdClass();
$message->headerString = $this->mailbox->getRawHeaders($msgNB);
if (empty($message->headerString)) {
return false;
}
} else {
$message = $this->callImapFunction('imap_headerinfo', [$this->mailbox, $msgNB]);
}
return $message;
}
protected function deleteMessage($msgNB): void
{
if ($this->usePear) {
$this->mailbox->deleteMsg($msgNB);
} else {
$this->callImapFunction('imap_delete', [$this->mailbox, $msgNB]);
$this->callImapFunction('imap_expunge', [$this->mailbox]);
}
}
public function close(): void
{
if ($this->usePear) {
$this->mailbox->disconnect();
} else {
$this->callImapFunction('imap_close', [$this->mailbox]);
}
}
public function decodeMessage(bool $attachments = false): bool
{
if ($this->usePear) {
return $this->decodeMessagePear($attachments);
} else {
return $this->decodeMessageImap($attachments);
}
}
private function decodeMessagePear(bool $attachments): bool
{
$this->_message->headerinfo = $this->mailbox->getParsedHeaders($this->_message->messageNB);
if (empty($this->_message->headerinfo['subject'])) {
return false;
}
$this->_message->text = '';
$this->_message->html = $this->mailbox->getBody($this->_message->messageNB);
if (!empty($this->_message->headerinfo['content-type']) && strpos($this->_message->headerinfo['content-type'], 'boundary') !== false) {
$matches = [];
preg_match('#boundary="([^"]+)"#i', $this->_message->headerinfo['content-type'], $matches);
if (!empty($matches[1]) && strpos($this->_message->html, $matches[1]) !== false) {
$inlineImages = [];
$segments = explode('--'.$matches[1], $this->_message->html);
foreach ($segments as $segment) {
if (strpos($segment, 'Content-Type: text/plain') !== false) {
$matches = [];
preg_match('#boundary="([^"]+)"#i', $segment, $matches);
if (!empty($matches[1])) {
$parts = explode('--'.$matches[1], $segment);
foreach ($parts as $onePart) {
$content = trim(preg_replace('#(charset=".*?\r\n)|Content-(Type|ID|Disposition|Transfer-Encoding):.*?\r\n#is', "", $onePart));
if (strpos($onePart, 'Content-Transfer-Encoding') !== false) {
preg_match('#Content-Transfer-Encoding: (.+)#i', $onePart, $encoding);
$encoding = trim($encoding[1]);
if (!empty($encoding) && $encoding !== 'Content-Type:') {
$content = mb_convert_encoding($content, 'UTF-8', $encoding);
}
}
if (strpos($onePart, "Content-Type: text/plain") !== false) {
$this->_message->text .= ' '.$content;
}
if (strpos($onePart, "Content-Type: text/html") !== false) {
$this->_message->html .= ' '.$content;
}
}
} else {
$this->_message->html .= ' '.trim(preg_replace('#(charset=".*?\r\n)|Content-(Type|ID|Disposition|Transfer-Encoding):.*?\r\n#is', "", $segment));
}
} elseif (preg_match("#Content-Type: .*?/(png|jpg|jpeg|gif)#i", $segment) !== false) {
preg_match('#name="([^"]+)"#i', $segment, $filename);
if (empty($filename) || empty($filename[1])) {
continue;
}
$name = trim($filename[1]);
$filename = $name;
$extensionPos = strrpos($filename, '.');
if ($extensionPos === false) {
continue;
}
$extension = substr($filename, $extensionPos + 1);
$filename = preg_replace('#[^a-zA-Z0-9]#Uis', '_', substr($filename, 0, $extensionPos));
$uploadFolder = str_replace(['/', '\\'], DS, acym_getFilesFolder());
$pathToUpload = ACYM_ROOT.trim($uploadFolder, DS).DS;
if (file_exists($pathToUpload.$filename.'.'.$extension)) {
$fileNumber = 1;
while (file_exists($pathToUpload.$filename.'_('.$fileNumber.').'.$extension)) {
$fileNumber++;
}
$filename = $filename.'_('.$fileNumber.')';
}
if ($attachments && preg_match("#Content-Disposition: attachment;#i", $segment) !== false) {
$data = trim(substr($segment, strpos($segment, "\r\n\r\n")));
if (strpos($segment, 'Content-Transfer-Encoding: base64') !== false) {
$data = base64_decode($data);
}
try {
if (acym_writeFile($pathToUpload.$filename.'.'.$extension, $data)) {
$attachment = new \stdClass();
$attachment->filename = str_replace(DS, '/', trim($uploadFolder, '\\/')).'/'.$filename.'.'.$extension;
$attachment->size = filesize(ACYM_ROOT.$attachment->filename);
$this->attachments[] = $attachment;
}
} catch (\Exception $e) {
$completeFilename = $filename.'.'.$extension;
$this->display(acym_translationSprintf('ACYM_ERROR_UPLOADING_ATTACHMENT_X_TO_X', $completeFilename, $e->getMessage()), false);
}
} else {
preg_match('#Content-ID: <([^>]+)>#i', $segment, $contentID);
if (empty($contentID) || empty($contentID[1])) {
continue;
}
$contentID = trim($contentID[1]);
$data = trim(substr($segment, strpos($segment, "\r\n\r\n")));
if (strpos($segment, 'Content-Transfer-Encoding: base64') !== false) {
$data = base64_decode($data);
}
try {
if (acym_writeFile($pathToUpload.$filename.'.'.$extension, $data)) {
$inlineImages['cid:'.$contentID] = acym_rootURI().$uploadFolder.'/'.$filename.'.'.$extension;
}
} catch (\Exception $e) {
$this->display(acym_translationSprintf('ACYM_ERROR_UPLOAD_ATTACHMENT', $filename.'.'.$extension, $e->getMessage()), false);
}
}
}
}
if (!empty($inlineImages)) {
$this->_message->html = str_replace(array_keys($inlineImages), $inlineImages, $this->_message->html);
}
}
}
$this->_message->subject = $this->decodeHeader($this->_message->headerinfo['subject']);
if (empty($this->_message->header)) {
$this->_message->header = new \stdClass();
}
$this->_message->header->sender_email = @$this->_message->headerinfo['return-path'];
if (is_array($this->_message->header->sender_email)) {
$this->_message->header->sender_email = reset($this->_message->header->sender_email);
}
if (preg_match($this->detectEmail, $this->_message->header->sender_email, $results)) {
$this->_message->header->sender_email = $results[0];
}
$this->_message->header->sender_name = strip_tags(@$this->_message->headerinfo['from']);
$this->_message->header->reply_to_email = $this->_message->header->sender_email;
$this->_message->header->reply_to_name = $this->_message->header->sender_name;
$this->_message->header->from_email = $this->_message->header->sender_email;
$this->_message->header->from_name = $this->_message->header->sender_name;
return true;
}
private function decodeMessageImap(bool $attachments): bool
{
$this->_message->structure = $this->callImapFunction('imap_fetchstructure', [$this->mailbox, $this->_message->messageNB]);
if (empty($this->_message->structure)) {
return false;
}
$this->_message->headerinfo = $this->callImapFunction('imap_fetchheader', [$this->mailbox, $this->_message->messageNB]);
$this->_message->html = '';
$this->_message->text = '';
if ($this->_message->structure->type == 1) {
$this->_message->contentType = 2;
if ($this->_message->structure->subtype == "MIXED") {
$allParts = $this->explodeBodyMixed($this->_message->structure);
} else {
$allParts = $this->explodeBody($this->_message->structure);
}
$text = '';
foreach ($allParts as $num => $onePart) {
$decodedContent = $this->decodeContent($this->callImapFunction('imap_fetchbody', [$this->mailbox, $this->_message->messageNB, $num]), $onePart);
if ($onePart->subtype == 'HTML') {
$this->_message->html .= $decodedContent;
} else {
if ($onePart->subtype == 'PLAIN') {
$text .= $decodedContent."\n";
}
$this->_message->text .= $decodedContent."\n\n- - -\n\n";
}
if (!empty($this->action) && $attachments) {
if ((isset($onePart->parameters) && is_array($onePart->parameters)) || (isset($onePart->dparameters) && is_array($onePart->dparameters))) {
$this->uploadAttachment($onePart, $num);
} elseif (isset($onePart->parts) && is_array($onePart->parts)) {
foreach ($onePart->parts as $num2 => $innerPart) {
$this->uploadAttachment($innerPart, $num2);
}
}
}
}
if (!empty($this->action) && empty($this->_message->html)) {
$this->_message->html = $text."\n";
}
if (!empty($this->inlineImages)) {
$this->_message->html = str_replace(array_keys($this->inlineImages), $this->inlineImages, $this->_message->html);
}
} else {
if ($this->_message->structure->subtype == 'HTML') {
$this->_message->contentType = 1;
$this->_message->html = $this->decodeContent($this->callImapFunction('imap_body', [$this->mailbox, $this->_message->messageNB]), $this->_message->structure);
} else {
$this->_message->contentType = 0;
$this->_message->text = $this->decodeContent($this->callImapFunction('imap_body', [$this->mailbox, $this->_message->messageNB]), $this->_message->structure);
}
}
$this->_message->subject = $this->decodeHeader($this->_message->subject);
$this->decodeAddressImap('sender');
$this->decodeAddressImap('from');
$this->decodeAddressImap('reply_to');
$this->decodeAddressImap('to');
return true;
}
private function uploadAttachment($attachment, $num): void
{
if (!isset($attachment->disposition) || (strtolower($attachment->disposition) !== 'attachment' && strtolower($attachment->disposition) !== 'inline')) {
return;
}
$name = empty($attachment->dparameters[0]->value) ? $attachment->parameters[0]->value : $attachment->dparameters[0]->value;
$filename = $this->decodeHeader($name);
$extensionPos = strrpos($filename, '.');
if ($extensionPos === false) {
foreach ($attachment->parameters as $key => $oneParam) {
if (strtolower($oneParam->attribute) == 'name') {
$filename = $this->decodeHeader($attachment->parameters[$key]->value);
$extensionPos = strrpos($filename, '.');
break;
}
}
}
if ($extensionPos === false) {
return;
}
$extension = substr($filename, $extensionPos + 1);
if (!in_array(strtolower($extension), $this->allowed_extensions)) {
return;
}
$filename = preg_replace('#[^a-zA-Z0-9]#Uis', '_', substr($filename, 0, $extensionPos));
$uploadFolder = str_replace(['/', '\\'], DS, acym_getFilesFolder());
$pathToUpload = ACYM_ROOT.trim($uploadFolder, DS).DS;
if (file_exists($pathToUpload.$filename.'.'.$extension)) {
$fileNumber = 1;
while (file_exists($pathToUpload.$filename.'_('.$fileNumber.').'.$extension)) {
$fileNumber++;
}
$filename = $filename.'_('.$fileNumber.')';
}
$data = $this->decodeContent($this->callImapFunction('imap_fetchbody', [$this->mailbox, $this->_message->messageNB, $num]), $attachment);
try {
if (acym_writeFile($pathToUpload.$filename.'.'.$extension, $data)) {
if (strtolower($attachment->disposition) !== 'attachment' && !empty($attachment->id) && in_array(
strtolower($attachment->subtype),
acym_getImageFileExtensions()
)) {
$this->inlineImages['cid:'.trim($attachment->id, '<>')] = acym_rootURI().$uploadFolder.'/'.$filename.'.'.$extension;
} else {
$attachment = new \stdClass();
$attachment->filename = str_replace(DS, '/', trim($uploadFolder, '\\/')).'/'.$filename.'.'.$extension;
$attachment->size = filesize(ACYM_ROOT.$attachment->filename);
$this->attachments[] = $attachment;
}
}
} catch (\Exception $e) {
$completeFilename = $filename.'.'.$extension;
$this->display(acym_translationSprintf('ACYM_ERROR_UPLOADING_ATTACHMENT_X_TO_X', $completeFilename, $e->getMessage()), false);
}
}
public function handleMessages(): void
{
$maxMessages = min($this->nbMessages, $this->config->get('bounce_max', 0));
if (empty($maxMessages)) {
$maxMessages = min($this->nbMessages, 500);
}
if ($this->report) {
if (function_exists('apache_get_modules')) {
$modules = apache_get_modules();
$this->mod_security2 = in_array('mod_security2', $modules);
}
@ini_set('output_buffering', 'off');
@ini_set('zlib.output_compression', 0);
if (!headers_sent()) {
while (ob_get_level() > 0 && $this->obend++ < 3) {
@ob_end_flush();
}
}
$disp = "