Skip to content

Commit

Permalink
feat: mass messaging
Browse files Browse the repository at this point in the history
  • Loading branch information
samerton committed Mar 9, 2024
1 parent 28f0a0f commit 970cf6d
Show file tree
Hide file tree
Showing 12 changed files with 275 additions and 127 deletions.
28 changes: 26 additions & 2 deletions core/classes/Core/Alert.php
Expand Up @@ -12,14 +12,16 @@ class Alert
/**
* Creates an alert for the specified user.
*
* @deprecated Use Alert::send instead
*
* @param int $user_id Contains the ID of the user who we are creating the alert for.
* @param string $type Contains the alert type, eg 'tag' for user tagging.
* @param array $text_short Contains the alert text in short form for the dropdown.
* @param array $text Contains full information about the alert.
* @param string $link Contains link to view the alert, defaults to #.
* @param ?string $link Contains link to view the alert, defaults to #.
* @param ?string $content Optional alert content.
*/
public static function create(int $user_id, string $type, array $text_short, array $text, string $link = '#', string $content = null): void
public static function create(int $user_id, string $type, array $text_short, array $text, ?string $link = '#', string $content = null): void
{
$db = DB::getInstance();

Expand All @@ -45,6 +47,28 @@ public static function create(int $user_id, string $type, array $text_short, arr
]);
}

/**
* Post a new alert to a user
*
* @param int $userId
* @param string $title
* @param string $content
* @param string|null $link Optional link to redirect the user to on click
* @return void
*/
public static function send(int $userId, string $title, string $content, ?string $link = '')
{
DB::getInstance()->insert('alerts', [
'user_id' => $userId,
'type' => 'alert',
'url' => $link ?? '',
'content_short' => $title, // Column maintained for legacy reasons
'content' => $title,
'content_rich' => $content,
'created' => date('U'),
]);
}

