You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

499 lines
16 KiB

<?php
// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
//
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
// $Id$
/**
* set some default params (mainly utf8 as tiki is utf8) + use the mailCharset pref from a user
*/
use Laminas\Mail\Exception\ExceptionInterface as ZendMailException;
use SlmMail\Exception\ExceptionInterface as SlmMailException;
class TikiMail
{
/**
* @var \Laminas\Mail\Message
*/
private $mail;
private $charset;
public $errors;
/**
* @param string|null $user to username
* @param string|null $from from email
* @param string|null $fromName from Name
*/
public function __construct($user = null, $from = null, $fromName = null)
{
global $user_preferences, $prefs;
require_once __DIR__ . '/../mail/maillib.php';
$tikilib = TikiLib::lib('tiki');
$userlib = TikiLib::lib('user');
$to = '';
$this->errors = [];
if (! empty($user)) {
if ($userlib->user_exists($user)) {
$to = $userlib->get_user_email($user);
$tikilib->get_user_preferences($user, ['mailCharset']);
$this->charset = $user_preferences[$user]['mailCharset'];
} else {
$str = tra('Mail to: User not found');
trigger_error($str);
$this->errors = [$str];
return;
}
}
if (! empty($from)) {
$this->mail = tiki_get_basic_mail();
try {
$this->mail->setFrom($from, $fromName);
$this->mail->setSender($from);
} catch (Exception $e) {
// was already set, then do nothing
}
} else {
$this->mail = tiki_get_admin_mail($fromName);
}
if (! empty($to)) {
$this->mail->addTo($to);
}
if (empty($this->charset)) {
$this->charset = $prefs['users_prefs_mailCharset'];
}
}
public function setUser($user)
{
}
public function setFrom($email, $name = null)
{
if (! $name) {
$name = null; // zend now requires "Name must be a string" (or null, not false)
}
$this->mail->setFrom($email, $name);
}
public function setReplyTo($email, $name = null)
{
if (! $name) {
$name = null; // zend now requires "Name must be a string" (or null, not false)
}
$this->mail->setReplyTo($email, $name);
}
public function setSubject($subject)
{
$this->mail->setSubject($subject);
}
public function setHtml($html, $text = null, $images_dir = null)
{
global $prefs;
if ($prefs['mail_apply_css'] != 'n') {
$html = $this->applyStyle($html);
}
$body = $this->mail->getBody();
if (! ($body instanceof \Laminas\Mime\Message) && ! empty($body)) {
$this->convertBodyToMime($body);
$body = $this->mail->getBody();
}
if (! $body instanceof Laminas\Mime\Message) {
$body = new Laminas\Mime\Message();
}
$partHtml = false;
$partText = false;
$parts = [];
foreach ($body->getParts() as $part) {
/* @var $part Laminas\Mime\Part */
if ($part->getType() == Laminas\Mime\Mime::TYPE_HTML) {
$partHtml = $part;
$part->setContent($html);
if ($this->charset) {
$part->setCharset($this->charset);
}
} elseif ($part->getType() == Laminas\Mime\Mime::TYPE_TEXT) {
$partText = $part;
if ($text) {
$part->setContent($text);
if ($this->charset) {
$part->setCharset($this->charset);
}
}
} else {
$parts[] = $part;
}
}
if (! $partText && $text) {
$partText = new Laminas\Mime\Part($text);
$partText->setType(Laminas\Mime\Mime::TYPE_TEXT);
if ($this->charset) {
$partText->setCharset($this->charset);
}
}
if ($partText) {
$parts[] = $partText;
}
if (! $partHtml) {
$partHtml = new Laminas\Mime\Part($html);
$partHtml->setType(Laminas\Mime\Mime::TYPE_HTML);
if ($this->charset) {
$partHtml->setCharset($this->charset);
}
}
$parts[] = $partHtml;
$body->setParts($parts);
$this->mail->setBody($body);
// use multipart/alternative for mail clients to display html and fall back to plain text parts
if ($text) {
$this->mail->getHeaders()->get('content-type')->setType('multipart/alternative');
}
}
public function setText($text = '')
{
$body = $this->mail->getBody();
if ($body instanceof \Laminas\Mime\Message) {
$parts = $body->getParts();
$textPartFound = false;
foreach ($parts as $part) {
/* @var $part Laminas\Mime\Part */
if ($part->getType() == Laminas\Mime\Mime::TYPE_TEXT) {
$part->setContent($text);
if ($this->charset) {
$part->setCharset($this->charset);
}
$textPartFound = true;
break;
}
}
if (! $textPartFound) {
$part = new Laminas\Mime\Part($text);
$part->setType(Laminas\Mime\Mime::TYPE_TEXT);
if ($this->charset) {
$part->setCharset($this->charset);
}
$parts[] = $part;
}
$body->setParts($parts);
} else {
$this->mail->setBody($text);
if ($this->charset) {
$headers = $this->mail->getHeaders();
$contentType = $headers->get('Content-type');
if (! empty($contentType)) {
$headers->removeHeader($contentType);
}
$headers->addHeaderLine(
'Content-type: text/plain; charset=' . $this->charset
);
}
}
}
public function setCc($address)
{
foreach ((array) $address as $cc) {
$this->mail->addCc($cc);
}
}
public function setBcc($address)
{
foreach ((array) $address as $bcc) {
$this->mail->addBcc($bcc);
}
}
public function setHeader($name, $value)
{
$headers = $this->mail->getHeaders();
switch ($name) {
case 'Message-Id':
$headers->addHeader(Laminas\Mail\Header\MessageId::fromString('Message-ID: ' . trim($value)));
break;
case 'In-Reply-To':
$headers->addHeader(Laminas\Mail\Header\InReplyTo::fromString('In-Reply-To: ' . trim($value)));
break;
case 'References':
$headers->addHeader(Laminas\Mail\Header\References::fromString('References: ' . trim($value)));
break;
default:
$this->mail->getHeaders()->addHeaderLine($name, $value);
break;
}
}
public function addPart($content, $type)
{
$body = $this->mail->getBody();
if (! ($body instanceof \Laminas\Mime\Message)) {
$this->convertBodyToMime($body);
$body = $this->mail->getBody();
}
$part = new Laminas\Mime\Part($content);
$part->setType($type);
$part->setCharset($this->charset);
$body->addPart($part);
$headers = $this->mail->getHeaders();
$headers->removeHeader('Content-type');
$headers->addHeaderLine(
'Content-type: multipart/mixed; boundary="' . $body->getMime()->boundary() . '"'
);
}
/**
* Get the Laminas Message object
*
* @return \Laminas\Mail\Message
*/
public function getMessage()
{
return $this->mail;
}
public function send($recipients, $type = 'mail')
{
global $tikilib, $prefs;
$logslib = TikiLib::lib('logs');
$this->mail->getHeaders()->removeHeader('to');
foreach ((array) $recipients as $to) {
try {
$this->mail->addTo($to);
} catch (Laminas\Mail\Exception\InvalidArgumentException $e) {
$title = 'mail error';
$error = $e->getMessage();
$this->errors[] = $error;
$error = ' [' . $error . ']';
$logslib->add_log($title, $to . '/' . $this->mail->getSubject() . $error);
}
}
if ($prefs['zend_mail_handler'] == 'smtp' && $prefs['zend_mail_queue'] == 'y') {
$query = "INSERT INTO `tiki_mail_queue` (message) VALUES (?)";
$bindvars = [serialize($this->mail)];
$tikilib->query($query, $bindvars, -1, 0);
$title = 'mail';
} else {
try {
$email_body = $this->mail->getBody();
if ($prefs['email_footer']) {
if (is_string($email_body)) {
$new_body = $email_body . PHP_EOL . PHP_EOL . $prefs['email_footer'];
$this->mail->setBody($new_body);
} else {
foreach($email_body->getParts() as $part) {
$content = $part->getContent();
if ($part->getType() === 'text/html') {
$content = str_replace('</body>', '<br><br>' . $prefs['email_footer'] . '</body>', $content);
} else { // hopefully plain text
$content = $content . PHP_EOL . PHP_EOL . $prefs['email_footer'];
}
$part->setContent($content);
}
$this->mail->setBody($email_body);
}
}
tiki_send_email($this->mail);
$title = 'mail';
$error = '';
} catch (ZendMailException | SlmMailException $e) {
$title = 'mail error';
$error = $e->getMessage();
$this->errors[] = $error;
$error = ' [' . $error . ']';
}
if ($title == 'mail error' || $prefs['log_mail'] == 'y') {
foreach ($recipients as $u) {
$logslib->add_log($title, $u . '/' . $this->mail->getSubject() . $error);
}
}
}
return $title == 'mail';
}
protected function convertBodyToMime($text)
{
$textPart = new Laminas\Mime\Part($text);
$textPart->setType(Laminas\Mime\Mime::TYPE_TEXT);
$newBody = new Laminas\Mime\Message();
$newBody->addPart($textPart);
$this->mail->setBody($newBody);
}
public function addAttachment($data, $filename, $mimetype)
{
$body = $this->mail->getBody();
if (! ($body instanceof \Laminas\Mime\Message)) {
$this->convertBodyToMime($body);
$body = $this->mail->getBody();
}
$attachment = new Laminas\Mime\Part($data);
$attachment->setFileName($filename);
$attachment->setType($mimetype);
$attachment->setEncoding(Laminas\Mime\Mime::ENCODING_BASE64);
$attachment->setDisposition(Laminas\Mime\Mime::DISPOSITION_INLINE);
$body->addPart($attachment);
}
/**
* scramble an email with a method
*
* @param string $email email address to be scrambled
* @param string $method unicode or y: each character is replaced with the unicode value
* strtr: mr@tw.org -> mr AT tw DOT org
* x: mr@tw.org -> mr@xxxxxx
*
* @return string scrambled email
*/
public static function scrambleEmail($email, $method = 'unicode')
{
switch ($method) {
case 'strtr':
$trans = [ "@" => tra("(AT)"),
"." => tra("(DOT)")
];
return strtr($email, $trans);
case 'x':
$encoded = $email;
for ($i = strpos($email, "@") + 1, $istrlen_email = strlen($email); $i < $istrlen_email; $i++) {
if ($encoded[$i] != ".") {
$encoded[$i] = 'x';
}
}
return $encoded;
case 'unicode':
case 'y':// for previous compatibility
$encoded = '';
for ($i = 0, $istrlen_email = strlen($email); $i < $istrlen_email; $i++) {
$encoded .= '&#' . ord($email[$i]) . ';';
}
return $encoded;
case 'n':
default:
return $email;
}
}
private function collectCss()
{
static $css;
if ($css) {
return $css;
}
$cachelib = TikiLib::lib('cache');
if ($css = $cachelib->getCached('email_css')) {
return $css;
}
$headerlib = TikiLib::lib('header');
$files = $headerlib->get_css_files();
$contents = array_map(function ($file) {
if ($file[0] == '/') {
return file_get_contents($file);
} elseif (substr($file, 0, 4) == 'http') {
return TikiLib::lib('tiki')->httprequest($file);
} else {
if (strpos($file, 'themes/') === 0) { // only use the tiki base and current theme files
return file_get_contents(TIKI_PATH . '/' . $file);
}
}
}, $files);
$css = implode("\n\n", array_filter($contents));
$cachelib->cacheItem('email_css', $css);
return $css;
}
private function applyStyle($html)
{
$html = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' . $html;
$css = $this->collectCss();
$processor = new \TijsVerkoyen\CssToInlineStyles\CssToInlineStyles();
$html = $processor->convert($html, $css);
return $html;
}
}
/**
* Format text, sender and date for a plain text email reply
* - Split into 75 char long lines prepended with >
*
* @param $text email text to be quoted
* @param $from email from name/address to be quoted
* @param $date date of mail to be quoted
* @return string text ready for replying in a plain text email
*/
function format_email_reply(&$text, $from, $date)
{
$lines = preg_split('/[\n\r]+/', wordwrap($text));
for ($i = 0, $icount_lines = count($lines); $i < $icount_lines; $i++) {
$lines[$i] = '> ' . $lines[$i] . "\n";
}
$str = ! empty($from) ? $from . ' wrote' : '';
$str .= ! empty($date) ? ' on ' . $date : '';
$str = "\n\n\n" . $str . "\n" . implode($lines);
return $str;
}
/**
* Attempt to close any unclosed HTML tags
* Needs to work with what's inside the BODY
* originally from http://snipplr.com/view/3618/close-tags-in-a-htmlsnippet/
*
* @param $html html input
* @return string corrected html out
*/
function closetags($html)
{
#put all opened tags into an array
preg_match_all("#<([a-z]+)( .*)?(?!/)>#iU", $html, $result);
$openedtags = $result[1];
#put all closed tags into an array
preg_match_all("#</([a-z]+)>#iU", $html, $result);
$closedtags = $result[1];
$len_opened = count($openedtags);
# all tags are closed
if (count($closedtags) == $len_opened) {
return $html;
}
$openedtags = array_reverse($openedtags);
# close tags
for ($i = 0; $i < $len_opened; $i++) {
if (! in_array($openedtags[$i], $closedtags)) {
$html .= "</" . $openedtags[$i] . ">";
} else {
unset($closedtags[array_search($openedtags[$i], $closedtags)]);
}
}
return $html;
}