diff options
Diffstat (limited to 'modules-available/eventlog/inc/notificationtransport.inc.php')
-rw-r--r-- | modules-available/eventlog/inc/notificationtransport.inc.php | 279 |
1 files changed, 279 insertions, 0 deletions
diff --git a/modules-available/eventlog/inc/notificationtransport.inc.php b/modules-available/eventlog/inc/notificationtransport.inc.php new file mode 100644 index 00000000..499f6371 --- /dev/null +++ b/modules-available/eventlog/inc/notificationtransport.inc.php @@ -0,0 +1,279 @@ +<?php + +abstract class NotificationTransport +{ + + public static function getInstance(array $data) + { + switch ($data['type'] ?? '') { + case 'mail': + return new MailNotificationTransport($data); + case 'irc': + return new IrcNotificationTransport($data); + case 'http': + return new HttpNotificationTransport($data); + case 'group': + return new GroupNotificationTransport($data); + } + error_log('Invalid Notification Transport: ' . ($data['type'] ?? '(unset)')); + return null; + } + + public static function newGroup(int ...$ids): GroupNotificationTransport + { + return new GroupNotificationTransport(['group-list' => $ids]); + } + + public abstract function __construct(array $data); + + public abstract function toString(): string; + + public abstract function fire(string $subject, string $message, array $raw): bool; + + public abstract function isValid(): bool; + +} + +class MailNotificationTransport extends NotificationTransport +{ + + /** @var int */ + private $mailConfigId; + + /** @var int[] */ + private $userIds; + + /** @var string */ + private $extraMails; + + public function __construct(array $data) + { + $this->mailConfigId = (int)($data['mail-config-id'] ?? 0); + $this->userIds = array_map(function ($i) { return (int)$i; }, $data['mail-users'] ?? []); + $this->extraMails = (string)($data['mail-extra-mails'] ?? ''); + } + + public function toString(): string + { + static $mailList = null; + if ($mailList === null) { + $mailList = Database::queryIndexedList("SELECT configid, host, senderaddress, replyto + FROM mail_config"); + } + $str = 'Via: ' . ($mailList[$this->mailConfigId]['host'] ?? '<none>') + . ' as ' . ($mailList[$this->mailConfigId]['senderaddress'] ?? $mailList[$this->mailConfigId]['replyto'] ?? '<none>'); + if (!empty($this->userIds)) { + $str .= ', Users: ' . count($this->userIds); + } + if (!empty($this->extraMails)) { + $str .= ', External: ' . substr_count($this->extraMails, '@'); + } + return $str; + } + + public function fire(string $subject, string $message, array $raw): bool + { + if (!$this->isValid()) + return false; + $addrsOut = []; + if (preg_match_all('/[^@\s]+@[^@\s]+/', $this->extraMails, $out)) { + $addrsOut = $out[0]; + } + if (!empty($this->userIds)) { + $mails = Database::queryColumnArray("SELECT email + FROM user + WHERE userid IN (:users)", + ['users' => $this->userIds]); + foreach ($mails as $mail) { + if (preg_match('/^[^@\s]+@[^@\s]+$/', $mail)) { + $addrsOut[] = $mail; + } + } + } + if (empty($addrsOut)) + return false; + Mailer::queue($this->mailConfigId, $addrsOut, $subject, $message); + return true; + } + + public function isValid(): bool + { + if ($this->mailConfigId === 0) + return false; + $mailer = Mailer::instanceFromConfig($this->mailConfigId); + return $mailer !== null; + } +} + +class IrcNotificationTransport extends NotificationTransport +{ + + private $server; + + private $serverPasswort; + + private $target; + + private $nickName; + + public function __construct(array $data) + { + $this->server = $data['irc-server'] ?? ''; + $this->serverPasswort = $data['irc-server-password'] ?? ''; + $this->target = $data['irc-target'] ?? ''; + $this->nickName = $data['irc-nickname'] ?? 'BWLP-' . mt_rand(10000, 99999); + } + + public function toString(): string + { + return '(' . $this->server . '), ' . $this->nickName . ' @ ' . $this->target; + } + + public function fire(string $subject, string $message, array $raw): bool + { + if (!$this->isValid()) + return false; + return !Taskmanager::isFailed(Taskmanager::submit('IrcNotification', [ + 'serverAddress' => $this->server, + 'serverPassword' => $this->serverPasswort, + 'channel' => $this->target, + 'message' => preg_replace('/[\r\n]+\s*/', ' ', $message), + 'nickName' => $this->nickName, + ])); + } + + public function isValid(): bool + { + return !empty($this->server) && !empty($this->target); + } +} + +class HttpNotificationTransport extends NotificationTransport +{ + + /** @var string */ + private $uri; + + /** @var string */ + private $method; + + /** @var string */ + private $postField; + + /** @var string */ + private $postFormat; + + public function __construct(array $data) + { + $this->uri = $data['http-uri'] ?? ''; + $this->method = $data['http-method'] ?? 'POST'; + $this->postField = $data['http-post-field'] ?? 'message=%TEXT%&subject=%SUBJECT%'; + $this->postFormat = $data['http-post-format'] ?? 'FORM'; + } + + public function toString(): string + { + return $this->uri . ' (' . $this->method . ')'; + } + + public function fire(string $subject, string $message, array $raw): bool + { + if (!$this->isValid()) + return false; + $url = str_replace(['%TEXT%', '%SUBJECT%'], [urlencode($message), urlencode($subject)], $this->uri); + if ($this->method === 'POST') { + switch ($this->postFormat) { + case 'FORM': + $body = str_replace(['%TEXT%', '%SUBJECT%'], [urlencode($message), urlencode($subject)], $this->postField); + $ctype = 'application/x-www-form-urlencoded'; + break; + case 'JSON': + $body = str_replace(['%TEXT%', '%SUBJECT%'], [json_encode($message), + json_encode($subject)], $this->postField); + $ctype = 'application/json'; + break; + default: + $out = []; + foreach ($raw as $k1 => $a) { + foreach ($a as $k2 => $v) { + $out["$k1.$k2"] = $v; + } + } + $body = json_encode($out); + $ctype = 'application/json'; + } + } else { + $body = null; + $ctype = null; + } + return !Taskmanager::isFailed(Taskmanager::submit('HttpRequest', [ + 'url' => $url, + 'postData' => $body, + 'contentType' => $ctype, + ])); + } + + public function isValid(): bool + { + return !empty($this->uri); + } +} + +class GroupNotificationTransport extends NotificationTransport +{ + + /** @var int[] list of contained notification transports */ + private $list; + + public function __construct(array $data) + { + $this->list = array_map(function ($i) { return (int)$i; }, $data['group-list'] ?? []); + } + + public function toString(): string + { + static $groupList = null; + if ($groupList === null) { + $groupList = Database::queryKeyValueList("SELECT transportid, title FROM notification_backend"); + } + $out = array_map(function ($i) use ($groupList) { return $groupList[$i] ?? "#$i"; }, $this->list); + return implode(', ', $out); + } + + public function fire(string $subject, string $message, array $raw): bool + { + // This is static, so recursing into groups will keep track of ones we already saw + static $done = false; + $first = ($done === false); + if ($first) { // Non-recursive call, init list + $done = []; + } + $list = array_diff($this->list, $done); + if (!empty($list)) { + $done = array_merge($done, $list); + $res = Database::simpleQuery("SELECT data FROM notification_backend WHERE transportid IN (:ids)", + ['ids' => $list]); + foreach ($res as $row) { + $data = json_decode($row['data'], true); + if (is_array($data)) { + $inst = NotificationTransport::getInstance($data); + if ($inst !== null) { + $inst->fire($subject, $message, $raw); + } + } + } + } + if ($first) { + $done = false; // Outer-most call, reset + } + return true; + } + + public function isValid(): bool + { + // Do we really care about empty groups? They might be pointless, but not really invalid + // We could consider groups containing invalid IDs as invalid, but that would mean that we + // potentially ignore all the other existing IDs in this group, as it would never fire + return true; + } +}
\ No newline at end of file |