22 private $fromname =
'';
26 $this->headers = array();
29 $this->attachments = array();
30 $this->html_images = array();
31 $this->auto_embbed_images =
true;
32 $this->auto_embed_images =
true;
33 $this->boundary =
'==Multipart_Boundary_x' . md5((
string) microtime(
true)) .
'x';
34 $this->sendmail_path =
'';
36 $this->spam_protection =
true;
42 return filter_var((
string) $inAddress, FILTER_VALIDATE_EMAIL) !==
false;
51 foreach ($this->headers as $key =>
$value) {
52 $retval .= $key .
': ' .
$value .
"\n";
58 $name = trim((
string) $name);
61 if (strcasecmp($name,
'From') === 0) {
62 $from = $this->parse_address(
$value);
63 $this->from = $from[
'email'];
64 $this->fromname = $from[
'name'];
67 $this->headers[$name] =
$value;
71 $this->from = trim((
string) $email);
72 $this->fromname = trim((
string) $name);
73 $this->headers[
'From'] = $this->format_address($this->from, $this->fromname);
76 public function attachfile(
$file, $disp =
'attachment', $contentType =
'', $cid =
'') {
80 $this->errstr =
'Attachment nicht lesbar: ' .
$file;
84 $this->attachments[] = array(
86 'name' => basename(
$file),
87 'disposition' => $disp ?:
'attachment',
89 'cid' => (
string) $cid,
96 $file = (string) $inFileName;
98 if (function_exists(
'mime_content_type') && is_file(
$file)) {
99 $type = @mime_content_type(
$file);
105 $extension = strtolower((
string) strrchr(basename(
$file),
'.'));
107 switch ($extension) {
108 case '.gz':
return 'application/gzip';
109 case '.zip':
return 'application/zip';
110 case '.tar':
return 'application/x-tar';
112 case '.html':
return 'text/html';
113 case '.txt':
return 'text/plain';
115 case '.jpeg':
return 'image/jpeg';
116 case '.gif':
return 'image/gif';
117 case '.png':
return 'image/png';
118 case '.pdf':
return 'application/pdf';
119 case '.csv':
return 'text/csv';
120 case '.json':
return 'application/json';
121 default:
return 'application/octet-stream';
126 if (strlen(trim((
string) $text)) > 0) {
127 $this->textbody = (string) $text;
132 if (strlen(trim((
string) $text)) > 0) {
133 $this->htmlbody = (string) $text;
138 if (strlen(trim((
string) $text)) > 0) {
139 $this->htmlbody .= (string) $text;
152 if ($deliveryMode !==
'external') {
153 return $this->handle_internal_delivery(
162 $mail =
new PHPMailer(
true);
163 $mail->CharSet =
'UTF-8';
164 $mail->Encoding =
'base64';
170 $this->add_addresses(
$mail, $to,
'addAddress');
171 $this->add_addresses(
$mail,
$options[
'cc'] ?? $this->headers[
'Cc'] ?? array(),
'addCC');
172 $this->add_addresses(
$mail,
$options[
'bcc'] ?? $this->headers[
'Bcc'] ?? array(),
'addBCC');
173 $this->add_addresses(
$mail,
$options[
'reply_to'] ?? $this->headers[
'Reply-To'] ?? array(),
'addReplyTo');
177 $this->configure_headers(
$mail);
178 $this->configure_attachments(
$mail);
181 if ($spamReason !==
'') {
182 $this->errstr =
'Mail wurde durch den Spam-Schutz blockiert: ' . $spamReason;
183 $this->sys_msg(
'security', $to,
$subject, $this->errstr);
191 }
catch (PHPMailerException $e) {
192 $this->errstr = $e->getMessage();
193 }
catch (Throwable $e) {
194 $this->errstr = $e->getMessage();
197 $this->sys_msg(
'error', $to,
$subject, $this->errstr);
210 if (function_exists(
'dbx')) {
211 $configured =
dbx()->get_config(
'dbx',
'mail_delivery_mode',
'internal');
212 if (is_scalar($configured)) {
213 $mode = strtolower(trim((
string)$configured));
217 return in_array(
$mode, array(
'internal',
'disabled',
'external'),
true)
228 private function handle_internal_delivery(
234 $from = $this->normalize_from(
$options[
'from'] ?? null);
235 if ($from[
'email'] ===
'' && $this->from !==
'') {
236 $from = array(
'email' => $this->from,
'name' => $this->fromname);
241 'from' => $from[
'email'],
242 'to' => $this->address_debug($to),
243 'subject' => $subject,
245 if (function_exists(
'dbx')) {
249 $mode ===
'internal' ?
'mail-internal' :
'mail-disabled',
252 ?
'Externer E-Mail-Versand ist ausgeschaltet; Ereignis wurde intern angenommen.'
253 :
'E-Mail-Versand ist global deaktiviert.',
256 }
catch (Throwable $e) {
262 if (
$mode ===
'internal') {
266 $this->errstr =
'E-Mail-Versand ist global deaktiviert.';
270 private function mail_config(
$options) {
273 if (function_exists(
'dbx')) {
274 $cfg =
dbx()->get_config(
'dbx',
'mail');
275 if (is_array(
$cfg)) {
279 $defaultMail =
dbx()->get_config(
'dbx',
'default_mail');
280 if ($defaultMail !==
'undef' && $defaultMail !==
'') {
281 $config[
'default'] = (string) $defaultMail;
285 if (is_string(
$options[
'mail'] ??
null)) {
291 if (is_array(
$options[
'mail'] ??
null)) {
299 $profiles = $this->mail_profiles_from_config(
$config);
304 $profileName = (string) (
$options[
'mail_profile'] ??
$options[
'profile'] ??
'');
305 $from = $this->normalize_from(
$options[
'from'] ??
null);
307 if ($profileName ===
'' && $from[
'email'] !==
'') {
308 $profileName = $this->profile_for_sender($profiles, $from[
'email']);
311 if ($profileName ===
'') {
312 $profileName = (string) (
$config[
'default'] ??
'');
315 if ($profileName ===
'' || !isset($profiles[$profileName]) || !is_array($profiles[$profileName])) {
316 $profileName = (string) (
$config[
'default'] ??
'');
319 if (($profileName ===
'' || !isset($profiles[$profileName])) && count($profiles)) {
320 $keys = array_keys($profiles);
321 $profileName = (string)
$keys[0];
326 if ($profileName !==
'' && isset($profiles[$profileName]) && is_array($profiles[$profileName])) {
327 return array_replace_recursive(
$base, $profiles[$profileName], array(
'profile' => $profileName));
333 private function mail_profiles_from_config(array
$config) {
334 if (is_array(
$config[
'profiles'] ??
null)) {
340 if ($key ===
'default' || $key ===
'profiles') {
344 $profiles[(string) $key] =
$value;
351 private function mail_base_config(array
$config) {
364 private function profile_for_sender(array $profiles, $email) {
365 $domain = strtolower((
string) substr(strrchr((
string) $email,
'@') ?:
'', 1));
366 if ($domain ===
'') {
370 foreach ($profiles as $name =>
$profile) {
371 if ($this->profile_matches_domain(
$profile, $domain,
false)) {
372 return (
string) $name;
376 foreach ($profiles as $name =>
$profile) {
377 if ($this->profile_matches_domain(
$profile, $domain,
true)) {
378 return (
string) $name;
385 private function profile_matches_domain(
$profile, $domain, $allowWildcard) {
391 if (is_string($domains)) {
392 $domains = preg_split(
'/[;,]+/', $domains);
395 foreach ((array) $domains as
$allowed) {
403 if ($allowWildcard && (
$allowed ===
'*' || (substr(
$allowed, 0, 2) ===
'*.' && str_ends_with($domain, substr(
$allowed, 1))))) {
411 private function configure_transport(PHPMailer
$mail, array
$config) {
412 $transport = strtolower((
string) (
$config[
'transport'] ??
$config[
'type'] ??
''));
413 $host = trim((
string) (
$config[
'host'] ??
$config[
'smtp_host'] ??
''));
415 if ($transport ===
'smtp' || $host !==
'') {
416 $username = (string) (
$config[
'user'] ??
$config[
'username'] ??
'');
418 $auth = (bool) (
$config[
'auth'] ??
$config[
'smtp_auth'] ?? ($username !==
''));
420 if ($auth && ($username ===
'' ||
$password ===
'')) {
421 throw new PHPMailerException(
'SMTP auth configuration incomplete.');
428 $mail->SMTPAuth = $auth;
429 $mail->Username = $username;
432 $secure = strtolower((
string) (
$config[
'secure'] ??
$config[
'smtp_secure'] ??
''));
433 if (in_array($secure, array(
'tls',
'ssl'),
true)) {
434 $mail->SMTPSecure = $secure;
439 if ($transport ===
'sendmail' || $this->sendmail_path !==
'' || !empty(
$config[
'sendmail_path'])) {
441 $path = $this->sendmail_path ?: (string) (
$config[
'sendmail_path'] ??
'');
443 $mail->Sendmail = $path;
452 $forceFrom = !empty(
$config[
'force_from']);
453 $from = $forceFrom ? array(
'email' =>
'',
'name' =>
'') : $this->normalize_from(
$options[
'from'] ?? null);
455 if ($from[
'email'] ===
'') {
456 $from = $this->normalize_from($this->from ? array(
'email' => $this->from,
'name' => $this->fromname) :
null);
459 if ($from[
'email'] ===
'') {
460 $from = $this->normalize_from(
$config[
'from'] ?? array(
461 'email' =>
$config[
'from_email'] ??
'',
462 'name' =>
$config[
'from_name'] ??
'',
466 if ($from[
'email'] ===
'') {
467 throw new PHPMailerException(
'Absender fehlt.');
470 $mail->setFrom($from[
'email'], $from[
'name']);
472 $sender = trim((
string) (
$config[
'sender'] ??
$config[
'return_path'] ??
$config[
'envelope_from'] ??
''));
473 if ($sender !==
'') {
474 $mail->Sender = $sender;
478 private function configure_body(PHPMailer
$mail, array
$options) {
480 $text = (string) (
$options[
'text'] ?? $this->textbody);
485 $mail->AltBody = $text !==
'' ? $text : $this->html_to_text(
$html);
489 $mail->isHTML(
false);
493 private function configure_headers(PHPMailer
$mail) {
494 foreach ($this->headers as $key =>
$value) {
495 if (in_array(strtolower((
string) $key), array(
'from',
'to',
'cc',
'bcc',
'reply-to',
'content-type',
'mime-version'),
true)) {
498 $mail->addCustomHeader((
string) $key, (
string)
$value);
502 private function configure_attachments(PHPMailer
$mail) {
503 foreach ($this->attachments as $attachment) {
504 if (!is_array($attachment)) {
508 $path = (string) ($attachment[
'path'] ??
'');
509 if ($path ===
'' || !is_file($path) || !is_readable($path)) {
513 $name = (string) ($attachment[
'name'] ?? basename($path));
514 $encoding = PHPMailer::ENCODING_BASE64;
515 $type = (string) ($attachment[
'content_type'] ?? $this->getContentType($path));
516 $disp = (string) ($attachment[
'disposition'] ??
'attachment');
517 $cid = (string) ($attachment[
'cid'] ??
'');
519 if ($disp ===
'inline' && $cid !==
'') {
520 $mail->addEmbeddedImage($path, $cid, $name, $encoding, $type);
522 $mail->addAttachment($path, $name, $encoding, $type, $disp);
527 private function embed_html_images(PHPMailer
$mail,
$html) {
528 if (!$this->auto_embbed_images && !$this->auto_embed_images) {
532 preg_match_all(
'/<img[^>]+src=["\']([^"\']+)["\']/i',
$html, $matches);
533 $images = array_unique($matches[1] ?? array());
535 foreach ($images as $src) {
536 $file = $this->html_image_path($src);
541 $cid =
'part.' . md5(
$file);
542 $mail->addEmbeddedImage(
$file, $cid, basename(
$file), PHPMailer::ENCODING_BASE64, $this->getContentType(
$file));
543 $html = str_replace($src,
'cid:' . $cid,
$html);
544 $this->html_images[] =
$file;
550 private function html_image_path($src) {
551 $src = trim((
string) $src);
552 if ($src ===
'' || preg_match(
'/^(https?:|data:|cid:)/i', $src)) {
557 if (!preg_match(
'/^[A-Za-z]:[\\\\\\/]/', $path) && substr($path, 0, 1) !==
'/') {
558 $path =
dbx()->get_base_dir() . ltrim($path,
'/\\');
561 $path =
dbx()->os_path($path);
562 return is_file($path) && is_readable($path) ? $path :
'';
565 private function add_addresses(PHPMailer
$mail, $addresses,
$method) {
566 foreach ($this->normalize_addresses($addresses) as $address) {
567 if ($address[
'email'] !==
'') {
568 $mail->$method($address[
'email'], $address[
'name']);
573 private function normalize_addresses($addresses) {
574 if ($addresses ===
null || $addresses ===
'') {
578 if (is_string($addresses)) {
579 $parts = preg_split(
'/[;,]+/', $addresses);
580 return array_map(array($this,
'parse_address'), array_filter(array_map(
'trim', $parts)));
583 if (is_array($addresses)) {
584 if (isset($addresses[
'email'])) {
585 return array($this->normalize_from($addresses));
589 foreach ($addresses as $key =>
$value) {
590 if (is_string($key) && is_string(
$value) && filter_var($key, FILTER_VALIDATE_EMAIL)) {
591 $out[] = array(
'email' => $key,
'name' =>
$value);
602 private function normalize_from($from) {
603 if (is_array($from)) {
605 'email' => trim((
string) ($from[
'email'] ?? $from[
'mail'] ?? $from[0] ??
'')),
606 'name' => trim((
string) ($from[
'name'] ?? $from[1] ??
'')),
610 if (is_string($from)) {
611 return $this->parse_address($from);
614 return array(
'email' =>
'',
'name' =>
'');
617 private function parse_address(
$value) {
619 if (preg_match(
'/^(.*)<([^>]+)>$/',
$value, $m)) {
620 return array(
'email' => trim($m[2]),
'name' => trim(trim($m[1]),
'"\' '));
622 return array(
'email' =>
$value,
'name' =>
'');
625 private function format_address($email, $name =
'') {
626 $email = trim((
string) $email);
627 $name = trim((
string) $name);
628 return $name !==
'' ? $name .
' <' . $email .
'>' : $email;
631 private function html_to_text(
$html) {
632 return trim(html_entity_decode(strip_tags((
string)
$html), ENT_QUOTES | ENT_HTML5,
'UTF-8'));
635 private function sys_msg($status, $to, $subject, $message) {
636 if (!function_exists(
'dbx')) {
641 dbx()->sys_msg($status,
'mail',
'', (
string) $subject,
'to=' . $this->address_debug($to) .
' ' . (
string) $message);
642 }
catch (Throwable $e) {
647 private function address_debug($addresses) {
649 foreach ($this->normalize_addresses($addresses) as $address) {
650 if ($address[
'email'] !==
'') {
651 $list[] = $address[
'email'];
654 return implode(
',', $list);
657 private function spam_guard_reason(PHPMailer
$mail, $to,
string $subject, array
$options): string {
658 if (!$this->spam_protection || !empty(
$options[
'skip_spam_guard'])) {
662 return $this->spam_reason_for_text($this->spam_guard_text(
$mail, $to, $subject,
$options));
666 if (!$this->spam_protection) {
670 return $this->spam_content_reason($text);
673 private function spam_guard_text(PHPMailer
$mail, $to,
string $subject, array
$options): string {
680 $this->address_debug($to),
681 $this->address_debug(
$options[
'reply_to'] ?? $this->headers[
'Reply-To'] ?? array()),
684 foreach ($this->headers as $key =>
$value) {
685 $parts[] = (string) $key .
': ' . (
string)
$value;
688 return html_entity_decode(strip_tags(implode(
"\n", $parts)), ENT_QUOTES | ENT_HTML5,
'UTF-8');
691 private function spam_content_reason(
string $text): string {
696 $hardPatterns = array(
697 '/(?:https?:\/\/)?(?:www\.)?telegra\.ph\//i' =>
'telegra.ph link',
698 '/\btransaction[-\s_]*\d{2}-\d{2}-/i' =>
'transaction spam code',
699 '/\b(?:transaction|transfer|top\s*up)\b.{0,80}\b(?:get|claim|bonus|payment)\b/i' =>
'finance spam phrase',
702 foreach ($hardPatterns as $pattern => $reason) {
703 if (preg_match($pattern, $text)) {
708 if (preg_match(
'/\b(?:transaction|transfer|top\s*up|crypto|bitcoin|wallet|usdt|profit|investment)\b/i', $text)) {
711 if (preg_match(
'/(?:\$|usd|eur)\s*\d{3,}|\d{3,}\s*(?:\$|usd|eur)/i', $text)) {
714 if (preg_match(
'/(?:https?:\/\/|www\.|[a-z0-9-]+\.(?:ph|ru|top|xyz|click|link|icu|buzz|cfd|quest)\b)/i', $text)) {
717 if (preg_match(
'/\bget\s*(?:-|>|>|to|:)/i', $text)) {
720 if (preg_match(
'/[\x{1F300}-\x{1FAFF}]/u', $text)) {
724 $urlCount = preg_match_all(
'/(?:https?:\/\/|www\.|[a-z0-9-]+\.[a-z]{2,}\/)/i', $text);
725 if ($urlCount >= 2) {
729 return $score >= 5 ?
'spam score ' . $score :
'';
foreach(array('bootstrapRowColumns', 'setBootstrapColumnLayout', 'addBootstrapColumn', 'dissolveBootstrapColumns',) as $function) $keys