15 public $markup = [
'b' =>
'*',
'i' =>
'/',
'u' =>
'_'];
30 $this->
component(
'log')->debug(
'Initializing Swift Mailer version ' . \Swift::VERSION,__FILE__,__LINE__);
38 if(!$this->restrict)
return $recipients;
40 foreach(\
Rsi\Record::explode($recipients) as $key => $value){
41 $email = is_numeric($key) ? $value : $key;
42 foreach($this->restrict as $filter)
if(preg_match(substr($filter,0,1) ==
'/' ? $filter :
'/' . preg_quote($filter,
'/') .
'/',$email)){
43 $allowed[$key] = $value;
56 protected function log($message,$result,$failures = null,$context = null){
57 $context = array_merge($context ?: [],compact(
'result',
'failures'));
58 foreach([
'from',
'to',
'cc',
'bcc',
'subject',
'body'] as $key)
if($value = call_user_func([$message,
'get' . ucfirst($key)])) $context[$key] = $value;
59 $this->
component(
'log')->debug(
'Sending mail "' . $message->getSubject() .
'"',__FILE__,__LINE__,$context);
69 preg_match(
'/^https?:\\/\\//i',$filename) ||
70 (preg_match(
'/^[\\w\\-\\/]+\\.' . $ext .
'$/i',$filename) && is_file($filename = \
Rsi\Http::docRoot() . DIRECTORY_SEPARATOR . $filename));
79 protected function dataTags($body,$data,$tag = null){
80 return $this->dataPrefix && preg_match_all(
"/<$tag(?=\\s)[^>]*\\s{$this->dataPrefix}$data(?=[=\\s>]).*?>/is",$body,$matches) ? $matches[0] : [];
89 $body = preg_replace(
'/<style>.*?<\\/style>/s',
'',$body);
90 foreach($this->markup as $tag => $char) $body = preg_replace(
"/(<$tag.*?>|<\\/$tag>)/",$char,$body);
91 if(preg_match_all(
'/<a.*?href\s*=\s*([^\s>]+).*?>(.*?)<\\/a>/',$body,$matches,PREG_SET_ORDER))
foreach($matches as list($full,$link,$descr)){
92 $link = \Rsi\Str::stripQuotes($link);
93 $body = str_replace($full,$descr . ($link == $descr ?
'' :
" ($link)"),$body);
95 return strip_tags($body);
107 if(preg_match_all(
'/<link rel=[\'"]stylesheet[\'"] href=[\'"]\\/?([^\'"]+)[\'"].*?>/i',$body,$matches,PREG_SET_ORDER))
foreach($matches as list($full,$filename)){
108 if($this->
validFilename($filename,
'css')) $style .= file_get_contents($filename);
109 else $full = basename($filename) .
' not found!';
110 $body = str_replace($full,
'',$body);
112 if(preg_match_all(
'/<style>(.*?)<\\/style>/is',$body,$matches,PREG_SET_ORDER))
foreach($matches as list($full,$inline)){
114 $body = str_replace($full,
'',$body);
116 if($style) $body = (new \TijsVerkoyen\CssToInlineStyles\CssToInlineStyles($body,$style))->convert();
119 foreach($this->
dataTags($body,
'embed',
'img') as $tag)
if($this->
validFilename($filename = \
Rsi\Record::iget(\
Rsi\Str::attributes($tag),
'src'),$this->imageExtMask))
try{
120 if(!array_key_exists($filename,$cids)){
121 $cids[$filename] = $cid = $message->embed(\Swift_Image::fromPath($filename));
122 $log->debug(
"Embedded image '$filename' as '$cid'",__FILE__,__LINE__);
124 $body = str_replace($tag,str_replace($filename,$cids[$filename],$tag),$body);
127 $log->info(
"Could not inline image '$tag': " . $e->getMessage(),$e->getFile(),$e->getLine(),$e->getTrace());
130 foreach($this->
dataTags($body,$data =
'attach',
'a') as $tag)
if($this->
validFilename($filename = \
Rsi\Record::iget($attributes = \
Rsi\Str::attributes($tag),
'href'),$this->attachmentExtMask))
try{
131 $attachment = \Swift_Attachment::fromPath($filename);
132 if($download = \
Rsi\Record::iget($attributes,
'download')) $attachment->setFilename($download);
133 $message->attach($attachment);
134 $log->debug(
"Attached file '$filename'",__FILE__,__LINE__);
135 if(!is_bool($replace = \
Rsi\Record::iget($attributes,$this->dataPrefix . $data))) $body = preg_replace(
'/' . preg_quote($tag,
'/') .
'.*?<\\/a>/is',$replace,$body);
138 $log->info(
"Could not attach file '$tag': " . $e->getMessage(),$e->getFile(),$e->getLine(),$e->getTrace());
151 public function message($from = null,$to = null,$subject = null,$body = null,$html =
false){
152 if(is_array($tags = $body)){
154 $body = $trans->id(str_replace(
'*',$id = $subject,$this->bodyId),$tags);
155 $subject = $trans->id(str_replace(
'*',$id,$this->subjectId),$tags);
157 $message = new \Swift_Message();
158 $message->setFrom($from ?: ($this->defaultFrom ?: ini_get(
'sendmail_from')));
159 $message->setTo($to);
160 if($subject) $message->setSubject($subject);
162 if($html) $message->setBody($this->
htmlBody($body,$message),
'text/html')->addPart($this->
textBody($body),
'text/plain');
163 else $message->setBody($body);
173 $mailer = $this->mailer;
174 if(array_key_exists($host,$this->mailers)){
175 if(!array_key_exists(
'mailer',$config = $this->mailers[$host])){
176 $this->mailers[$host][
'mailer'] = new \Swift_Mailer(
new \Swift_SmtpTransport($config[
'host'] ?? $host,$config[
'port'] ?? 25));
177 if($this->_logger) $this->mailers[$host][
'mailer']->registerPlugin(
new \Swift_Plugins_LoggerPlugin($this->_logger));
179 $mailer = $this->mailers[$host][
'mailer'];
190 public function send($message){
191 if(!($message instanceof \Swift_Message)) $message = call_user_func_array([$this,
'message'],func_get_args());
192 foreach([
'To',
'Cc',
'Bcc'] as $key) call_user_func([$message,
'set' . $key],$this->
filterRecipients(call_user_func([$message,
'get' . $key])));
193 $result = $failures =
false;
195 $result = $this->
mailer(($from = imap_rfc822_parse_adrlist(\
Rsi\Record::key($message->getFrom()),null)) ? $from[0]->host : null)->send($message,$failures);
196 $this->
log($message,$result,$failures);
197 $message->setCc(null);
198 $message->setBcc(null);
199 if($result)
foreach($this->forward as
$forward){
200 foreach($forward as $key => $mask)
if($key && !preg_match($mask,implode(
'#',(array)call_user_func([$message,
'get' . ucfirst($key)]))))
continue 2;
201 $message->setTo($forward[null]);
202 $this->
mailer->send($message);
206 $this->
log($message,null,null,$this->_logger ? [
'logger' => $this->_logger->dump()] : null);
219 public function queue($message,$delay = 0,$id = null){
221 if($this->
queue->path){
222 if(!$id)
while(file_exists($this->
queue->path . ($id = \
Rsi\Str::random(32,
'+')) . $this->queue->ext));
224 \
Rsi\File::serialize($temp = ($filename = $this->
queue->path . $id . $this->queue->ext) . $this->queue->tempExt,$message) &&
225 touch($temp,time() + $delay)
226 ) $result = rename($temp,$filename);
227 else $this->
component(
'log')->error(
"Could not queue message '$id': " . $message->getSubject());
229 return $result ? $id :
false;
236 public function delete($id){
237 return \Rsi\File::unlink($this->
queue->path . $id . $this->queue->ext);
244 public function spool($time = null){
246 if($time) $time += time();
248 foreach((
new \FilesystemIterator($this->
queue->path)) as $filename => $file)
try{
249 if(\
Rsi\Str::endsWith($filename,$this->
queue->ext . $this->queue->tempExt)){
250 if($file->getMTime() < time() - $this->
queue->timeout)
251 $log->warning(
'Purged temp message',[
'result' => \
Rsi\File::unlink($file->getPathname())]);;
253 elseif(\
Rsi\Str::endsWith($filename,$this->
queue->ext . $this->queue->busyExt)){
254 if($file->getMTime() < time() - $this->
queue->timeout)
255 $log->warning(
'Reset busy message',[
'result' => rename($filename,substr($file->getPathname(),0,-strlen($this->
queue->busyExt)))]);
258 \
Rsi\Str::endsWith($filename,$this->
queue->ext) &&
259 ($file->getMTime() <= time()) &&
260 class_exists(
'Swift_Message') &&
261 rename($filename,$busy = $filename . $this->
queue->busyExt) &&
262 $this->
send(\
Rsi\File::unserialize($busy)) &&
265 if($time && ($time < time()))
break;
277 public function box($name = null){
278 return $this->imap->mailbox($name);
283 $imap = new \Rsi\Wrapper\Record($this->
config(
'imap'));
284 $this->_imap = new \Rsi\Imap($imap->host,$imap->username,$imap->password,$imap->options,$imap->port);
291 $this->_mailer = new \Swift_Mailer($this->transport);
292 if($this->_fred->debug) $this->_mailer->registerPlugin(
new \Swift_Plugins_LoggerPlugin(
293 $this->_logger =
new \Swift_Plugins_Loggers_ArrayLogger()
300 if(!$this->_transport){
301 if($smtp = $this->
config(
'smtp')){
302 if($host = $smtp[
'host'] ?? null) $port = $smtp[
'port'] ?? 25;
304 $host = ini_get(
'SMTP');
305 $port = ini_get(
'smtp_port');
307 $this->_transport = new \Swift_SmtpTransport($host,$port);
308 if($username = $smtp[
'username'] ?? null) $this->_transport->setUsername($username);
309 if($password = $smtp[
'password'] ?? null) $this->_transport->setPassword($password);
311 elseif($sendmail = $this->
config(
'sendmail')) $this->_transport = new \Swift_SendmailTransport($sendmail);
312 else $this->_transport = new \Swift_MailTransport();
318 if(!$this->_queue) $this->_queue = new \Rsi\Wrapper\Record($this->
config(
'queue') + [
321 'tempExt' =>
'.temp',
322 'busyExt' =>
'.busy',
328 public function __invoke($from = null,$to = null,$subject = null,$body = null,$html =
false){
329 return $this->
send($from,$to,$subject,$body,$html);
log($message, $result, $failures=null, $context=null)
Add a sent message to the log.
__invoke($from=null, $to=null, $subject=null, $body=null, $html=false)
$restrict
Regular expressions that the e-mail address has to match (at least one; empty = allow all)...
queue($message, $delay=0, $id=null)
Add a message to the queue (with possible delay).
$attachmentExtMask
Allowed file extensions for (local) attachments.
$bodyId
Get message body from translation component (when body is array of tags); asterisk is...
$dataPrefix
Prefix for data tags (empty = do not allow).
config($key, $default=null)
Retrieve a config value.
spool($time=null)
Spool the message queue.
message($from=null, $to=null, $subject=null, $body=null, $html=false)
Create a new Swift Mailer message object.
$subjectId
Get subject from translation component (when body is array of tags); asterisk is. ...
$imageExtMask
Allowed file extensions for (local) embedable images.
$forward
Forwarding rules (array of records with regex for from, to, cc, bcc, subject, and/or body; if all...
dataTags($body, $data, $tag=null)
Find all tags with a certain data tag.
htmlBody($body, $message)
Process HTML body.
$mailers
Domain specific mailer/transport (key = domain name; value = array with mailer or host...
textBody($body)
Remove all tags from the message body.
mailer($host=null)
Domain specific mailer.
send($message)
Send a message.
validFilename($filename, $ext)
Check if a filename is valid (external or in document root).
filterRecipients($recipients)
Filter a list of recipients.
component($name)
Get a component (local or default).
box($name=null)
Open an IMAP mailbox.