summaryrefslogblamecommitdiffstats
path: root/modules-available/eventlog/inc/notificationtransport.inc.php
blob: 499f6371ec22da031201c1e1cf72ea403c8eeef2 (plain) (tree)






















































































































































































































































































                                                                                                                                              
<?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;
	}
}