/**
* Get user alerts.
*
Expand Down
2 changes: 1 addition & 1 deletion core/classes/Queue/Task.php
Expand Up @@ -157,7 +157,7 @@ public function fromId(int $id): ?Task
$this->_scheduledFor = $task->scheduled_for;
$this->_status = $task->status;
$this->_task = $task->task;
$this->_userId = $task->userId;
$this->_userId = $task->user_id;

return $this;
}
Expand Down
47 changes: 47 additions & 0 deletions custom/templates/DefaultRevamp/user/alert.tpl
@@ -0,0 +1,47 @@
{include file='header.tpl'}
{include file='navbar.tpl'}

<h2 class="ui header">
{$TITLE}
</h2>

<div class="ui stackable grid" id="alerts">
<div class="ui centered row">
<div class="ui six wide tablet four wide computer column">
{include file='user/navigation.tpl'}
</div>
<div class="ui ten wide tablet twelve wide computer column">
<div class="ui segment">
<h3 class="ui header">
{$ALERT_TITLE}
{if !$ALERT_READ}
<span class="ui green label">
{$NEW}
</span>
{/if}
<div class="res right floated">
{if isset($VIEW)}
<a href="{$VIEW_LINK}" class="ui mini button">{$VIEW}</a>
{/if}
<a href="{$BACK_LINK}" class="ui mini blue button">{$BACK}</a>
<form action="{$DELETE_LINK}" method="post" style="display:inline">
<input type="hidden" name="token" value="{$TOKEN}">
<button type="submit" class="ui mini negative button">{$DELETE}</button>
</form>
</div>
</h3>
{if isset($ERROR)}
<div class="ui negative message">{$ERROR}</div>
{/if}

<div class="ui divider"></div>

<div class="forum_post">
{$ALERT_CONTENT}
</div>
</div>
</div>
</div>
</div>

{include file='footer.tpl'}
16 changes: 6 additions & 10 deletions custom/templates/DefaultRevamp/user/alerts.tpl
Expand Up @@ -29,24 +29,20 @@
<div class="ui middle aligned relaxed selection list">
{nocache}
{if count($ALERTS_LIST)}
{foreach from=$ALERTS_LIST key=name item=alert}
<a class="item" href="{$alert->view_link}" data-toggle="popup">
{foreach from=$ALERTS_LIST item=alert}
<a class="item" href="{$alert.view_link}">
<i class="angle right icon"></i>
<div class="content">
<div class="description">
{if $alert->read eq 0}
<strong>{$alert->content}</strong>
{if $alert.read eq 0}
<strong>{$alert.title}</strong>
{else}
{$alert->content}
{$alert.title}
{/if}
<br />{$alert->date_nice}
<br />{$alert.date_nice}
</div>
</div>
</a>
<div class="ui wide popup">
<h4>{$alert->content}</h4>
{$alert->date}
</div>
{/foreach}
{else}
<div class="ui info message">
Expand Down
27 changes: 25 additions & 2 deletions custom/templates/DefaultRevamp/user/notification_settings.tpl
Expand Up @@ -14,6 +14,29 @@
<div class="ui segment">
<h3 class="ui header">{$NOTIFICATION_SETTINGS_TITLE}</h3>

{if isset($SUCCESS)}
<div class="ui success icon message">
<i class="check icon"></i>
<div class="content">
<div class="header">{$SUCCESS_TITLE}</div>
{$SUCCESS}
</div>
</div>
{/if}

{if isset($ERRORS)}
<div class="ui error icon message">
<i class="x icon"></i>
<div class="content">
<ul class="list">
{foreach from=$ERRORS item=error}
<li>{$error}</li>
{/foreach}
</ul>
</div>
</div>
{/if}

<form action="" method="post">
<table class="ui definition celled table">
<thead>
Expand All @@ -29,13 +52,13 @@
<td>{$setting.value}</td>
<td>
<div class="ui toggle checkbox">
<input type="checkbox" name="{$setting.type}:alert">
<input type="checkbox" name="{$setting.type}:alert"{if $setting.alert} checked{/if}>
<label class="screenreader-only">{$ALERT}</label>
</div>
</td>
<td>
<div class="ui toggle checkbox">
<input type="checkbox" name="{$setting.type}:email">
<input type="checkbox" name="{$setting.type}:email"{if $setting.email} checked{/if}>
<label class="screenreader-only">{$EMAIL}</label>
</div>
</td>
Expand Down
22 changes: 11 additions & 11 deletions modules/Core/classes/Core/Notification.php
Expand Up @@ -25,8 +25,9 @@ class Notification {
* @param string $title Title of notification
* @param string $content Notification content
* @param int|int[] $recipients Notification recipient or recipients - array of user IDs
* @param ?int $authorId User ID that sent the notification
* @param int $authorId User ID that sent the notification
* @param ?callable $contentCallback Optional callback to perform for each recipient's content
* @param bool $skipPurify Whether to skip content purifying, default false
*
* @throws NotificationTypeNotFoundException
*/
Expand All @@ -35,8 +36,9 @@ public function __construct(
string $title,
string $content,
$recipients,
int $authorId = null,
callable $contentCallback = null
int $authorId,
callable $contentCallback = null,
bool $skipPurify = false
) {
if (!in_array($type, array_column(self::getTypes(), 'key'))) {
throw new NotificationTypeNotFoundException("Type $type not registered");
Expand All @@ -50,8 +52,8 @@ public function __construct(
$recipients = [$recipients];
}

$this->_recipients = array_map(static function ($recipient) use ($content, $contentCallback) {
$newContent = $contentCallback($recipient, $content);
$this->_recipients = array_map(static function ($recipient) use ($content, $contentCallback, $skipPurify, $title) {
$newContent = $contentCallback($recipient, $title, $content, $skipPurify);
return ['id' => $recipient, 'content' => $newContent];
}, $recipients);
}
Expand Down Expand Up @@ -80,21 +82,19 @@ public function send(): void {
}
}

public function sendAlert(int $userId, string $content): void {
$text = ['content' => $this->_title];

Alert::create($userId, $this->_type, $text, $text, null, $content);
private function sendAlert(int $userId, string $content): void {
Alert::send($userId, $this->_title, $content);
}

public function sendEmail(int $userId, string $content): void {
private function sendEmail(int $userId, string $content): void {
$task = (new SendEmail())->fromNew(
Module::getIdFromName('Core'),
'Send Email Notification',
[
'content' => $content,
'title' => $this->_title,
],
Date::next()->getTimestamp(), // TODO: schedule a date/time?
date('U'), // TODO: schedule a date/time?
'User',
$userId,
false,
Expand Down
16 changes: 7 additions & 9 deletions modules/Core/classes/Tasks/MassMessage.php
Expand Up @@ -19,7 +19,7 @@ public function run(): string {
if (!empty($this->getData()['users'])) {
$whereIn = implode(',', array_map(static fn ($u) => '?', $this->getData()['users']));
$where = "WHERE id IN ($whereIn)";
$whereVars = $this->getData()['users'];
$whereVars = array_map(static fn ($u) => $u['id'], $this->getData()['users']);
}

$recipients = DB::getInstance()->query(
Expand All @@ -36,24 +36,22 @@ public function run(): string {
$this->getData()['type'],
$this->getData()['title'],
$this->getData()['content'],
$recipients->results(),
array_map(static fn ($r) => $r->id, $recipients->results()),
$this->getUserId(),
$this->getData()['callback']
$this->getData()['callback'],
$this->getData()['skip_purify'] ?? false
);
$notification->send();

$this->setOutput(['start' => $start, 'end' => $end, 'next_status' => $nextStatus]);
$this->setOutput(['userIds' => $whereVars, 'start' => $start, 'end' => $end, 'next_status' => $nextStatus]);
$this->setFragmentNext($end);

return $nextStatus;
}

public static function parseContent(int $userId, string $content): string {
public static function parseContent(int $userId, string $title, string $content, bool $skipPurify = false): string {
$user = new User($userId);
$event = EventHandler::executeEvent(GenerateNotificationContentEvent::class, [
'content' => $content,
'user' => $user,
]);
$event = EventHandler::executeEvent(new GenerateNotificationContentEvent($content, $title, $user, $skipPurify));

return $event['content'];
}
Expand Down
3 changes: 3 additions & 0 deletions modules/Core/language/en_UK.json
Expand Up @@ -859,6 +859,7 @@
"general/log_out_click": "Click here to log out",
"general/log_out_complete": "Logout successful. Click {{linkStart}}here{{linkEnd}} to continue.",
"general/more": "More",
"general/new": "New",
"general/next": "Next",
"general/no": "No",
"general/no_default_server": "There is no default server, please select one in the StaffCP -> Integrations -> Minecraft tab.",
Expand Down Expand Up @@ -1136,6 +1137,7 @@
"user/active_template": "Active Template",
"user/agree_t_and_c": "I have read and accept the {{linkStart}}Terms and Conditions{{linkEnd}}.",
"user/alerts": "Alerts",
"user/alerts_follow_link": "Follow alert link",
"user/authme_account_linked": "Account linked successfully.",
"user/authme_account_not_found": "That AuthMe account could not be found.",
"user/authme_email_help_1": "Finally, please enter the following details.",
Expand Down Expand Up @@ -1254,6 +1256,7 @@
"user/no_wall_posts": "There are no wall posts here yet.",
"user/not_connected": "Not Connected",
"user/notification_settings": "Notification Settings",
"user/notification_settings_updated_successfully": "Notification settings updated successfully.",
"user/overview": "Overview",
"user/oauth_already_linked": "Another NamelessMC user is already linked to that {{provider}} account.",
"user/oauth_link_confirm": "You will be taken to the {{provider}} website to link your account.",
Expand Down
1 change: 0 additions & 1 deletion modules/Core/module.php
Expand Up @@ -514,7 +514,6 @@ public function __construct(Language $language, Pages $pages, User $user, Naviga

EventHandler::registerListener(GroupClonedEvent::class, CloneGroupHook::class);

EventHandler::registerListener(GenerateNotificationContentEvent::class, GenerateNotificationContentEvent::class);
EventHandler::registerListener(GenerateNotificationContentEvent::class, 'ContentHook::purify');
EventHandler::registerListener(GenerateNotificationContentEvent::class, 'ContentHook::renderEmojis', 10);
EventHandler::registerListener(GenerateNotificationContentEvent::class, 'MentionsHook::parsePost', 5);
Expand Down

0 comments on commit 970cf6d

Please sign in to comment.