MyBB Community Forums

Full Version: Private threads
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Pages: 1 2
Can someone make a plugin/mod for "Private threads" (sometimes called "Conferences")?

My vision of it is:
* There is a (designated) forum, all threads in which are "private" (i.e. have a list of allowed users assigned to it).
* Listing this forum shows only threads visible to current user. Guests can see no threads.
* A moderator of this forum can see all the threads.
* Thread owner (author) can modify access list (add/remove users).
In Russia, there is a saying "Saving the drowning one is in the hands of the drowning one" (something like that).

So I will post my findings. Note that currently this is more like a concept/hack than a full-scale mod. Note also, that there seems to be no way to do this as a plugin.

Step 1. Modify the DB (I will assume that TABLE_PREFIX = "mybb_"):
ALTER TABLE mybb_threads ADD COLUMN userlist VARCHAR(255) NOT NULL DEFAULT '';

Step 2. Modify 'showthread' template:
Find
{$classic_header}
Change to
{$confusers}{$classic_header}

Step 3. Alter file showthread.php:
Find
// Get the thread details from the database.
$thread = get_thread($mybb->input['tid']); 
Insert after:
$userlist = $thread['userlist'];
if ($userlist != '')
{
//	if (strpos($userlist, $mybb->user['uid'].",") === FALSE)
//	{
//		error($lang->error_invalidthread);
//		return;
//	}
    $ulist = explode(",", $thread['userlist']);
	$confuserarr = array();

	$canview = FALSE;

	foreach ($ulist as $user)
	{
		if ($user == $mybb->user['uid'])
			$canview = TRUE;

		$dbuser = get_user($user);
		$uname = format_name($dbuser['username'], $dbuser['usergroup'], $dbuser['displaygroup']);
		$uuu = build_profile_link($uname, $user);

		$confuserarr[] = $uuu;
	}

	if ($canview === FALSE)
		error($lang->error_invalidthread);

	$confusers = implode(', ', $confuserarr);
	$confusers = '<tr><td colspan="2"><div id="thread_ulist">This thread can be viewed by: '.$confusers.'</div></td></tr>';
}
else
{
	$confusers = "<tr><td><div>This is a public thread</div></td></tr>";
} 

