Add the ability to subscribe to forums (as well as threads).

E.g. you could subscribe to the BOINC News forum and get it view email
This commit is contained in:
David Anderson 2024-07-31 00:49:03 -07:00
parent f0fed141d0
commit ca491ce55f
10 changed files with 255 additions and 139 deletions

View File

@ -545,11 +545,12 @@ create table post (
primary key (id)
) engine=InnoDB;
-- subscription to a thread
-- subscription to a thread or forum
--
create table subscriptions (
userid integer not null,
threadid integer not null,
-- or negative of forum ID (kludge)
notified_time integer not null default 0
-- deprecated
) engine=InnoDB;
@ -723,8 +724,9 @@ create table notify (
-- destination of notification
create_time integer not null,
type integer not null,
-- see html/inc/forum_db.inc
opaque integer not null
-- some other ID, e.g. that of the thread, user or PM record
-- the ID of the thread, user or PM record
);
create table badge (

View File

@ -145,7 +145,15 @@ function show_forum_header($user) {
<input type="hidden" name="search_forum" value="-1">
<input type="hidden" name="search_sort" value="'.CREATE_TIME_NEW.'">
<input type="text" class="" name="search_keywords">
<input class="btn btn-primary btn-sm" title="'.tra("Search for words in forum messages").'" type="submit" value="'.tra("Search forums").'"><br>
';
echo sprintf(
'<input class="%s" %s title="%s" type="submit" value="%s"><br>',
'btn btn-sm',
button_style(),
tra("Search for words in forum messages"),
tra("Search forums")
);
echo '
<small><a href="forum_search.php">'.tra("Advanced search").'</a></small>
</td>
';
@ -863,25 +871,53 @@ function post_warning($forum=null) {
return $x;
}
function notify_subscriber($thread, $user) {
function notify_thread_subscriber($thread, $user) {
BoincForumPrefs::lookup($user);
if ($user->prefs->pm_notification == 1) {
send_reply_notification_email($thread, $user);
send_thread_notification_email($thread, $user);
}
$now = time();
$type = NOTIFY_SUBSCRIBED_POST;
BoincNotify::replace("userid=$user->id, create_time=$now, type=$type, opaque=$thread->id");
$type = NOTIFY_SUBSCRIBED_THREAD;
BoincNotify::replace(
"userid=$user->id, create_time=$now, type=$type, opaque=$thread->id"
);
}
// notify subscribed users, except for the given user
//
function notify_subscribers($thread, $user) {
function notify_thread_subscribers($thread, $user) {
$subs = BoincSubscription::enum("threadid=$thread->id");
foreach ($subs as $sub) {
if ($user && ($user->id == $sub->userid)) continue;
$user2 = BoincUser::lookup_id($sub->userid);
if ($user2) {
notify_subscriber($thread, $user2);
notify_thread_subscriber($thread, $user2);
}
}
}
function notify_forum_subscriber($forum, $user) {
BoincForumPrefs::lookup($user);
if ($user->prefs->pm_notification == 1) {
send_forum_notification_email($forum, $user);
}
$now = time();
$type = NOTIFY_SUBSCRIBED_FORUM;
BoincNotify::replace(
"userid=$user->id, create_time=$now, type=$type, opaque=$forum->id"
);
}
// notify subscribed users, except for the given user
//
function notify_forum_subscribers($forum, $user) {
$id = -$forum->id;
$subs = BoincSubscription::enum("threadid=$id");
foreach ($subs as $sub) {
if ($user && ($user->id == $sub->userid)) continue;
$user2 = BoincUser::lookup_id($sub->userid);
if ($user2) {
notify_forum_subscriber($forum, $user2);
}
}
}
@ -909,7 +945,7 @@ function create_post($content, $parent_id, $user, $forum, $thread, $signature) {
return null;
}
notify_subscribers($thread, $user);
notify_thread_subscribers($thread, $user);
$user->prefs->update("posts=posts+1");
$thread->update("replies=replies+1, timestamp=$now");
@ -961,6 +997,8 @@ function create_thread($title, $content, $user, $forum, $signature, $export) {
$thread = BoincThread::lookup_id($id);
create_post($content, 0, $user, $forum, $thread, $signature);
$forum->update("threads=threads+1");
notify_forum_subscribers($forum, $user);
exit;
return $thread;
}
@ -1043,7 +1081,7 @@ function move_post($post, $old_thread, $old_forum, $new_thread, $new_forum) {
update_thread_timestamp($new_thread);
update_forum_timestamp($old_forum);
update_forum_timestamp($new_forum);
notify_subscribers($new_thread, $g_logged_in_user);
notify_thread_subscribers($new_thread, $g_logged_in_user);
return true;
}
@ -1350,11 +1388,11 @@ function show_thread_and_context($thread, $user) {
';
}
// see if thread is in subscription list
// see if ID is in subscription list
//
function is_subscribed($thread, $subs) {
function is_subscribed($id, $subs) {
foreach ($subs as $sub) {
if ($sub->threadid == $thread->id) return true;
if ($sub->threadid == $id) return true;
}
return false;
}
@ -1369,21 +1407,40 @@ function is_forum_visible_to_user($forum, $user) {
return true;
}
function subscribed_post_email_line($notify) {
function subscribed_thread_email_line($notify) {
$thread = BoincThread::lookup_id($notify->opaque);
return "There are new posts in the thread '$thread->title'";
}
function subscribed_post_web_line($notify) {
function subscribed_thread_web_line($notify) {
$thread = BoincThread::lookup_id($notify->opaque);
return tra("New posts in the thread %1","<a href=forum_thread.php?id=$thread->id>$thread->title</a>");
}
function subscribe_rss($notify, &$title, &$msg, &$url) {
function subscribed_thread_rss($notify) {
$thread = BoincThread::lookup_id($notify->opaque);
$title = tra("New posts in subscribed thread");
$msg = tra("There are new posts in the thread '%1'",$thread->title);
$url = secure_url_base()."forum_thread.php?id=$thread->id";
return [$title, $msg, $url];
}
function subscribed_forum_email_line($notify) {
$forum = BoincForum::lookup_id($notify->opaque);
return "There are new threads in the forum '$forum->title'";
}
function subscribed_forum_web_line($notify) {
$forum = BoincForum::lookup_id($notify->opaque);
return tra("New threads in the forum %1","<a href=forum_forum.php?id=$forum->id>$forum->title</a>");
}
function subscribed_forum_rss($notify) {
$forum = BoincForum::lookup_id($notify->opaque);
$title = tra("New posts in subscribed forum");
$msg = tra("There are new threads in the forum '%1'",$forum->title);
$url = secure_url_base()."forum_forum.php?id=$forum->id";
return [$title, $msg, $url];
}
function show_mark_as_read_button($user) {
@ -1399,14 +1456,14 @@ function show_mark_as_read_button($user) {
}
function remove_subscriptions_forum($userid, $forumid) {
$subs = BoincSubscription::enum("userid=$userid");
$subs = BoincSubscription::enum("userid=$userid and threadid>0");
foreach ($subs as $sub) {
$thread = BoincThread::lookup_id($sub->threadid);
if ($thread && $thread->forum == $forumid) {
BoincSubscription::delete($userid, $thread->id);
}
}
$notices = BoincNotify::enum("userid=$userid and type=".NOTIFY_SUBSCRIBED_POST);
$notices = BoincNotify::enum("userid=$userid and type=".NOTIFY_SUBSCRIBED_THREAD);
foreach ($notices as $n) {
$thread = BoincThread::lookup_id($n->opaque);
if ($thread && $thread->forum == $forumid) {
@ -1417,7 +1474,7 @@ function remove_subscriptions_forum($userid, $forumid) {
function remove_subscriptions_thread($userid, $threadid) {
BoincSubscription::delete($userid, $threadid);
BoincNotify::delete_aux("userid=$userid and type=".NOTIFY_SUBSCRIBED_POST." and opaque=$threadid");
BoincNotify::delete_aux("userid=$userid and type=".NOTIFY_SUBSCRIBED_THREAD." and opaque=$threadid");
}
function parse_forum_cookie() {

View File

@ -329,6 +329,7 @@ class BoincNotify {
define ('NOTIFY_FRIEND_REQ', 1);
define ('NOTIFY_FRIEND_ACCEPT', 2);
define ('NOTIFY_PM', 3);
define ('NOTIFY_SUBSCRIBED_POST', 4);
define ('NOTIFY_SUBSCRIBED_THREAD', 4);
define ('NOTIFY_SUBSCRIBED_FORUM', 5);
?>

View File

@ -123,13 +123,13 @@ For assistance with ".PROJECT." go to ".$master_url;
return $success;
}
// If a user is subscribed to a thread that is replied to,
// send them an email notifying them of the reply.
// There's a new post in the thread, which the user is subscribed to.
// send them an email notifying them.
//
function send_reply_notification_email($thread, $user){
$title = PROJECT . ": A user has posted to '". $thread->title ."'";
function send_thread_notification_email($thread, $user){
$title = PROJECT . ": there is a new post in '". $thread->title ."'";
$link = secure_url_base() . "forum_thread.php?id=" . $thread->id;
$body = "Another " . PROJECT . " user has posted to the thread
$body = "A " . PROJECT . " user has posted to the thread
\"" . $thread->title . "\".\n"
."To view the updated thread, visit:\n$link
@ -141,6 +141,24 @@ Do not reply to this message.
return send_email($user, $title, $body);
}
// There's a new thread in the forum, which the user is subscribed to.
// send them an email notifying them.
//
function send_forum_notification_email($forum, $user){
$title = PROJECT . ": there is a new thread in '". $forum->title ."'";
$link = secure_url_base() . "forum_forum.php?id=" . $forum->id;
$body = "A " . PROJECT . " user has added a thread to the forum
\"" . $thread->title . "\".\n"
."To view the updated forum, visit:\n$link
--------------------------
To change email preferences, visit:
".secure_url_base()."edit_forum_preferences_form.php
Do not reply to this message.
";
return send_email($user, $title, $body);
}
//////////////////// a user clicks the red "x" to report a post ///////////
//
function send_report_post_email($user, $forum, $thread, $post, $message) {

View File

@ -1,7 +1,7 @@
<?php
// This file is part of BOINC.
// http://boinc.berkeley.edu
// Copyright (C) 2008 University of California
// https://boinc.berkeley.edu
// Copyright (C) 2024 University of California
//
// BOINC is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License
@ -16,6 +16,8 @@
// You should have received a copy of the GNU Lesser General Public License
// along with BOINC. If not, see <http://www.gnu.org/licenses/>.
// notifications as an RSS feed
require_once("../project/project.inc");
function notify_rss_auth($user) {
@ -37,8 +39,8 @@ function show_notify_rss_item($notify) {
case NOTIFY_PM:
pm_rss($notify, $title, $msg, $url);
break;
case NOTIFY_SUBSCRIBED_POST:
subscribe_rss($notify, $title, $msg, $url);
case NOTIFY_SUBSCRIBED_THREAD:
[$title, $msg, $url] = subscribe_rss($notify);
break;
}
if (!$msg) {

View File

@ -207,8 +207,10 @@ function notify_description($notify) {
return friend_notify_accept_web_line($notify);
case NOTIFY_PM:
return pm_web_line($notify);
case NOTIFY_SUBSCRIBED_POST:
return subscribed_post_web_line($notify);
case NOTIFY_SUBSCRIBED_THREAD:
return subscribed_thread_web_line($notify);
case NOTIFY_SUBSCRIBED_FORUM:
return subscribed_forum_web_line($notify);
}
return null;
}

View File

@ -958,7 +958,7 @@ function show_button($url, $text, $desc=null, $class=null, $extra=null) {
// for places with a bunch of buttons, like forum posts
//
function show_button_small($url, $text, $desc=null) {
echo button_text($url, $text, $desc, "btn-primary btn-xs");
echo button_text($url, $text, $desc, "btn btn-xs", button_style());
}
// used for showing icons

View File

@ -87,8 +87,8 @@ function send_notify_emails() {
case NOTIFY_PM:
$x = pm_email_line($notify);
break;
case NOTIFY_SUBSCRIBED_POST:
$x = subscribed_post_email_line($notify);
case NOTIFY_SUBSCRIBED_THREAD:
$x = subscribed_thread_email_line($notify);
break;
}
if ($x) {

View File

@ -16,123 +16,143 @@
// You should have received a copy of the GNU Lesser General Public License
// along with BOINC. If not, see <http://www.gnu.org/licenses/>.
// display the threads in a forum.
// forum page: display 'New thread' button and list of threads
require_once('../inc/util.inc');
require_once('../inc/time.inc');
require_once('../inc/forum.inc');
require_once('../inc/pm.inc');
check_get_args(array("id", "sort", "start"));
$id = get_int("id");
$sort_style = get_int("sort", true);
$start = get_int("start", true);
if (!$start) $start = 0;
$forum = BoincForum::lookup_id($id);
if (!$forum) error_page("forum ID not found");
$user = get_logged_in_user(false);
BoincForumPrefs::lookup($user);
if (DISABLE_FORUMS && !is_admin($user)) {
error_page("Forums are disabled");
}
if (!is_forum_visible_to_user($forum, $user)) {
function forum_page($forum, $user, $msg=null) {
global $forum_sort_styles;
$sort_style = get_int("sort", true);
$start = get_int("start", true);
if (!$start) $start = 0;
$subs = null;
if ($user) {
remove_subscriptions_forum($user->id, $id);
if (DISABLE_FORUMS && !is_admin($user)) {
error_page("Forums are disabled");
}
BoincForumPrefs::lookup($user);
$subs = BoincSubscription::enum("userid=$user->id");
}
error_page(tra("Not visible to you"));
}
if (!$sort_style) {
// get the sort style either from the logged in user or a cookie
if ($user){
$sort_style = $user->prefs->forum_sorting;
} else {
list($sort_style, $thread_style) = parse_forum_cookie();
if (!is_forum_visible_to_user($forum, $user)) {
if ($user) {
remove_subscriptions_forum($user->id, $forum->id);
}
error_page(tra("Not visible to you"));
}
} else {
// set the sort style
if ($user){
$user->prefs->forum_sorting = $sort_style;
$user->prefs->update("forum_sorting=$sort_style");
if (!$sort_style) {
// get the sort style either from the logged in user or a cookie
if ($user){
$sort_style = $user->prefs->forum_sorting;
} else {
list($sort_style, $thread_style) = parse_forum_cookie();
}
} else {
list($old_style, $thread_style) = parse_forum_cookie();
send_cookie(
'sorting', implode("|", array($sort_style, $thread_style)), true
// set the sort style
if ($user){
$user->prefs->forum_sorting = $sort_style;
$user->prefs->update("forum_sorting=$sort_style");
} else {
list($old_style, $thread_style) = parse_forum_cookie();
send_cookie(
'sorting', implode("|", array($sort_style, $thread_style)), true
);
}
}
switch ($forum->parent_type) {
case 0:
$category = BoincCategory::lookup_id($forum->category);
if ($category->is_helpdesk) {
page_head(tra("Questions and Answers").' : '.$forum->title);
} else {
page_head(tra("Message boards").' : '.$forum->title);
}
if ($msg) echo "<p>$msg</p>\n";
show_forum_header($user);
show_forum_title($category, $forum, NULL);
break;
case 1:
$team = BoincTeam::lookup_id($forum->category);
page_head(tra("Team message board for %1", $team->name));
if ($msg) echo "<p>$msg</p>\n";
show_forum_header($user);
show_team_forum_title($forum);
break;
}
echo '
<p>
<form action="forum_forum.php" method="get" class="form-inline">
<input type="hidden" name="id" value="'.$forum->id.'">
<table width="100%" cellspacing="0" cellpadding="0">
<tr valign="top">
<td colspan=2>
';
if (user_can_create_thread($user, $forum)) {
show_button(
"forum_post.php?id=$forum->id",
tra("New thread"),
tra("Add a new thread to this forum")
);
}
}
switch ($forum->parent_type) {
case 0:
$category = BoincCategory::lookup_id($forum->category);
if ($category->is_helpdesk) {
page_head(tra("Questions and Answers").' : '.$forum->title);
if (is_subscribed(-$forum->id, $subs)) {
BoincNotify::delete_aux(sprintf(
'userid=%d and type=%d and opaque=%d',
$logged_in_user->id,
NOTIFY_SUBSCRIBED_FORUM,
$forum->id
));
show_button_small(
"forum_forum.php?id=$forum->id&action=unsubscribe",
'Unsubscribe',
'Unsubscribe from this forum'
);
} else {
page_head(tra("Message boards").' : '.$forum->title);
show_button_small(
"forum_forum.php?id=$forum->id&action=subscribe",
'Subscribe',
'Click to get notified when there are new threads in this forum'
);
}
show_forum_header($user);
show_forum_title($category, $forum, NULL);
break;
case 1:
$team = BoincTeam::lookup_id($forum->category);
page_head(tra("Team message board for %1", $team->name));
show_forum_header($user);
show_team_forum_title($forum);
break;
}
echo '
<p>
<form action="forum_forum.php" method="get" class="form-inline">
<input type="hidden" name="id" value="'.$forum->id.'">
<table width="100%" cellspacing="0" cellpadding="0">
<tr valign="top">
<td colspan=2>
';
if (user_can_create_thread($user, $forum)) {
show_button(
"forum_post.php?id=$id",
tra("New thread"),
tra("Add a new thread to this forum")
echo '</td>
<td valign=top align="right">
<div class="form-group">
';
echo select_from_array("sort", $forum_sort_styles, $sort_style);
echo sprintf('
<input class="btn btn-sm" %s type="submit" value="Sort">
</div>
</td>
</tr>
</table>
</form>
<p></p> ',
button_style()
);
show_forum_threads($forum, $start, $sort_style, $user, $subs);
echo "
<p>".
tra("This message board is available as an %1 RSS feed %2", "<a href=forum_rss.php?forumid=$forum->id&setup=1>", "<img src=img/feed_logo.png></a>");
page_tail();
}
echo '</td>
<td valign=top align="right">
<div class="form-group">
';
echo select_from_array("sort", $forum_sort_styles, $sort_style);
echo sprintf('
<input class="btn btn-sm" %s type="submit" value="Sort">
</div>
</td>
</tr>
</table>
</form>
<p></p> ',
button_style()
);
show_forum($forum, $start, $sort_style, $user);
echo "
<p>".
tra("This message board is available as an %1 RSS feed %2", "<a href=forum_rss.php?forumid=$forum->id&setup=1>", "<img src=img/feed_logo.png></a>");
page_tail();
// This function shows the threads for the given forum
// Starting from $start,
// Show the threads for the given forum
// starting from $start,
// using the given $sort_style (as defined in forum.php)
// and using the features for the logged in user in $user.
//
function show_forum($forum, $start, $sort_style, $user) {
function show_forum_threads($forum, $start, $sort_style, $user, $subs) {
$page_nav = page_links(
"forum_forum.php?id=$forum->id&amp;sort=$sort_style",
$forum->threads,
@ -164,10 +184,6 @@ function show_forum($forum, $start, $sort_style, $user) {
$sort_style, $show_hidden, $sticky_first
);
if ($user) {
$subs = BoincSubscription::enum("userid=$user->id");
}
// Run through the list of threads, displaying each of them
//
foreach ($threads as $thread) {
@ -177,7 +193,7 @@ function show_forum($forum, $start, $sort_style, $user) {
//if ($thread->status==1){
// This is an answered helpdesk thread
if ($user && is_subscribed($thread, $subs)) {
if ($user && is_subscribed($thread->id, $subs)) {
echo '<tr class="bg-info">';
} else {
// Just a standard thread.
@ -238,4 +254,20 @@ function show_forum($forum, $start, $sort_style, $user) {
echo "<br>$page_nav"; // show page links
}
$id = get_int("id");
$forum = BoincForum::lookup_id($id);
if (!$forum) error_page("forum ID not found");
$user = get_logged_in_user(false);
$action = get_str('action', true);
if ($action == 'subscribe') {
BoincSubscription::replace($user->id, -$id);
forum_page($forum, $user, 'You are now subscribed to this forum.');
} else if ($action == 'unsubscribe') {
BoincSubscription::delete($user->id, -$id);
forum_page($forum, $user, 'You are now unsubscribed from this forum.');
} else {
forum_page($forum, $user);
}
?>

View File

@ -167,10 +167,12 @@ if (!$logged_in_user) {
}
if ($is_subscribed) {
$type = NOTIFY_SUBSCRIBED_POST;
BoincNotify::delete_aux(
"userid=$logged_in_user->id and type=$type and opaque=$thread->id"
);
BoincNotify::delete_aux(sprintf(
'userid=%d and type=%d and opaque=%d',
$logged_in_user->id,
NOTIFY_SUBSCRIBED_THREAD,
$thread->id
));
$url = "forum_subscribe.php?action=unsubscribe&amp;thread=".$thread->id."$tokens";
show_button_small(
$url,
@ -182,7 +184,7 @@ if (!$logged_in_user) {
show_button_small(
$url,
tra("Subscribe"),
tra("Click to get email when there are new posts in this thread")
tra("Click to get notified when there are new posts in this thread")
);
}