19 public $markup = [
'b' =>
'*',
'i' =>
'/',
'u' =>
'_'];
35 $this->
component(
'log')->debug(
'Initializing Swift Mailer version ' . \Swift::VERSION,__FILE__,__LINE__);
51 if(!$this->restrict)
return $recipients;
53 foreach(\
Rsi\Record::explode($recipients) as $key => $value){
54 $email = is_numeric($key) ? $value : $key;
55 foreach($this->restrict as $filter)
if(preg_match(substr($filter,0,1) ==
'/' ? $filter :
'/' . preg_quote($filter,
'/') .
'/',$email)){
56 $allowed[$key] = $value;
69 protected function log($message,$result,$failures =
null,$context =
null){
70 $context = array_merge($context ?: [],compact(
'result',
'failures'));
71 foreach([
'from',
'to',
'cc',
'bcc',
'subject',
'body'] as $key)
if($value = call_user_func([$message,
'get' . ucfirst($key)])) $context[$key] = $value;
72 $this->
component(
'log')->debug(
'Sending mail "' . $message->getSubject() .
'"',__FILE__,__LINE__,$context);
82 preg_match(
'/^https?:\\/\\//i',$filename) ||
83 (preg_match(
'/^[\\w\\-\\/]+\\.' . $ext .
'$/i',$filename) && is_file($filename = \
Rsi\Http::docRoot() . DIRECTORY_SEPARATOR . $filename));
92 protected function dataTags($body,$data,$tag =
null){
93 return $this->dataPrefix && preg_match_all(
"/<$tag(?=\\s)[^>]*\\s{$this->dataPrefix}$data(?=[=\\s>]).*?>/is",$body,$matches) ? $matches[0] : [];
102 $body = preg_replace(
'/<style>.*?<\\/style>/s',
'',$body);
103 foreach($this->markup as $tag => $char) $body = preg_replace(
"/<($tag\\b.*?|\\/$tag)>/",$char,$body);
104 if(preg_match_all(
'/<a.*?href\s*=\s*([^\s>]+).*?>(.*?)<\\/a>/',$body,$matches,PREG_SET_ORDER))
foreach($matches as list($full,$link,$descr)){
105 $link = \Rsi\Str::stripQuotes($link);
106 $body = str_replace($full,$descr . ($link == $descr ?
'' :
" ($link)"),$body);
108 return strip_tags($body);
119 if($this->styleInline){
121 if(preg_match_all(
'/<link rel=[\'"]stylesheet[\'"] href=[\'"]\\/?([^\'"]+)[\'"].*?>/i',$body,$matches,PREG_SET_ORDER))
foreach($matches as list($full,$filename)){
122 if(!$this->
validFilename($filename,
'css')) $body = str_replace($full,basename($filename) .
' not found!',$body);
123 elseif($this->styleInline == self::STYLE_INLINE_FILE) $body = str_replace($full,
'<style>' . file_get_contents($filename) .
'</style>',$body);
125 $style .= file_get_contents($filename);
126 $body = str_replace($full,
'',$body);
129 if(($this->styleInline == self::STYLE_INLINE_HTML) && preg_match_all(
'/<style>(.*?)<\\/style>/is',$body,$matches,PREG_SET_ORDER))
foreach($matches as list($full,$inline)){
131 $body = str_replace($full,
'',$body);
133 if($style) $body = (new \TijsVerkoyen\CssToInlineStyles\CssToInlineStyles($body,$style))->convert();
137 foreach($this->
dataTags($body,
'embed',
'img') as $tag)
if($this->
validFilename($filename = \
Rsi\Record::iget(\
Rsi\Str::attributes($tag),
'src'),$this->imageExtMask))
try{
138 if(!array_key_exists($filename,$cids)){
139 $cids[$filename] = $cid = $message->embed(\Swift_Image::fromPath($this->
translateFilename($filename)));
140 $log->debug(
"Embedded image '$filename' as '$cid'",__FILE__,__LINE__);
142 $body = str_replace($tag,str_replace($filename,$cids[$filename],$tag),$body);
145 $log->info(
"Could not inline image '$tag': " . $e->getMessage(),$e->getFile(),$e->getLine(),$e->getTrace());
148 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{
150 if($download = \
Rsi\Record::iget($attributes,
'download')) $attachment->setFilename($download);
151 $message->attach($attachment);
152 $log->debug(
"Attached file '$filename'",__FILE__,__LINE__);
153 if(!is_bool($replace = \
Rsi\Record::iget($attributes,$this->dataPrefix . $data))) $body = preg_replace(
'/' . preg_quote($tag,
'/') .
'.*?<\\/a>/is',$replace,$body);
156 $log->info(
"Could not attach file '$tag': " . $e->getMessage(),$e->getFile(),$e->getLine(),$e->getTrace());
169 public function message($from =
null,$to =
null,$subject =
null,$body =
null,$html =
false){
170 if(is_array($tags = $body)){
172 $body = $trans->id(str_replace(
'*',$id = $subject,$this->bodyId),$tags);
173 $subject = $trans->id(str_replace(
'*',$id,$this->subjectId),$tags);
175 $message = new \Swift_Message();
176 $message->setFrom($from ?: ($this->defaultFrom ?: ini_get(
'sendmail_from')));
177 $message->setTo($to);
178 if($subject) $message->setSubject($subject);
180 if($html) $message->setBody($this->
htmlBody($body,$message),
'text/html')->addPart($this->
textBody($body),
'text/plain');
181 else $message->setBody($body);
192 $html = $message->html;
193 foreach($message->attachments as $attachment)
if($html && $attachment->id)
194 $html = preg_replace(
'/\\bcid:' . preg_quote($attachment->id,
'/') .
'\\b/',
$forward->embed(
new \Swift_Image($attachment->data,$attachment->name)),$html);
195 else $forward->attach(
new \Swift_Attachment($attachment->data,$attachment->name));
196 if($html)
$forward->setBody($html,
'text/html')->addPart($message->plain,
'text/plain');
197 else $forward->setBody($message->plain);
206 $mailer = $this->mailer;
207 if(array_key_exists($host,$this->mailers)){
208 if(!array_key_exists(
'mailer',$config = $this->mailers[$host])){
209 $this->mailers[$host][
'mailer'] = new \Swift_Mailer(
new \Swift_SmtpTransport($config[
'host'] ?? $host,$config[
'port'] ?? 25));
210 if($this->_logger) $this->mailers[$host][
'mailer']->registerPlugin(
new \Swift_Plugins_LoggerPlugin($this->_logger));
212 $mailer = $this->mailers[$host][
'mailer'];
223 public function send($message){
224 if(!($message instanceof \Swift_Message)) $message = call_user_func_array([$this,
'message'],func_get_args());
226 foreach([
'To',
'Cc',
'Bcc'] as $key)
if($recipients = call_user_func([$message,
'get' . $key])){
228 call_user_func([$message,
'set' . $key],$recipients);
230 $result = $failures =
false;
232 $result = $this->
mailer(($from = imap_rfc822_parse_adrlist(\
Rsi\Record::key($message->getFrom()),
'')) ? $from[0]->host :
null)->send($message,$failures);
233 $this->
log($message,$result,$failures);
234 $message->setCc(
null);
235 $message->setBcc(
null);
237 foreach(
$forward as $key => $mask)
if($key && !preg_match($mask,implode(
'#',(array)call_user_func([$message,
'get' . ucfirst($key)]))))
continue 2;
239 $this->
mailer->send($message);
243 $this->
log($message,
null,
null,$this->_logger ? [
'logger' => $this->_logger->dump()] :
null);
256 public function queue($message,$delay = 0,$id =
null){
258 if($this->
queue->path){
259 if(!$id)
while(file_exists($this->
queue->path . ($id = \
Rsi\Str::random(32,
'+')) . $this->queue->ext));
261 \
Rsi\File::serialize($temp = ($filename = $this->
queue->path . $id . $this->queue->ext) . $this->queue->tempExt,$message) &&
262 touch($temp,time() + $delay)
263 ) $result = \Rsi\File::rename($temp,$filename);
264 else $this->
component(
'log')->error(
"Could not queue message '$id': " . $message->getSubject());
266 return $result ? $id :
false;
273 public function delete($id){
274 return \Rsi\File::unlink($this->
queue->path . $id . $this->queue->ext);
281 public function spool($time =
null){
283 if($time) $time += time();
285 foreach((
new \FilesystemIterator($this->
queue->path)) as $filename => $file)
try{
286 if(\
Rsi\Str::endsWith($filename,$this->
queue->ext . $this->queue->tempExt)){
287 if($file->getMTime() < time() - $this->
queue->timeout)
288 $log->warning(
'Purged temp message',[
'result' => \
Rsi\File::unlink($file->getPathname())]);;
290 elseif(\
Rsi\Str::endsWith($filename,$this->
queue->ext . $this->queue->busyExt)){
291 if($file->getMTime() < time() - $this->
queue->timeout)
292 $log->warning(
'Reset busy message',[
'result' => rename($filename,substr($file->getPathname(),0,-strlen($this->
queue->busyExt)))]);
295 \
Rsi\Str::endsWith($filename,$this->
queue->ext) &&
296 ($file->getMTime() <= time()) &&
297 class_exists(
'Swift_Message') &&
298 rename($filename,$busy = $filename . $this->
queue->busyExt) &&
299 $this->send(\
Rsi\File::unserialize($busy)) &&
302 if($time && ($time < time()))
break;
313 $this->_mailer = $this->_transport =
null;
320 public function box($name =
null){
321 return $this->imap->mailbox($name);
326 $imap = new \Rsi\Wrapper\Record($this->
config(
'imap'));
327 $this->_imap = new \Rsi\Imap($imap->host,$imap->username,$imap->password,$imap->options,$imap->port);
334 $this->_mailer = new \Swift_Mailer($this->transport);
335 if($this->_fred->debug) $this->_mailer->registerPlugin(
new \Swift_Plugins_LoggerPlugin(
336 $this->_logger =
new \Swift_Plugins_Loggers_ArrayLogger()
343 if(!$this->_transport){
344 if($smtp = $this->
config(
'smtp')){
345 if($host = $smtp[
'host'] ??
null) $port = $smtp[
'port'] ?? 25;
347 $host = ini_get(
'SMTP');
348 $port = ini_get(
'smtp_port');
350 $this->_transport = new \Swift_SmtpTransport($host,$port,$smtp[
'security'] ??
null);
351 if($stream = $smtp[
'stream'] ??
null) $this->_transport->setStreamOptions($stream);
352 if($username = $smtp[
'username'] ??
null) $this->_transport->setUsername($username);
353 if($password = $smtp[
'password'] ??
null) $this->_transport->setPassword($password);
355 elseif($sendmail = $this->
config(
'sendmail')) $this->_transport = new \Swift_SendmailTransport($sendmail);
356 else $this->_transport = new \Swift_MailTransport();
362 if(!$this->_queue) $this->_queue = new \Rsi\Wrapper\Record($this->
config(
'queue') + [
365 'tempExt' =>
'.temp',
366 'busyExt' =>
'.busy',
372 public function __invoke($from =
null,$to =
null,$subject =
null,$body =
null,$html =
false){
373 return $this->
send($from,$to,$subject,$body,$html);