Step 4. Try to hide the thread (not working Sad)
Find:
while($thread = $db->fetch_array($query))
{
	if($db->type == "pgsql")
	{
		$thread['averagerating'] = $averagerating[$thread['tid']];
	}
Insert below:
	if (!is_moderator($fid) && $thread['t.userlist'] != '')
	{
		$canview = FALSE;

		foreach ($thread['t.userlist'] as $user)
		{
			if ($user == $mybb->user['uid'])
				$canview = TRUE;
        }

        if ($canview === FALSE)
        	continue;
    }

I also tried Step 4a:
Find:
if(is_array($threadcache))
{
	foreach($threadcache as $thread)
	{
insert:
		if (!is_moderator($fid) && $thread['t.userlist'] != '')
		{
			$canview = FALSE;

			foreach ($thread['t.userlist'] as $user)
			{
				if ($user == $mybb->user['uid'])
					$canview = TRUE;
	        }

	        if ($canview === FALSE)
	        	continue;
		}
but that didn't work as well.

Also, I still haven't figured out how to:
  • Count only accessible threads in "Conferences" forum
  • Show "Has new posts/threads" icon only if there are new thread/posts accessible to user.
Private messages with multiple recipients may not be as easy to track as a thread, but the functionality is there, it works, and it's private, and you can create subfolders for private messages to sort them out better.

If private messages would remember which message was a reply to which other message, it'd be possible to do a thread view for private messages. Which would actually be a cool feature, private thread with multiple users or no.

In general it seems to be a much better idea to make this a spin off the private message system than implement it as thread and then try to make that private... private messages already have everything except the thread view, threads have nothing but the thread view.
I'll keep this idea as "Plan B".
In the meantime, I went on and created editor for user lists. This requires even more edits.

Step 0. Create a forum for stroing private threads and note its fid - you'll need it in step 6.

Step 3 (replaces the one before):
Find
// Get the thread details from the database.
$thread = get_thread($mybb->input['tid']); 
Insert after:
$userlist = $thread['userlist'];
if ($userlist != '')
{
    $ulist = explode(",", $thread['userlist']);
    $confuserarr = array();

    $canview = FALSE;

    foreach ($ulist as $user)
    {
        if ($user == $mybb->user['uid'])
            $canview = TRUE;

        $dbuser = get_user($user);
        $uname = format_name($dbuser['username'], $dbuser['usergroup'], $dbuser['displaygroup']);
        $uuu = build_profile_link($uname, $user);

        $confuserarr[] = $uuu;
    }

    if ($canview === FALSE)
        error($lang->error_invalidthread);

    $confusers = implode(', ', $confuserarr);
    $confusers = '<tr><td colspan="2">This thread can be viewed by: '.$confusers."&nbsp;&nbsp;<a href=\"#\" onclick=\"MyBB.popupWindow('misc.php?action=editulist&tid=".$thread['tid']."', 'userList', 350, 380)\"><strong>[Edit]</strong></a></td></tr>";
}
else
{
    $confusers = "<tr><td><div>This is a public thread</div></td></tr>";
} 

Step 5. Create a new template misc_editulist:
<html>
<head>
<title>Edit user list</title>
{$headerinclude}
<style type="text/css">
body {
	text-align: left;
}
</style>
</head>
<body style="margin:0; padding: 4px; top: 0; left: 0;">
	<table width="100%" cellspacing="{$theme['borderwidth']}" cellpadding="{$theme['tablespace']}" border="0" align="center" class="tborder">
	<tr>
		<td class="thead">
			<div class="float_right" style="margin-top: 3px;"><span class="smalltext"><a href="#" onclick="window.close();">{$lang->close}</a></span></div>
			<div><strong>Edit user list</strong></div>
		</td>
	</tr>
	<tr>
		<td class="trow2">
			<div style="overflow: auto; height: 300px;"><table class="post_author" style="border-style: dashed border-width: 1pt" width="100%">{$ulist}</table>	</div>
		</td>
	</tr>
            <tr>
                        <td>
                                    <form method="get" action="misc.php" name="addusr">
                                         <input type="hidden" name="action" value="editulist" />
                                         <input type="hidden" name="tid" value="{$tid}" /> 
                                         <select name="adduser">
                                             {$allusers}
                                         </select>
                                         <input type="submit" value="Add" class="submit_button" name="do_add" /> 
                                     </form>
                        </td>
            </tr>
	</table>
</body>
</html>

Step 6. Modify misc.php:
Find
elseif($mybb->input['action'] == "whoposted")
Insert before:
elseif($mybb->input['action'] == "editulist")
{
	global $db;

	$tid = $mybb->input['tid'];

	$thread = get_thread($mybb->input['tid']);

	if(($mybb->user['uid'] == 0) || ($thread['uid'] != $mybb->user['uid'] && !is_moderator($thread['fid'])))
	{
		error_no_permission();
	}

	if (!isset($mybb->input['tid']))
	{
		error("No tid");
	}


	if ($thread['fid'] != '3') //!!! Change 3 to fid from step 0
	{
		error("Wrong fid");
	}

	$ulist = $thread['userlist'];
	$ulist_t = explode(",", $ulist);

	foreach ($ulist_t as $key)
	{
		if ($key == $thread['uid'])
			$ulist_a[$key] = 0;
		else
			$ulist_a[$key] = 1;
	}

	if($mybb->input['removeuser'])
	{
		if ($ulist_a[$mybb->input['removeuser']] === 1)
		{
			foreach($ulist_a as $uid => $flag)
			{
				if($uid == $mybb->input['removeuser'])
				{
					unset($ulist_a[$uid]);
					$key = array_search($uid, $ulist_t);
					unset($ulist_t[$key]);
				}
			}
			$ulist = implode(',', $ulist_t);
			$db->update_query("threads", array('userlist' => $ulist), "tid='".$tid."'");
		}
	}

	if ($mybb->input['adduser'])
	{
		$inlist = isset($ulist_a[$mybb->input['adduser']]);

		if ($inlist === FALSE)
		{
			$dbuser = get_user($mybb->input['adduser']);
			if (isset($dbuser['username']))
			{
	        	$ulist_a[$mybb->input['adduser']] = 1;
	        	$ulist_t[] = $mybb->input['adduser'];
			}

			$ulist = implode(',', $ulist_t);
			$db->update_query("threads", array('userlist' => $ulist), "tid='".$tid."'");
		}
	}

	$ulist = "<tr><th>Username</th><th>Remove</th></tr>";

	foreach ($ulist_a as $user => $flag)
	{
		$dbuser = get_user($user);
		if (!$dbuser['username'] || $flag == 0) //no such user -> skip
			continue;

		//format link
        $uname = format_name($dbuser['username'], $dbuser['usergroup'], $dbuser['displaygroup']);
        $uuu = build_profile_link($uname, $user);

		$ulist = $ulist."<tr><td width=\"95%\">".$uuu."</td><td>";
		if ($flag == 1)
			$ulist = $ulist.'<a href="misc.php?action=editulist&amp;tid='.$thread['tid'].'&amp;removeuser='.$user.'">[X]</a>';
		else
			$ulist = $ulist.'[Owner]';

		$ulist = $ulist.'</td></tr><br/>'."\n";
	}

	//Build list for select --> $allusers
	$allulist = $db->simple_select('users', 'uid, username');
	$allusers = "<option value=\"-1\">--- Select user ---</option> ";
	while ($ulist_r = $db->fetch_array($allulist))
	{
		if (!is_banned_username($ulist_r['username']) && !isset($ulist_a[$ulist_r['uid']]))
			$allusers = $allusers.'<option value="'.$ulist_r['uid'].'">'.$ulist_r['username'].'</option>\n';
	}

	eval("\$editulist = \"".$templates->get("misc_editulist")."\";");
	output_page($editulist);
}

And here goes the last part: modifying newthread script to incorporate user lists.

Step 7. Modify template newreply:
Find
</tr>
{$loginbox}
<tr>
Replace with
</tr>
{$loginbox}
{$ulistbox}
<tr>

Step 8. Create template ulistbox:
<tr>
<td class="trow2"><strong>User access list (comma separated): </strong></td>
<td class="trow2"><input type="text" class="textbox" name="userlist" size="30" value="{$username}" /></td>
</tr>

Step 9. Modify inc/datahandlers/post.php:
Find
		if($draft_check)
		{
			$this->thread_insert_data = array(
				"subject" => $db->escape_string($thread['subject']),
				"icon" => intval($thread['icon']),
				"username" => $db->escape_string($thread['username']),
				"dateline" => intval($thread['dateline']),
				"lastpost" => intval($thread['dateline']),
				"lastposter" => $db->escape_string($thread['username']),
				"visible" => $visible
			);

			$plugins->run_hooks_by_ref("datahandler_post_insert_thread", $this);
replace with
		if($draft_check)
		{
			$this->thread_insert_data = array(
				"subject" => $db->escape_string($thread['subject']),
				"icon" => intval($thread['icon']),
				"username" => $db->escape_string($thread['username']),
				"dateline" => intval($thread['dateline']),
				"lastpost" => intval($thread['dateline']),
				"lastposter" => $db->escape_string($thread['username']),
				"visible" => $visible,
                                             "userlist" => $thread['userlist']
			);

			$plugins->run_hooks_by_ref("datahandler_post_insert_thread", $this);
Find
		// Inserting a new thread into the database.
		else
		{
			$this->thread_insert_data = array(
				"fid" => $thread['fid'],
				"subject" => $db->escape_string($thread['subject']),
				"icon" => intval($thread['icon']),
				"uid" => $thread['uid'],
				"username" => $db->escape_string($thread['username']),
				"dateline" => intval($thread['dateline']),
				"lastpost" => intval($thread['dateline']),
				"lastposter" => $db->escape_string($thread['username']),
				"views" => 0,
				"replies" => 0,
				"visible" => $visible,
				"notes" => ''
			);

			$plugins->run_hooks_by_ref("datahandler_post_insert_thread", $this);
replace with
			$this->thread_insert_data = array(
				"fid" => $thread['fid'],
				"subject" => $db->escape_string($thread['subject']),
				"icon" => intval($thread['icon']),
				"uid" => $thread['uid'],
				"username" => $db->escape_string($thread['username']),
				"dateline" => intval($thread['dateline']),
				"lastpost" => intval($thread['dateline']),
				"lastposter" => $db->escape_string($thread['username']),
				"views" => 0,
				"replies" => 0,
				"visible" => $visible,
				"notes" => '',
				"userlist" => $thread['userlist']
			);

			$plugins->run_hooks_by_ref("datahandler_post_insert_thread", $this);

Step 10. Modify newthread.php:
Find
	// Set the thread data that came from the input to the $thread array.
	$new_thread = array(
		"fid" => $forum['fid'],
		"subject" => $mybb->input['subject'],
		"icon" => $mybb->input['icon'],
		"uid" => $uid,
		"username" => $username,
		"message" => $mybb->input['message'],
		"ipaddress" => get_ip(),
		"posthash" => $mybb->input['posthash']
	);
insert after
	//Build userlist
	$unlist = trim($mybb->input['userlist'], ", ");
	$unarr = explode(",", $unlist);
	$uidarr = array($mybb->user['uid']);
	foreach ($unarr as $uname)
	{
		$uname = trim($uname);
		$q = $db->simple_select("users", "uid", "username = '".$uname."'", array ("limit_start" => 0, "limit" => 1));
		$userid = $db->fetch_field($q, "uid");
		if (isset($userid) && (array_search($userid, $uidarr) === FALSE))
			$uidarr[] = $userid;
	}
	$uidlist = implode(",", $uidarr);

	$new_thread['userlist'] = $uidlist;

Find
	$plugins->run_hooks("newthread_start");
insert after:
	if ($fid == 3) //!!!! Replace with one from Step 0
	{
		if (!isset($username))
		{
			$username = $mybb->user['username'];
			eval("\$ulistbox = \"".$templates->get("ulistbox")."\";");
			unset($username);
		}
		else
			eval("\$ulistbox = \"".$templates->get("ulistbox")."\";");
	}
	else
		$ulistbox = "";


So, if anyone can help me with:
a) showing only the threads to which the user has access
b) showing correct thread count
c) correct pagination
d) correct "last post/thread" and "has new msg" state

then this mod will be complete.
I have solved point "a" (and "c", it seems) in the list above. The modifications are quite extensive, though.

So, here are the instructions:
0a. Get yourself a tool that can read and apply patch-files ("patch" on *nixes, "WinMerge" on Windows)
0b. Create a forum with ID=3 for private threads. It is easy to guess that it must be completely hidden form guests
1. Apply the forumdisplay.patch ([attachment=17635]) to forumdisplay.php
2. Apply the misc.patch ([attachment=17639]) to misc.php
3. Apply the showthread.patch ([attachment=17637]) to showthread.php
4. Apply the functions_search.patch ([attachment=17638]) to inc/functions_search.php
5. Create template misc_editulist:
<html>
<head>
<title>Edit user list</title>
{$headerinclude}
<style type="text/css">
body {
    text-align: left;
}
</style>
</head>
<body style="margin:0; padding: 4px; top: 0; left: 0;">
    <table width="100%" cellspacing="{$theme['borderwidth']}" cellpadding="{$theme['tablespace']}" border="0" align="center" class="tborder">
    <tr>
        <td class="thead">
            <div class="float_right" style="margin-top: 3px;"><span class="smalltext"><a href="#" onclick="window.close();">{$lang->close}</a></span></div>
            <div><strong>Change list of participants</strong></div>
        </td>
    </tr>
    <tr>
        <td class="trow2">
            <div style="overflow: auto; height: 300px;"><table class="post_author" style="border-style: dashed border-width: 1pt" width="100%">{$ulist}</table>    </div>
        </td>
    </tr>
            <tr>
                        <td>
                                    <form method="get" action="misc.php" name="addusr">
                                         <input type="hidden" name="action" value="editulist" />
                                         <input type="hidden" name="tid" value="{$tid}" />
                                         <input type="text" name="adduser">
                                         <input type="submit" value="Add" class="submit_button" name="do_add" />
                                     </form>
                        </td>
            </tr>
    </table>
</body>
6. Install and activate private threads plugin ([attachment=17640]): it will take care of modifying database and templates, and will block direct access to a private thread by its TID (thread ID)

Known bugs:
  • If no moderators/admins are in ACL for a private thread, it can't be moderated.
  • All private threads are clearly visible in "Archive" (Lite) mode - I'm still to find how to fix that
  • ACL cannot be specified on forum creation, only after the thread is created

To-do list:
  • Forum ID as a configuration parameter
  • Setting ACL on creation
  • Lite mode fix
  • Moderation of private threads
  • Better design for user list
  • Correct thread count (only accessible)
  • Correct last post/thread indication
Wow. A lot of this code could be done a plugin, though.
frostdchutz, "if you say "a" - say "b" as well" (a russian proverb, here - I'm eager to see how this is done in plugin Smile).
I won't write the code for you, just a suggestion. Smile

My Google SEO plugin also requires core file edits. However I try to keep the amount of changes to a bare minimum. This is how the patch looks like (partially), and it's simple enough to do the edits manually if necessary.

 function get_thread_link($tid, $page=0, $action='')
 {
+	if(function_exists("google_seo_url_thread"))
+	{
+		$link = google_seo_url_thread($tid, $page, $action);
+
+		if($link)
+		{
+			return $link;
+		}
+	}
+

So mainly all it does is add a function call and all the complicated logic is done in the plugin side of things. This also means no breakage if the other modifications aren't there (if the plugin isn't loaded, it acts as the original file).

As for your code, for example you can move the entire misc.patch in a plugin (misc start hook), since all it does is define a new action. In forumdisplay, the check_pt_access function could be defined in a plugin. The stuff you added in the loop could be done in a plugin in a separate loop and the core modification would be reduced to a single function call in that plugin. The more code you can move into the plugin the less edits / patches you'll have to maintain with every MyBB update.
Ah, I see... I was actually thinking about using a function defined in a plugin file, but somehow I though (the VC++ background, you know...) that I have to define a function in some special way for it to be visible in other modules.

BTW, is there a function in MyBB to create (not modify) a template?
any image??
Pages: 1 2