db = $database; } /** * Create a notification * * @param int $userId * @param string $type * @param string $title * @param string|null $message * @param bool $priority Play sound and make notification sticky * @param array $options * @param array $tags * * @throws InvalidArgumentException|RuntimeException * * @return int|false Created Notification-ID */ public function create( $userId, $type, $title, $message = null, $priority = false, $options = [], $tags = [] ) { if (!in_array($type, self::$validMessageTypes, true)) { throw new InvalidArgumentException(sprintf( '"%s" is not a valid message type. Valid types are: %s', $type, implode(', ', self::$validMessageTypes) )); } if (!$this->isValidUser($userId)) { throw new InvalidArgumentException(sprintf( 'User #%s is not a valid user.', $userId )); } // Truncate long messages $message = $this->truncateMessage($message); // Sanitize buttons if (is_array($options['buttons']) && count($options['buttons']) > 0) { foreach ($options['buttons'] as $index => $button) { if (empty($button['text']) || empty($button['link'])) { unset($options['buttons'][$index]); } if (empty($button['id'])) { $options['buttons'][$index]['id'] = uniqid('notification-button-', false); // Set Html-Id attribute } } } // Create notification $this->db->perform( 'INSERT INTO `notification_message` (`user_id`, `type`, `title`, `message`, `options_json`, `tags`, `priority`, `created_at`) VALUES (:user_id, :type, :title, :message, :options_json, :tags, :priority, NOW())', [ 'user_id' => (int)$userId, 'type' => $type, 'title' => $title, 'message' => $message, 'priority' => (int)$priority, 'options_json' => !empty($options) ? json_encode($options) : null, 'tags' => !empty($tags) ? $this->transformTagsArrayToString($tags) : null, ] ); $insertId = (int)$this->db->lastInsertId(); if ($insertId === 0) { throw new RuntimeException('Notification message could not be created.'); } return $insertId; } /** * Create push notification * * @param int $userId * @param string $title * @param string $message * @param bool $priority * * @return int Created ID */ public function createPushNotification($userId, $title, $message, $priority = false) { // strip_tags ist notwendig, da HTML von Browser-Benachrichtigungen nicht unterstützt wird. return $this->create($userId, self::TYPE_PUSH, strip_tags($title), strip_tags($message), $priority); } /** * @param int $userId * @param NotificationMessageData $data * * @return int|false Created ID */ public function createFromData($userId, NotificationMessageData $data) { return $this->create( $userId, $data->getType(), $data->getTitle(), $data->getMessage(), $data->isPriority(), $data->getOptions(), $data->getTags() ); } /** * @param int $notificationId * @param array $tags * * @return bool Returns true on success */ public function addTags($notificationId, array $tags) { try { $tagsArray = []; // Fetch existing tags $tagsExisting = $this->db->fetchValue( 'SELECT n.tags FROM notification_message AS n WHERE n.id = :id', ['id' => (int)$notificationId] ); if (!empty($tagsExisting)) { $tagsArray = $this->transformTagsStringToArray($tagsExisting); } // Update notification $tagsMerged = array_merge($tagsArray, $tags); $tagsString = $this->transformTagsArrayToString($tagsMerged); $this->db->perform( 'UPDATE notification_message SET tags = :tags WHERE id = :id', ['id' => (int)$notificationId, 'tags' => $tagsString] ); } catch (\Exception $e) { return false; } return true; } /** * @param int $notificationId * * @return bool */ public function delete($notificationId) { $numRows = (int)$this->db->fetchAffected( 'DELETE FROM notification_message WHERE id = :id LIMIT 1', ['id' => (int)$notificationId] ); return $numRows === 1; } /** * Delete notification messages by UserID * * @param int $userId * @param bool $keepPriorityMessages If true, high priority messages will not be deleted * * @return int Number of deleted messages */ public function deleteByUser($userId, $keepPriorityMessages = true) { $delete = $this->db->delete() ->from('notification_message') ->where('user_id = ?', (int)$userId); if ((bool)$keepPriorityMessages === true) { $delete->where('priority <> ?', 1); } $numRows = (int)$this->db->fetchAffected( $delete->getStatement(), $delete->getBindValues() ); return $numRows; } /** * Delete notification messages by tags * * If multiple tags submitted, all tags must occur in the same message. * * @example deleteByTags(['callcenter','incomingcall']) * * @param array $tags * @param int $userId * @param bool $keepPriorityMessages If true, high priority messages will not be deleted * * @return int Number of deleted messages */ public function deleteByTags(array $tags, $userId = null, $keepPriorityMessages = true) { $delete = $this->db->delete()->from('notification_message'); foreach ($tags as $tag) { $tag = $this->normalizeTag($tag); $delete->where('tags LIKE ?', "%|{$tag}|%"); } if ($userId !== null) { $delete->where('user_id = ?', (int)$userId); } if ((bool)$keepPriorityMessages === true) { $delete->where('priority <> ?', 1); } $numRows = (int)$this->db->fetchAffected( $delete->getStatement(), $delete->getBindValues() ); return $numRows; } /** * @return array */ public function getValidTypes() { return self::$validMessageTypes; } /** * @param int $userId * * @return bool */ private function isValidUser($userId) { // @todo @refactor Move to UserGateway $userCheck = (int)$this->db->fetchValue( 'SELECT u.id FROM `user` AS u WHERE u.id = :user_id AND u.activ = 1', ['user_id' => (int)$userId] ); return $userCheck === (int)$userId; } /** * @param array $tags * * @return string */ private function transformTagsArrayToString(array $tags) { if (empty($tags)) { return ''; } sort($tags); $tags = array_unique($tags); $string = '|'; foreach ($tags as $tag) { $tag = $this->normalizeTag($tag); $string .= $tag . '|'; } return $string; } /** * @param string $string * * @return array */ private function transformTagsStringToArray($string) { $tags = []; $parts = explode('|', $string); foreach ($parts as $tag) { $tag = $this->normalizeTag($tag); if (!empty($tag)) { $tags[] = $tag; } } sort($tags); $tags = array_unique($tags); return $tags; } /** * @param string $tag * * @return string */ private function normalizeTag($tag) { $tag = strtolower(trim($tag)); $tag = preg_replace('/[^a-z0-9\-]/', '', $tag); // Remove invalid chars $tag = preg_replace('/[-]+/', '-', $tag); // Replace multiple dashes return (string)$tag; } /** * @param null|string $message * * @return null|string */ private function truncateMessage($message = null) { if ($message !== null && mb_strlen($message) > 1024) { $message = mb_substr($message, 0, 1020) . ' ...'; } return $message; } /** * @param string $type * @param string $title * @param null|string $message * @param bool $priority * @param array $options * @param array $tags * * @return int */ public function createPushNotificationForConnectedUsers( $type, $title, $message = null, $priority = false, $options = [], $tags = [] ) { if (!in_array($type, self::$validMessageTypes, true)) { throw new InvalidArgumentException(sprintf( '"%s" is not a valid message type. Valid types are: %s', $type, implode(', ', self::$validMessageTypes) )); } $sql = "INSERT INTO notification_message (user_id, type, title, message, tags, options_json, priority, created_at) SELECT u.id, :type, :title, :msg,:tags,'',:priority,NOW() FROM `user` AS u INNER JOIN useronline uo on u.id = uo.user_id AND uo.login = 1"; return (int)$this->db->fetchAffected($sql, [ 'title' => strip_tags($title), 'msg' => strip_tags($message), 'type' => $type, 'priority' => (int)$priority, 'options_json' => !empty($options) ? json_encode($options) : null, 'tags' => !empty($tags) ? $this->transformTagsArrayToString($tags) : null, ]); } }