[Xfce4-commits] <forum:master> Update to fluxbb 1.4.5.

Nick Schermer noreply at xfce.org
Thu Mar 24 20:12:01 CET 2011


Updating branch refs/heads/master
         to 3d44937cc033904f9139ebccbbaf7f6b518aa738 (commit)
       from a8caa70b4a8b81584743a1d8464d3f4ffb489b3c (commit)

commit 3d44937cc033904f9139ebccbbaf7f6b518aa738
Author: Nick Schermer <nick at xfce.org>
Date:   Thu Mar 24 20:02:13 2011 +0100

    Update to fluxbb 1.4.5.

 admin_bans.php                     |   34 +++-
 admin_categories.php               |    6 +-
 admin_censoring.php                |    6 +-
 admin_groups.php                   |    2 +-
 admin_maintenance.php              |  188 +++++++++++++++-
 admin_options.php                  |   33 +++-
 admin_ranks.php                    |    2 +-
 admin_reports.php                  |    6 +-
 admin_users.php                    |  427 ++++++++++++++++++++++++++++++++++++
 db_update.php                      |   20 +-
 edit.php                           |   22 ++-
 extern.php                         |  123 +++++++---
 header.php                         |   46 ++++-
 include/cache.php                  |   48 ++++-
 include/common.php                 |    7 +-
 include/common_admin.php           |   40 +++-
 include/dblayer/sqlite.php         |    2 +-
 include/email.php                  |   19 ++-
 include/functions.php              |  325 +++++++++++++++++++--------
 include/parser.php                 |    8 +-
 include/search_idx.php             |    4 +-
 index.php                          |   15 +-
 lang/English/admin_bans.php        |    3 +-
 lang/English/admin_maintenance.php |   18 ++-
 lang/English/admin_options.php     |    7 +
 lang/English/admin_reports.php     |    1 +
 lang/English/admin_users.php       |   37 +++
 lang/English/misc.php              |    2 +-
 lang/English/post.php              |    2 +
 lang/English/profile.php           |    1 +
 lang/English/search.php            |   11 +-
 login.php                          |    8 +
 moderate.php                       |   62 +++++-
 post.php                           |   30 ++-
 profile.php                        |   27 ++-
 register.php                       |    6 +
 search.php                         |   12 +-
 style/Air.css                      |    9 +-
 style/Air/base_admin.css           |    6 +-
 style/Earth.css                    |    9 +-
 style/Earth/base_admin.css         |    6 +-
 style/Fire.css                     |    9 +-
 style/Fire/base_admin.css          |    6 +-
 style/imports/base_admin.css       |    1 +
 viewtopic.php                      |   34 +++-
 45 files changed, 1431 insertions(+), 259 deletions(-)

diff --git a/admin_bans.php b/admin_bans.php
index a344d9f..acd2a6e 100644
--- a/admin_bans.php
+++ b/admin_bans.php
@@ -52,9 +52,18 @@ if (isset($_REQUEST['add_ban']) || isset($_GET['edit_ban']))
 			}
 		}
 
-		// Make sure we're not banning an admin
-		if (isset($group_id) && $group_id == PUN_ADMIN)
-			message(sprintf($lang_admin_bans['User is admin message'], pun_htmlspecialchars($ban_user)));
+		// Make sure we're not banning an admin or moderator
+		if (isset($group_id))
+		{
+			if ($group_id == PUN_ADMIN)
+				message(sprintf($lang_admin_bans['User is admin message'], pun_htmlspecialchars($ban_user)));
+
+			$result = $db->query('SELECT g_moderator FROM '.$db->prefix.'groups WHERE g_id='.$group_id) or error('Unable to fetch group info', __FILE__, __LINE__, $db->error());
+			$is_moderator_group = $db->result($result);
+
+			if ($is_moderator_group)
+				message(sprintf($lang_admin_bans['User is mod message'], pun_htmlspecialchars($ban_user)));
+		}
 
 		// If we have a $user_id, we can try to find the last known IP of that user
 		if (isset($user_id))
@@ -183,6 +192,25 @@ else if (isset($_POST['add_edit_ban']))
 		message($lang_admin_bans['Must enter message']);
 	else if (strtolower($ban_user) == 'guest')
 		message($lang_admin_bans['Cannot ban guest message']);
+	
+	// Make sure we're not banning an admin or moderator
+	if (!empty($ban_user))
+	{
+		$result = $db->query('SELECT group_id FROM '.$db->prefix.'users WHERE username=\''.$db->escape($ban_user).'\' AND id>1') or error('Unable to fetch user info', __FILE__, __LINE__, $db->error());
+		if ($db->num_rows($result))
+		{
+			$group_id = $db->result($result);
+			
+			if ($group_id == PUN_ADMIN)
+				message(sprintf($lang_admin_bans['User is admin message'], pun_htmlspecialchars($ban_user)));
+	
+			$result = $db->query('SELECT g_moderator FROM '.$db->prefix.'groups WHERE g_id='.$group_id) or error('Unable to fetch group info', __FILE__, __LINE__, $db->error());
+			$is_moderator_group = $db->result($result);
+	
+			if ($is_moderator_group)
+				message(sprintf($lang_admin_bans['User is mod message'], pun_htmlspecialchars($ban_user)));
+		}
+	}
 
 	// Validate IP/IP range (it's overkill, I know)
 	if ($ban_ip != '')
diff --git a/admin_categories.php b/admin_categories.php
index 90c2935..2877cd3 100644
--- a/admin_categories.php
+++ b/admin_categories.php
@@ -170,7 +170,7 @@ generate_admin_menu('categories');
 	<div class="blockform">
 		<h2><span><?php echo $lang_admin_categories['Add categories head'] ?></span></h2>
 		<div class="box">
-			<form method="post" action="admin_categories.php?action=foo">
+			<form method="post" action="admin_categories.php">
 				<div class="inform">
 					<fieldset>
 						<legend><?php echo $lang_admin_categories['Add categories subhead'] ?></legend>
@@ -192,7 +192,7 @@ generate_admin_menu('categories');
 
 <?php if ($num_cats): ?>		<h2 class="block2"><span><?php echo $lang_admin_categories['Delete categories head'] ?></span></h2>
 		<div class="box">
-			<form method="post" action="admin_categories.php?action=foo">
+			<form method="post" action="admin_categories.php">
 				<div class="inform">
 					<fieldset>
 						<legend><?php echo $lang_admin_categories['Delete categories subhead'] ?></legend>
@@ -222,7 +222,7 @@ generate_admin_menu('categories');
 
 <?php if ($num_cats): ?>		<h2 class="block2"><span><?php echo $lang_admin_categories['Edit categories head'] ?></span></h2>
 		<div class="box">
-			<form method="post" action="admin_categories.php?action=foo">
+			<form method="post" action="admin_categories.php">
 				<div class="inform">
 					<fieldset>
 						<legend><?php echo $lang_admin_categories['Edit categories subhead'] ?></legend>
diff --git a/admin_censoring.php b/admin_censoring.php
index 8d18f0b..06d2a85 100644
--- a/admin_censoring.php
+++ b/admin_censoring.php
@@ -14,7 +14,7 @@ require PUN_ROOT.'include/common.php';
 require PUN_ROOT.'include/common_admin.php';
 
 
-if (!$pun_user['is_admmod'] || ($pun_user['g_moderator'] == '1' && $pun_config['o_censoring'] == '0'))
+if ($pun_user['g_id'] != PUN_ADMIN)
 	message($lang_common['No permission']);
 
 // Load the admin_censoring.php language file
@@ -95,12 +95,12 @@ generate_admin_menu('censoring');
 	<div class="blockform">
 		<h2><span><?php echo $lang_admin_censoring['Censoring head'] ?></span></h2>
 		<div class="box">
-			<form id="censoring" method="post" action="admin_censoring.php?action=foo">
+			<form id="censoring" method="post" action="admin_censoring.php">
 				<div class="inform">
 					<fieldset>
 						<legend><?php echo $lang_admin_censoring['Add word subhead'] ?></legend>
 						<div class="infldset">
-							<p><?php echo $lang_admin_censoring['Add word info'].($pun_user['g_id'] != PUN_ADMIN ? '' : ' '.($pun_config['o_censoring'] == '1' ? sprintf($lang_admin_censoring['Censoring enabled'], '<a href="admin_options.php#censoring">'.$lang_admin_common['Options'].'</a>') : sprintf($lang_admin_censoring['Censoring disabled'], '<a href="admin_options.php#censoring">'.$lang_admin_common['Options'].'</a>'))) ?></p>
+							<p><?php echo $lang_admin_censoring['Add word info'].' '.($pun_config['o_censoring'] == '1' ? sprintf($lang_admin_censoring['Censoring enabled'], '<a href="admin_options.php#censoring">'.$lang_admin_common['Options'].'</a>') : sprintf($lang_admin_censoring['Censoring disabled'], '<a href="admin_options.php#censoring">'.$lang_admin_common['Options'].'</a>')) ?></p>
 							<table cellspacing="0">
 							<thead>
 								<tr>
diff --git a/admin_groups.php b/admin_groups.php
index 0867ad5..1e1e9bd 100644
--- a/admin_groups.php
+++ b/admin_groups.php
@@ -465,7 +465,7 @@ generate_admin_menu('groups');
 	<div class="blockform">
 		<h2><span><?php echo $lang_admin_groups['Add groups head'] ?></span></h2>
 		<div class="box">
-			<form id="groups" method="post" action="admin_groups.php?action=foo">
+			<form id="groups" method="post" action="admin_groups.php">
 				<div class="inform">
 					<fieldset>
 						<legend><?php echo $lang_admin_groups['Add group subhead'] ?></legend>
diff --git a/admin_maintenance.php b/admin_maintenance.php
index 81096eb..fe58efe 100644
--- a/admin_maintenance.php
+++ b/admin_maintenance.php
@@ -22,14 +22,16 @@ if ($pun_user['g_id'] != PUN_ADMIN)
 // Load the admin_maintenance.php language file
 require PUN_ROOT.'lang/'.$admin_language.'/admin_maintenance.php';
 
-if (isset($_GET['i_per_page']) && isset($_GET['i_start_at']))
+$action = isset($_REQUEST['action']) ? trim($_REQUEST['action']) : '';
+
+if ($action == 'rebuild')
 {
-	$per_page = intval($_GET['i_per_page']);
-	$start_at = intval($_GET['i_start_at']);
+	$per_page = isset($_GET['i_per_page']) ? intval($_GET['i_per_page']) : 0;
+	$start_at = isset($_GET['i_start_at']) ? intval($_GET['i_start_at']) : 0;
 
 	// Check per page is > 0
 	if ($per_page < 1)
-		message($lang_admin_maintenance['Must be integer message']);
+		message($lang_admin_maintenance['Posts must be integer message']);
 
 	@set_time_limit(0);
 
@@ -112,7 +114,7 @@ h1 {
 		$result = $db->query('SELECT id FROM '.$db->prefix.'posts WHERE id > '.$end_at.' ORDER BY id ASC LIMIT 1') or error('Unable to fetch next ID', __FILE__, __LINE__, $db->error());
 
 		if ($db->num_rows($result) > 0)
-			$query_str = '?i_per_page='.$per_page.'&i_start_at='.$db->result($result);
+			$query_str = '?action=rebuild&i_per_page='.$per_page.'&i_start_at='.$db->result($result);
 	}
 
 	$db->end_transaction();
@@ -121,6 +123,122 @@ h1 {
 	exit('<script type="text/javascript">window.location="admin_maintenance.php'.$query_str.'"</script><hr /><p>'.sprintf($lang_admin_maintenance['Javascript redirect failed'], '<a href="admin_maintenance.php'.$query_str.'">'.$lang_admin_maintenance['Click here'].'</a>').'</p>');
 }
 
+if ($action == 'prune')
+{
+	$prune_from = trim($_POST['prune_from']);
+	$prune_sticky = intval($_POST['prune_sticky']);
+
+	if (isset($_POST['prune_comply']))
+	{
+		confirm_referrer('admin_maintenance.php');
+
+		$prune_days = intval($_POST['prune_days']);
+		$prune_date = ($prune_days) ? time() - ($prune_days * 86400) : -1;
+
+		@set_time_limit(0);
+
+		if ($prune_from == 'all')
+		{
+			$result = $db->query('SELECT id FROM '.$db->prefix.'forums') or error('Unable to fetch forum list', __FILE__, __LINE__, $db->error());
+			$num_forums = $db->num_rows($result);
+
+			for ($i = 0; $i < $num_forums; ++$i)
+			{
+				$fid = $db->result($result, $i);
+
+				prune($fid, $prune_sticky, $prune_date);
+				update_forum($fid);
+			}
+		}
+		else
+		{
+			$prune_from = intval($prune_from);
+			prune($prune_from, $prune_sticky, $prune_date);
+			update_forum($prune_from);
+		}
+
+		// Locate any "orphaned redirect topics" and delete them
+		$result = $db->query('SELECT t1.id FROM '.$db->prefix.'topics AS t1 LEFT JOIN '.$db->prefix.'topics AS t2 ON t1.moved_to=t2.id WHERE t2.id IS NULL AND t1.moved_to IS NOT NULL') or error('Unable to fetch redirect topics', __FILE__, __LINE__, $db->error());
+		$num_orphans = $db->num_rows($result);
+
+		if ($num_orphans)
+		{
+			for ($i = 0; $i < $num_orphans; ++$i)
+				$orphans[] = $db->result($result, $i);
+
+			$db->query('DELETE FROM '.$db->prefix.'topics WHERE id IN('.implode(',', $orphans).')') or error('Unable to delete redirect topics', __FILE__, __LINE__, $db->error());
+		}
+
+		redirect('admin_maintenance.php', $lang_admin_maintenance['Posts pruned redirect']);
+	}
+
+	$prune_days = trim($_POST['req_prune_days']);
+	if ($prune_days == '' || preg_match('/[^0-9]/', $prune_days))
+		message($lang_admin_maintenance['Days must be integer message']);
+
+	$prune_date = time() - ($prune_days * 86400);
+
+	// Concatenate together the query for counting number of topics to prune
+	$sql = 'SELECT COUNT(id) FROM '.$db->prefix.'topics WHERE last_post<'.$prune_date.' AND moved_to IS NULL';
+
+	if ($prune_sticky == '0')
+		$sql .= ' AND sticky=0';
+
+	if ($prune_from != 'all')
+	{
+		$prune_from = intval($prune_from);
+		$sql .= ' AND forum_id='.$prune_from;
+
+		// Fetch the forum name (just for cosmetic reasons)
+		$result = $db->query('SELECT forum_name FROM '.$db->prefix.'forums WHERE id='.$prune_from) or error('Unable to fetch forum name', __FILE__, __LINE__, $db->error());
+		$forum = '"'.pun_htmlspecialchars($db->result($result)).'"';
+	}
+	else
+		$forum = $lang_admin_maintenance['All forums'];
+
+	$result = $db->query($sql) or error('Unable to fetch topic prune count', __FILE__, __LINE__, $db->error());
+	$num_topics = $db->result($result);
+
+	if (!$num_topics)
+		message(sprintf($lang_admin_maintenance['No old topics message'], $prune_days));
+
+
+	$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_admin_common['Admin'], $lang_admin_common['Prune']);
+	define('PUN_ACTIVE_PAGE', 'admin');
+	require PUN_ROOT.'header.php';
+
+	generate_admin_menu('maintenance');
+
+?>
+	<div class="blockform">
+		<h2><span><?php echo $lang_admin_maintenance['Prune head'] ?></span></h2>
+		<div class="box">
+			<form method="post" action="admin_maintenance.php">
+				<div class="inform">
+					<input type="hidden" name="action" value="prune" />
+					<input type="hidden" name="prune_days" value="<?php echo $prune_days ?>" />
+					<input type="hidden" name="prune_sticky" value="<?php echo $prune_sticky ?>" />
+					<input type="hidden" name="prune_from" value="<?php echo $prune_from ?>" />
+					<fieldset>
+						<legend><?php echo $lang_admin_maintenance['Confirm prune subhead'] ?></legend>
+						<div class="infldset">
+							<p><?php printf($lang_admin_maintenance['Confirm prune info'], $prune_days, $forum, forum_number_format($num_topics)) ?></p>
+							<p class="warntext"><?php echo $lang_admin_maintenance['Confirm prune warn'] ?></p>
+						</div>
+					</fieldset>
+				</div>
+				<p class="buttons"><input type="submit" name="prune_comply" value="<?php echo $lang_admin_common['Prune'] ?>" /><a href="javascript:history.go(-1)"><?php echo $lang_admin_common['Go back'] ?></a></p>
+			</form>
+		</div>
+	</div>
+	<div class="clearer"></div>
+</div>
+<?php
+
+	require PUN_ROOT.'footer.php';
+	exit;
+}
+
 
 // Get the first post ID from the db
 $result = $db->query('SELECT id FROM '.$db->prefix.'posts ORDER BY id ASC LIMIT 1') or error('Unable to fetch topic info', __FILE__, __LINE__, $db->error());
@@ -139,6 +257,7 @@ generate_admin_menu('maintenance');
 		<div class="box">
 			<form method="get" action="admin_maintenance.php">
 				<div class="inform">
+					<input type="hidden" name="action" value="rebuild" />
 					<fieldset>
 						<legend><?php echo $lang_admin_maintenance['Rebuild index subhead'] ?></legend>
 						<div class="infldset">
@@ -171,6 +290,65 @@ generate_admin_menu('maintenance');
 					</fieldset>
 				</div>
 			</form>
+
+			<form method="post" action="admin_maintenance.php" onsubmit="return process_form(this)">
+				<div class="inform">
+					<input type="hidden" name="action" value="prune" />
+					<fieldset>
+						<legend><?php echo $lang_admin_maintenance['Prune subhead'] ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_maintenance['Days old label'] ?></th>
+									<td>
+										<input type="text" name="req_prune_days" size="3" maxlength="3" tabindex="5" />
+										<span><?php echo $lang_admin_maintenance['Days old help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_maintenance['Prune sticky label'] ?></th>
+									<td>
+										<input type="radio" name="prune_sticky" value="1" tabindex="6" checked="checked" /> <strong><?php echo $lang_admin_common['Yes'] ?></strong>   <input type="radio" name="prune_sticky" value="0" /> <strong><?php echo $lang_admin_common['No'] ?></strong>
+										<span><?php echo $lang_admin_maintenance['Prune sticky help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_maintenance['Prune from label'] ?></th>
+									<td>
+										<select name="prune_from" tabindex="7">
+											<option value="all"><?php echo $lang_admin_maintenance['All forums'] ?></option>
+<?php
+
+	$result = $db->query('SELECT c.id AS cid, c.cat_name, f.id AS fid, f.forum_name FROM '.$db->prefix.'categories AS c INNER JOIN '.$db->prefix.'forums AS f ON c.id=f.cat_id WHERE f.redirect_url IS NULL ORDER BY c.disp_position, c.id, f.disp_position') or error('Unable to fetch category/forum list', __FILE__, __LINE__, $db->error());
+
+	$cur_category = 0;
+	while ($forum = $db->fetch_assoc($result))
+	{
+		if ($forum['cid'] != $cur_category) // Are we still in the same category?
+		{
+			if ($cur_category)
+				echo "\t\t\t\t\t\t\t\t\t\t\t".'</optgroup>'."\n";
+
+			echo "\t\t\t\t\t\t\t\t\t\t\t".'<optgroup label="'.pun_htmlspecialchars($forum['cat_name']).'">'."\n";
+			$cur_category = $forum['cid'];
+		}
+
+		echo "\t\t\t\t\t\t\t\t\t\t\t\t".'<option value="'.$forum['fid'].'">'.pun_htmlspecialchars($forum['forum_name']).'</option>'."\n";
+	}
+
+?>
+											</optgroup>
+										</select>
+										<span><?php echo $lang_admin_maintenance['Prune from help'] ?></span>
+									</td>
+								</tr>
+							</table>
+							<p class="topspace"><?php printf($lang_admin_maintenance['Prune info'], '<a href="admin_options.php#maintenance">'.$lang_admin_common['Maintenance mode'].'</a>') ?></p>
+							<div class="fsetsubmit"><input type="submit" name="prune" value="<?php echo $lang_admin_common['Prune'] ?>" tabindex="8" /></div>
+						</div>
+					</fieldset>
+				</div>
+			</form>
 		</div>
 	</div>
 	<div class="clearer"></div>
diff --git a/admin_options.php b/admin_options.php
index fc0a933..fb4396c 100644
--- a/admin_options.php
+++ b/admin_options.php
@@ -60,6 +60,7 @@ if (isset($_POST['form_sent']))
 		'search_all_forums'		=> $_POST['form']['search_all_forums'] != '1' ? '0' : '1',
 		'additional_navlinks'	=> pun_trim($_POST['form']['additional_navlinks']),
 		'feed_type'				=> intval($_POST['form']['feed_type']),
+		'feed_ttl'				=> intval($_POST['form']['feed_ttl']),
 		'report_method'			=> intval($_POST['form']['report_method']),
 		'mailing_list'			=> pun_trim($_POST['form']['mailing_list']),
 		'avatars'				=> $_POST['form']['avatars'] != '1' ? '0' : '1',
@@ -176,6 +177,9 @@ if (isset($_POST['form_sent']))
 	if ($form['feed_type'] < 0 || $form['feed_type'] > 2)
 		message($lang_common['Bad request']);
 
+	if ($form['feed_ttl'] < 0)
+		message($lang_common['Bad request']);
+
 	if ($form['report_method'] < 0 || $form['report_method'] > 2)
 		message($lang_common['Bad request']);
 
@@ -204,6 +208,7 @@ if (isset($_POST['form_sent']))
 		require PUN_ROOT.'include/cache.php';
 
 	generate_config_cache();
+	clear_feed_cache();
 
 	redirect('admin_options.php', $lang_admin_options['Options updated redirect']);
 }
@@ -218,7 +223,7 @@ generate_admin_menu('options');
 	<div class="blockform">
 		<h2><span><?php echo $lang_admin_options['Options head'] ?></span></h2>
 		<div class="box">
-			<form method="post" action="admin_options.php?action=foo">
+			<form method="post" action="admin_options.php">
 				<p class="submittop"><input type="submit" name="save" value="<?php echo $lang_admin_common['Save changes'] ?>" /></p>
 				<div class="inform">
 					<input type="hidden" name="form_sent" value="1" />
@@ -566,6 +571,15 @@ generate_admin_menu('options');
 										<span><?php echo $lang_admin_options['Menu items help'] ?></span>
 									</td>
 								</tr>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_options['Feed subhead'] ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
 								<tr>
 									<th scope="row"><?php echo $lang_admin_options['Default feed label'] ?></th>
 									<td>
@@ -573,6 +587,23 @@ generate_admin_menu('options');
 										<span><?php echo $lang_admin_options['Default feed help'] ?></span>
 									</td>
 								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Feed TTL label'] ?></th>
+									<td>
+										<select name="form[feed_ttl]">
+											<option value="0"<?php if ($pun_config['o_feed_ttl'] == '0') echo ' selected="selected"'; ?>><?php echo $lang_admin_options['No cache'] ?></option>
+<?php
+
+		$times = array(5, 15, 30, 60);
+
+		foreach ($times as $time)
+			echo "\t\t\t\t\t\t\t\t\t\t\t".'<option value="'.$time.'"'.($pun_config['o_feed_ttl'] == $time ? ' selected="selected"' : '').'>'.sprintf($lang_admin_options['Minutes'], $time).'</option>'."\n";
+
+?>
+										</select>
+										<span><?php echo $lang_admin_options['Feed TTL help'] ?></span>
+									</td>
+								</tr>
 							</table>
 						</div>
 					</fieldset>
diff --git a/admin_ranks.php b/admin_ranks.php
index 2ea5892..421e812 100644
--- a/admin_ranks.php
+++ b/admin_ranks.php
@@ -113,7 +113,7 @@ generate_admin_menu('ranks');
 	<div class="blockform">
 		<h2><span><?php echo $lang_admin_ranks['Ranks head'] ?></span></h2>
 		<div class="box">
-			<form id="ranks" method="post" action="admin_ranks.php?action=foo">
+			<form id="ranks" method="post" action="admin_ranks.php">
 				<div class="inform">
 					<fieldset>
 						<legend><?php echo $lang_admin_ranks['Add rank subhead'] ?></legend>
diff --git a/admin_reports.php b/admin_reports.php
index c2d99de..4f7d091 100644
--- a/admin_reports.php
+++ b/admin_reports.php
@@ -32,7 +32,7 @@ if (isset($_POST['zap_id']))
 
 	if ($zapped == '')
 		$db->query('UPDATE '.$db->prefix.'reports SET zapped='.time().', zapped_by='.$pun_user['id'].' WHERE id='.$zap_id) or error('Unable to zap report', __FILE__, __LINE__, $db->error());
-	
+
 	// Delete old reports (which cannot be viewed anyway)
 	$result = $db->query('SELECT zapped FROM '.$db->prefix.'reports WHERE zapped IS NOT NULL ORDER BY zapped DESC LIMIT 10,1') or error('Unable to fetch read reports to delete', __FILE__, __LINE__, $db->error());
 	if ($db->num_rows($result) > 0)
@@ -68,7 +68,7 @@ if ($db->num_rows($result))
 		$forum = ($cur_report['forum_name'] != '') ? '<span><a href="viewforum.php?id='.$cur_report['forum_id'].'">'.pun_htmlspecialchars($cur_report['forum_name']).'</a></span>' : '<span>'.$lang_admin_reports['Deleted'].'</span>';
 		$topic = ($cur_report['subject'] != '') ? '<span>» <a href="viewtopic.php?id='.$cur_report['topic_id'].'">'.pun_htmlspecialchars($cur_report['subject']).'</a></span>' : '<span>» '.$lang_admin_reports['Deleted'].'</span>';
 		$post = str_replace("\n", '<br />', pun_htmlspecialchars($cur_report['message']));
-		$post_id = ($cur_report['pid'] != '') ? '<span>» <a href="viewtopic.php?pid='.$cur_report['pid'].'#p'.$cur_report['pid'].'">Post #'.$cur_report['pid'].'</a></span>' : '<span>» '.$lang_admin_reports['Deleted'].'</span>';
+		$post_id = ($cur_report['pid'] != '') ? '<span>» <a href="viewtopic.php?pid='.$cur_report['pid'].'#p'.$cur_report['pid'].'">'.sprintf($lang_admin_reports['Post ID'], $cur_report['pid']).'</a></span>' : '<span>» '.$lang_admin_reports['Deleted'].'</span>';
 		$report_location = array($forum, $topic, $post_id);
 
 ?>
@@ -130,7 +130,7 @@ if ($db->num_rows($result))
 		$forum = ($cur_report['forum_name'] != '') ? '<span><a href="viewforum.php?id='.$cur_report['forum_id'].'">'.pun_htmlspecialchars($cur_report['forum_name']).'</a></span>' : '<span>'.$lang_admin_reports['Deleted'].'</span>';
 		$topic = ($cur_report['subject'] != '') ? '<span>» <a href="viewtopic.php?id='.$cur_report['topic_id'].'">'.pun_htmlspecialchars($cur_report['subject']).'</a></span>' : '<span>» '.$lang_admin_reports['Deleted'].'</span>';
 		$post = str_replace("\n", '<br />', pun_htmlspecialchars($cur_report['message']));
-		$post_id = ($cur_report['pid'] != '') ? '<span>» <a href="viewtopic.php?pid='.$cur_report['pid'].'#p'.$cur_report['pid'].'">Post #'.$cur_report['pid'].'</a></span>' : '<span>» '.$lang_admin_reports['Deleted'].'</span>';
+		$post_id = ($cur_report['pid'] != '') ? '<span>» <a href="viewtopic.php?pid='.$cur_report['pid'].'#p'.$cur_report['pid'].'">'.sprintf($lang_admin_reports['Post ID'], $cur_report['pid']).'</a></span>' : '<span>» '.$lang_admin_reports['Deleted'].'</span>';
 		$zapped_by = ($cur_report['zapped_by'] != '') ? '<a href="profile.php?id='.$cur_report['zapped_by_id'].'">'.pun_htmlspecialchars($cur_report['zapped_by']).'</a>' : $lang_admin_reports['NA'];
 		$zapped_by = ($cur_report['zapped_by'] != '') ? '<strong>'.pun_htmlspecialchars($cur_report['zapped_by']).'</strong>' : $lang_admin_reports['NA'];
 		$report_location = array($forum, $topic, $post_id);
diff --git a/admin_users.php b/admin_users.php
index f1266f2..d9c74d9 100644
--- a/admin_users.php
+++ b/admin_users.php
@@ -253,6 +253,419 @@ if (isset($_GET['show_users']))
 }
 
 
+// Move multiple users to other user groups
+else if (isset($_POST['move_users']) || isset($_POST['move_users_comply']))
+{
+	if ($pun_user['g_id'] > PUN_ADMIN)
+		message($lang_common['No permission']);
+
+	confirm_referrer('admin_users.php');
+	
+	if (isset($_POST['users']))
+	{
+		$user_ids = is_array($_POST['users']) ? array_keys($_POST['users']) : explode(',', $_POST['users']);
+		$user_ids = array_map('intval', $user_ids);
+		
+		// Delete invalid IDs
+		$user_ids = array_diff($user_ids, array(0, 1));
+	}
+	else
+		$user_ids = array();
+	
+	if (empty($user_ids))
+		message($lang_admin_users['No users selected']);
+	
+	// Are we trying to batch move any admins?
+	$result = $db->query('SELECT COUNT(*) FROM '.$db->prefix.'users WHERE id IN ('.implode(',', $user_ids).') AND group_id='.PUN_ADMIN) or error('Unable to fetch user info', __FILE__, __LINE__, $db->error());
+	if ($db->result($result) > 0)
+		message($lang_admin_users['No move admins message']);
+	
+	// Fetch all user groups
+	$all_groups = array();
+	$result = $db->query('SELECT g_id, g_title FROM '.$db->prefix.'groups WHERE g_id NOT IN ('.PUN_GUEST.','.PUN_ADMIN.') ORDER BY g_title ASC') or error('Unable to fetch groups', __FILE__, __LINE__, $db->error());
+	while ($row = $db->fetch_row($result))
+		$all_groups[$row[0]] = $row[1];
+
+	if (isset($_POST['move_users_comply']))
+	{
+		$new_group = isset($_POST['new_group']) && isset($all_groups[$_POST['new_group']]) ? $_POST['new_group'] : message($lang_admin_users['Invalid group message']);
+		
+		// Is the new group a moderator group?
+		$result = $db->query('SELECT g_moderator FROM '.$db->prefix.'groups WHERE g_id='.$new_group) or error('Unable to fetch group info', __FILE__, __LINE__, $db->error());
+		$new_group_mod = $db->result($result);
+		
+		// Fetch user groups
+		$user_groups = array();
+		$result = $db->query('SELECT id, group_id FROM '.$db->prefix.'users WHERE id IN ('.implode(',', $user_ids).')') or error('Unable to fetch user groups', __FILE__, __LINE__, $db->error());
+		while ($cur_user = $db->fetch_assoc($result))
+		{
+			if (!isset($user_groups[$cur_user['group_id']]))
+				$user_groups[$cur_user['group_id']] = array();
+			
+			$user_groups[$cur_user['group_id']][] = $cur_user['id'];
+		}
+		
+		// Are any users moderators?
+		$group_ids = array_keys($user_groups);
+		$result = $db->query('SELECT g_id, g_moderator FROM '.$db->prefix.'groups WHERE g_id IN ('.implode(',', $group_ids).')') or error('Unable to fetch group moderators', __FILE__, __LINE__, $db->error());
+		while ($cur_group = $db->fetch_assoc($result))
+		{
+			if ($cur_group['g_moderator'] == '0')
+				unset($user_groups[$cur_group['g_id']]);
+		}
+		
+		if (!empty($user_groups) && $new_group != PUN_ADMIN && $new_group_mod != '1')
+		{
+			// Fetch forum list and clean up their moderator list
+			$result = $db->query('SELECT id, moderators FROM '.$db->prefix.'forums') or error('Unable to fetch forum list', __FILE__, __LINE__, $db->error());
+			while ($cur_forum = $db->fetch_assoc($result))
+			{
+				$cur_moderators = ($cur_forum['moderators'] != '') ? unserialize($cur_forum['moderators']) : array();
+	
+				foreach ($user_groups as $group_users)
+					$cur_moderators = array_diff($cur_moderators, $group_users);
+				
+				$cur_moderators = (!empty($cur_moderators)) ? '\''.$db->escape(serialize($cur_moderators)).'\'' : 'NULL';
+				$db->query('UPDATE '.$db->prefix.'forums SET moderators='.$cur_moderators.' WHERE id='.$cur_forum['id']) or error('Unable to update forum', __FILE__, __LINE__, $db->error());
+			}
+		}
+		
+		// Change user group
+		$db->query('UPDATE '.$db->prefix.'users SET group_id='.$new_group.' WHERE id IN ('.implode(',', $user_ids).')') or error('Unable to change user group', __FILE__, __LINE__, $db->error());
+		
+		redirect('admin_users.php', $lang_admin_users['Users move redirect']);
+	}
+
+	$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_admin_common['Admin'], $lang_admin_common['Users'], $lang_admin_users['Move users']);
+	define('PUN_ACTIVE_PAGE', 'admin');
+	require PUN_ROOT.'header.php';
+	
+	generate_admin_menu('users');
+
+?>
+	<div class="blockform">
+		<h2><span><?php echo $lang_admin_users['Move users'] ?></span></h2>
+		<div class="box">
+			<form name="confirm_move_users" method="post" action="admin_users.php">
+				<input type="hidden" name="users" value="<?php echo implode(',', $user_ids) ?>" />
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_users['Move users subhead'] ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_users['New group label'] ?></th>
+									<td>
+										<select name="new_group" tabindex="1">
+<?php foreach ($all_groups as $gid => $group) : ?>											<option value="<?php echo $gid ?>"><?php echo pun_htmlspecialchars($group) ?></option>
+<?php endforeach; ?>
+										</select>
+										<span><?php echo $lang_admin_users['New group help'] ?></span>
+									</td>
+								</tr>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+				<p class="submitend"><input type="submit" name="move_users_comply" value="<?php echo $lang_admin_common['Save'] ?>" tabindex="2" /></p>
+			</form>
+		</div>
+	</div>
+	<div class="clearer"></div>
+</div>
+<?php
+
+	require PUN_ROOT.'footer.php';
+}
+
+
+// Delete multiple users
+else if (isset($_POST['delete_users']) || isset($_POST['delete_users_comply']))
+{
+	if ($pun_user['g_id'] > PUN_ADMIN)
+		message($lang_common['No permission']);
+
+	confirm_referrer('admin_users.php');
+	
+	if (isset($_POST['users']))
+	{
+		$user_ids = is_array($_POST['users']) ? array_keys($_POST['users']) : explode(',', $_POST['users']);
+		$user_ids = array_map('intval', $user_ids);
+		
+		// Delete invalid IDs
+		$user_ids = array_diff($user_ids, array(0, 1));
+	}
+	else
+		$user_ids = array();
+	
+	if (empty($user_ids))
+		message($lang_admin_users['No users selected']);
+	
+	// Are we trying to delete any admins?
+	$result = $db->query('SELECT COUNT(*) FROM '.$db->prefix.'users WHERE id IN ('.implode(',', $user_ids).') AND group_id='.PUN_ADMIN) or error('Unable to fetch user info', __FILE__, __LINE__, $db->error());
+	if ($db->result($result) > 0)
+		message($lang_admin_users['No delete admins message']);
+
+	if (isset($_POST['delete_users_comply']))
+	{
+		// Fetch user groups
+		$user_groups = array();
+		$result = $db->query('SELECT id, group_id FROM '.$db->prefix.'users WHERE id IN ('.implode(',', $user_ids).')') or error('Unable to fetch user groups', __FILE__, __LINE__, $db->error());
+		while ($cur_user = $db->fetch_assoc($result))
+		{
+			if (!isset($user_groups[$cur_user['group_id']]))
+				$user_groups[$cur_user['group_id']] = array();
+			
+			$user_groups[$cur_user['group_id']][] = $cur_user['id'];
+		}
+		
+		// Are any users moderators?
+		$group_ids = array_keys($user_groups);
+		$result = $db->query('SELECT g_id, g_moderator FROM '.$db->prefix.'groups WHERE g_id IN ('.implode(',', $group_ids).')') or error('Unable to fetch group moderators', __FILE__, __LINE__, $db->error());
+		while ($cur_group = $db->fetch_assoc($result))
+		{
+			if ($cur_group['g_moderator'] == '0')
+				unset($user_groups[$cur_group['g_id']]);
+		}
+		
+		// Fetch forum list and clean up their moderator list
+		$result = $db->query('SELECT id, moderators FROM '.$db->prefix.'forums') or error('Unable to fetch forum list', __FILE__, __LINE__, $db->error());
+		while ($cur_forum = $db->fetch_assoc($result))
+		{
+			$cur_moderators = ($cur_forum['moderators'] != '') ? unserialize($cur_forum['moderators']) : array();
+
+			foreach ($user_groups as $group_users)
+				$cur_moderators = array_diff($cur_moderators, $group_users);
+			
+			$cur_moderators = (!empty($cur_moderators)) ? '\''.$db->escape(serialize($cur_moderators)).'\'' : 'NULL';
+			$db->query('UPDATE '.$db->prefix.'forums SET moderators='.$cur_moderators.' WHERE id='.$cur_forum['id']) or error('Unable to update forum', __FILE__, __LINE__, $db->error());
+		}
+		
+		// Delete any subscriptions
+		$db->query('DELETE FROM '.$db->prefix.'topic_subscriptions WHERE user_id IN ('.implode(',', $user_ids).')') or error('Unable to delete topic subscriptions', __FILE__, __LINE__, $db->error());
+		$db->query('DELETE FROM '.$db->prefix.'forum_subscriptions WHERE user_id IN ('.implode(',', $user_ids).')') or error('Unable to delete forum subscriptions', __FILE__, __LINE__, $db->error());
+		
+		// Remove them from the online list (if they happen to be logged in)
+		$db->query('DELETE FROM '.$db->prefix.'online WHERE user_id IN ('.implode(',', $user_ids).')') or error('Unable to remove users from online list', __FILE__, __LINE__, $db->error());
+		
+		// Should we delete all posts made by these users?
+		if (isset($_POST['delete_posts']))
+		{
+			require PUN_ROOT.'include/search_idx.php';
+			@set_time_limit(0);
+
+			// Find all posts made by this user
+			$result = $db->query('SELECT p.id, p.topic_id, t.forum_id FROM '.$db->prefix.'posts AS p INNER JOIN '.$db->prefix.'topics AS t ON t.id=p.topic_id INNER JOIN '.$db->prefix.'forums AS f ON f.id=t.forum_id WHERE p.poster_id IN ('.implode(',', $user_ids).')') or error('Unable to fetch posts', __FILE__, __LINE__, $db->error());
+			if ($db->num_rows($result))
+			{
+				while ($cur_post = $db->fetch_assoc($result))
+				{
+					// Determine whether this post is the "topic post" or not
+					$result2 = $db->query('SELECT id FROM '.$db->prefix.'posts WHERE topic_id='.$cur_post['topic_id'].' ORDER BY posted LIMIT 1') or error('Unable to fetch post info', __FILE__, __LINE__, $db->error());
+
+					if ($db->result($result2) == $cur_post['id'])
+						delete_topic($cur_post['topic_id']);
+					else
+						delete_post($cur_post['id'], $cur_post['topic_id']);
+
+					update_forum($cur_post['forum_id']);
+				}
+			}
+		}
+		else
+			// Set all their posts to guest
+			$db->query('UPDATE '.$db->prefix.'posts SET poster_id=1 WHERE poster_id IN ('.implode(',', $user_ids).')') or error('Unable to update posts', __FILE__, __LINE__, $db->error());
+
+		// Delete the users
+		$db->query('DELETE FROM '.$db->prefix.'users WHERE id IN ('.implode(',', $user_ids).')') or error('Unable to delete users', __FILE__, __LINE__, $db->error());
+
+		// Delete user avatars
+		foreach ($user_ids as $user_id)
+			delete_avatar($user_id);
+		
+		redirect('admin_users.php', $lang_admin_users['Users delete redirect']);
+	}
+
+	$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_admin_common['Admin'], $lang_admin_common['Users'], $lang_admin_users['Delete users']);
+	define('PUN_ACTIVE_PAGE', 'admin');
+	require PUN_ROOT.'header.php';
+	
+	generate_admin_menu('users');
+
+?>
+	<div class="blockform">
+		<h2><span><?php echo $lang_admin_users['Delete users'] ?></span></h2>
+		<div class="box">
+			<form name="confirm_del_users" method="post" action="admin_users.php">
+				<input type="hidden" name="users" value="<?php echo implode(',', $user_ids) ?>" />
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_users['Confirm delete legend'] ?></legend>
+						<div class="infldset">
+							<p><?php echo $lang_admin_users['Confirm delete info'] ?></p>
+							<div class="rbox">
+								<label><input type="checkbox" name="delete_posts" value="1" checked="checked" /><?php echo $lang_admin_users['Delete posts'] ?><br /></label>
+							</div>
+							<p class="warntext"><strong><?php echo $lang_admin_users['Delete warning'] ?></strong></p>
+						</div>
+					</fieldset>
+				</div>
+				<p class="buttons"><input type="submit" name="delete_users_comply" value="<?php echo $lang_admin_users['Delete'] ?>" /> <a href="javascript:history.go(-1)"><?php echo $lang_admin_common['Go back'] ?></a></p>
+			</form>
+		</div>
+	</div>
+	<div class="clearer"></div>
+</div>
+<?php
+
+	require PUN_ROOT.'footer.php';
+}
+
+
+// Ban multiple users
+else if (isset($_POST['ban_users']) || isset($_POST['ban_users_comply']))
+{
+	if ($pun_user['g_id'] != PUN_ADMIN && ($pun_user['g_moderator'] != '1' || $pun_user['g_mod_ban_users'] == '0'))
+		message($lang_common['No permission']);
+
+	confirm_referrer('admin_users.php');
+	
+	if (isset($_POST['users']))
+	{
+		$user_ids = is_array($_POST['users']) ? array_keys($_POST['users']) : explode(',', $_POST['users']);
+		$user_ids = array_map('intval', $user_ids);
+		
+		// Delete invalid IDs
+		$user_ids = array_diff($user_ids, array(0, 1));
+	}
+	else
+		$user_ids = array();
+	
+	if (empty($user_ids))
+		message($lang_admin_users['No users selected']);
+	
+	// Are we trying to ban any admins?
+	$result = $db->query('SELECT COUNT(*) FROM '.$db->prefix.'users WHERE id IN ('.implode(',', $user_ids).') AND group_id='.PUN_ADMIN) or error('Unable to fetch group info', __FILE__, __LINE__, $db->error());
+	if ($db->result($result) > 0)
+		message($lang_admin_users['No ban admins message']);
+	
+	// Also, we cannot ban moderators
+	$result = $db->query('SELECT COUNT(*) FROM '.$db->prefix.'users AS u INNER JOIN '.$db->prefix.'groups AS g ON u.group_id=g.g_id WHERE g.g_moderator=1 AND u.id IN ('.implode(',', $user_ids).')') or error('Unable to fetch moderator group info', __FILE__, __LINE__, $db->error());
+	if ($db->result($result) > 0)
+		message($lang_admin_users['No ban mods message']);
+	
+	if (isset($_POST['ban_users_comply']))
+	{
+		$ban_message = pun_trim($_POST['ban_message']);
+		$ban_expire = pun_trim($_POST['ban_expire']);
+		$ban_the_ip = isset($_POST['ban_the_ip']) ? intval($_POST['ban_the_ip']) : 0;
+		
+		if ($ban_expire != '' && $ban_expire != 'Never')
+		{
+			$ban_expire = strtotime($ban_expire.' GMT');
+	
+			if ($ban_expire == -1 || !$ban_expire)
+				message($lang_admin_users['Invalid date message'].' '.$lang_admin_users['Invalid date reasons']);
+	
+			$diff = ($pun_user['timezone'] + $pun_user['dst']) * 3600;
+			$ban_expire -= $diff;
+	
+			if ($ban_expire <= time())
+				message($lang_admin_users['Invalid date message'].' '.$lang_admin_users['Invalid date reasons']);
+		}
+		else
+			$ban_expire = 'NULL';
+	
+		$ban_message = ($ban_message != '') ? '\''.$db->escape($ban_message).'\'' : 'NULL';
+		
+		// Fetch user information
+		$user_info = array();
+		$result = $db->query('SELECT id, username, email, registration_ip FROM '.$db->prefix.'users WHERE id IN ('.implode(',', $user_ids).')') or error('Unable to fetch user info', __FILE__, __LINE__, $db->error());
+		while ($cur_user = $db->fetch_assoc($result))
+			$user_info[$cur_user['id']] = array('username' => $cur_user['username'], 'email' => $cur_user['email'], 'ip' => $cur_user['registration_ip']);
+		
+		// Overwrite the registration IP with one from the last post (if it exists)
+		if ($ban_the_ip != 0)
+		{
+			$result = $db->query('SELECT p.poster_id, p.poster_ip FROM '.$db->prefix.'posts AS p INNER JOIN (SELECT MAX(id) AS id FROM '.$db->prefix.'posts WHERE poster_id IN ('.implode(',', $user_ids).') GROUP BY poster_id) AS i ON p.id=i.id') or error('Unable to fetch post info', __FILE__, __LINE__, $db->error());
+			while ($cur_address = $db->fetch_assoc($result))
+				$user_info[$cur_address['poster_id']]['ip'] = $cur_address['poster_ip'];
+		}
+		
+		// And insert the bans!
+		foreach ($user_ids as $user_id)
+		{
+			$ban_username = '\''.$db->escape($user_info[$user_id]['username']).'\'';
+			$ban_email = '\''.$db->escape($user_info[$user_id]['email']).'\'';
+			$ban_ip = ($ban_the_ip != 0) ? '\''.$db->escape($user_info[$user_id]['ip']).'\'' : 'NULL';
+			
+			$db->query('INSERT INTO '.$db->prefix.'bans (username, ip, email, message, expire, ban_creator) VALUES('.$ban_username.', '.$ban_ip.', '.$ban_email.', '.$ban_message.', '.$ban_expire.', '.$pun_user['id'].')') or error('Unable to add ban', __FILE__, __LINE__, $db->error());
+		}
+		
+		// Regenerate the bans cache
+		if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+			require PUN_ROOT.'include/cache.php';
+	
+		generate_bans_cache();
+	
+		redirect('admin_users.php', $lang_admin_users['Users banned redirect']);
+	}
+
+	$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_admin_common['Admin'], $lang_admin_common['Bans']);
+	$focus_element = array('bans2', 'ban_message');
+	define('PUN_ACTIVE_PAGE', 'admin');
+	require PUN_ROOT.'header.php';
+
+	generate_admin_menu('users');
+
+?>
+	<div class="blockform">
+		<h2><span><?php echo $lang_admin_users['Ban users'] ?></span></h2>
+		<div class="box">
+			<form id="bans2" name="confirm_ban_users" method="post" action="admin_users.php">
+				<input type="hidden" name="users" value="<?php echo implode(',', $user_ids) ?>" />
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_users['Message expiry subhead'] ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_users['Ban message label'] ?></th>
+									<td>
+										<input type="text" name="ban_message" size="50" maxlength="255" tabindex="1" />
+										<span><?php echo $lang_admin_users['Ban message help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_users['Expire date label'] ?></th>
+									<td>
+										<input type="text" name="ban_expire" size="17" maxlength="10" tabindex="2" />
+										<span><?php echo $lang_admin_users['Expire date help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_users['Ban IP label'] ?></th>
+									<td>
+										<input type="radio" name="ban_the_ip" tabindex="3" value="1" checked="checked" /> <strong><?php echo $lang_admin_common['Yes'] ?></strong>   <input type="radio" name="ban_the_ip" tabindex="4" value="0" checked="checked" /> <strong><?php echo $lang_admin_common['No'] ?></strong>
+										<span><?php echo $lang_admin_users['Ban IP help'] ?></span>
+									</td>
+								</tr>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+				<p class="submitend"><input type="submit" name="ban_users_comply" value="<?php echo $lang_admin_common['Save'] ?>" tabindex="3" /></p>
+			</form>
+		</div>
+	</div>
+	<div class="clearer"></div>
+</div>
+<?php
+
+	require PUN_ROOT.'footer.php';
+}
+
+
 else if (isset($_GET['find_user']))
 {
 	$form = isset($_GET['form']) ? $_GET['form'] : array();
@@ -356,8 +769,14 @@ else if (isset($_GET['find_user']))
 
 	// Generate paging links
 	$paging_links = '<span class="pages-label">'.$lang_common['Pages'].' </span>'.paginate($num_pages, $p, 'admin_users.php?find_user=&'.implode('&', $query_str));
+	
+	// Some helper variables for permissions
+	$can_delete = $can_move = $pun_user['g_id'] == PUN_ADMIN;
+	$can_ban = $pun_user['g_id'] == PUN_ADMIN || ($pun_user['g_moderator'] == '1' && $pun_user['g_mod_ban_users'] == '1');
+	$can_action = ($can_delete || $can_ban || $can_move) && $num_users > 0;
 
 	$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_admin_common['Admin'], $lang_admin_common['Users'], $lang_admin_users['Results head']);
+	$page_head = array('js' => '<script type="text/javascript" src="common.js"></script>');
 	define('PUN_ACTIVE_PAGE', 'admin');
 	require PUN_ROOT.'header.php';
 
@@ -377,6 +796,7 @@ else if (isset($_GET['find_user']))
 </div>
 
 
+<form id="search-users-form" action="admin_users.php" method="post">
 <div id="users2" class="blocktable">
 	<h2><span><?php echo $lang_admin_users['Results head'] ?></span></h2>
 	<div class="box">
@@ -390,6 +810,8 @@ else if (isset($_GET['find_user']))
 					<th class="tc4" scope="col"><?php echo $lang_admin_users['Results posts head'] ?></th>
 					<th class="tc5" scope="col"><?php echo $lang_admin_users['Results admin note head'] ?></th>
 					<th class="tcr" scope="col"><?php echo $lang_admin_users['Results actions head'] ?></th>
+<?php if ($can_action): ?>					<th class="tcmod" scope="col"><?php echo $lang_admin_users['Select'] ?></th>
+<?php endif; ?>
 				</tr>
 			</thead>
 			<tbody>
@@ -416,6 +838,8 @@ else if (isset($_GET['find_user']))
 					<td class="tc4"><?php echo forum_number_format($user_data['num_posts']) ?></td>
 					<td class="tc5"><?php echo ($user_data['admin_note'] != '') ? pun_htmlspecialchars($user_data['admin_note']) : ' ' ?></td>
 					<td class="tcr"><?php echo $actions ?></td>
+<?php if ($can_action): ?>					<td class="tcmod"><input type="checkbox" name="users[<?php echo $user_data['id'] ?>]" value="1" /></td>
+<?php endif; ?> 
 				</tr>
 <?php
 
@@ -435,6 +859,8 @@ else if (isset($_GET['find_user']))
 	<div class="inbox crumbsplus">
 		<div class="pagepost">
 			<p class="pagelink"><?php echo $paging_links ?></p>
+<?php if ($can_action): ?>			<p class="conr modbuttons"><a href="#" onclick="return select_checkboxes('search-users-form', this, '<?php echo $lang_admin_users['Unselect all'] ?>')"><?php echo $lang_admin_users['Select all'] ?></a> <?php if ($can_ban) : ?><input type="submit" name="ban_users" value="<?php echo $lang_admin_users['Ban'] ?>" /><?php endif; if ($can_delete) : ?><input type="submit" name="delete_users" value="<?php echo $lang_admin_users['Delete'] ?>" /><?php endif; if ($can_move) : ?><input type="submit" name="move_users" value="<?php echo $lang_admin_users['Change group'] ?>" /><?php endif; ?></p>
+<?php endif; ?>
 		</div>
 		<ul class="crumbs">
 			<li><a href="admin_index.php"><?php echo $lang_admin_common['Admin'].' '.$lang_admin_common['Index'] ?></a></li>
@@ -444,6 +870,7 @@ else if (isset($_GET['find_user']))
 		<div class="clearer"></div>
 	</div>
 </div>
+</form>
 <?php
 
 	require PUN_ROOT.'footer.php';
diff --git a/db_update.php b/db_update.php
index ff299a5..b15b777 100644
--- a/db_update.php
+++ b/db_update.php
@@ -7,9 +7,9 @@
  */
 
 // The FluxBB version this script updates to
-define('UPDATE_TO', '1.4.4');
+define('UPDATE_TO', '1.4.5');
 
-define('UPDATE_TO_DB_REVISION', 10);
+define('UPDATE_TO_DB_REVISION', 11);
 define('UPDATE_TO_SI_REVISION', 2);
 define('UPDATE_TO_PARSER_REVISION', 2);
 
@@ -174,9 +174,6 @@ if (!file_exists(PUN_ROOT.'style/'.$default_style.'.css'))
 // Start a session, used to queue up errors if duplicate users occur when converting from FluxBB v1.2.
 session_start();
 
-if (!isset($_SESSION['dupe_users']))
-	$_SESSION['dupe_users'] = array();
-
 //
 // Determines whether $str is UTF-8 encoded or not
 //
@@ -710,6 +707,10 @@ switch ($stage)
 		if (!array_key_exists('o_feed_type', $pun_config))
 			$db->query('INSERT INTO '.$db->prefix.'config (conf_name, conf_value) VALUES (\'o_feed_type\', \'2\')') or error('Unable to insert config value \'o_feed_type\'', __FILE__, __LINE__, $db->error());
 
+		// Insert new config option o_feed_ttl
+		if (!array_key_exists('o_feed_ttl', $pun_config))
+			$db->query('INSERT INTO '.$db->prefix.'config (conf_name, conf_value) VALUES (\'o_feed_ttl\', \'0\')') or error('Unable to insert config value \'o_feed_ttl\'', __FILE__, __LINE__, $db->error());
+
 		// Insert config option o_base_url which was removed in 1.3
 		if (!array_key_exists('o_base_url', $pun_config))
 		{
@@ -1398,6 +1399,9 @@ switch ($stage)
 	case 'conv_users':
 		$query_str = '?stage=preparse_posts';
 
+		if ($start_at == 0)
+			$_SESSION['dupe_users'] = array();
+
 		function _conv_users($cur_item, $old_charset)
 		{
 			global $lang_update;
@@ -1461,7 +1465,7 @@ switch ($stage)
 				else if (preg_match('/(?:\[\/?(?:b|u|s|ins|del|em|i|h|colou?r|quote|code|img|url|email|list|\*)\]|\[(?:img|url|quote|list)=)/i', $username))
 					$errors[$id][] = $lang_update['Username BBCode error'];
 
-				$result = $db->query('SELECT username FROM '.$db->prefix.'users WHERE (UPPER(username)=UPPER(\''.$db->escape($username).'\') OR UPPER(username)=UPPER(\''.$db->escape(preg_replace('/[^\p{L}\p{N}]/u', '', $username)).'\')) AND id>1') or error('Unable to fetch user info', __FILE__, __LINE__, $db->error());
+				$result = $db->query('SELECT username FROM '.$db->prefix.'users WHERE (UPPER(username)=UPPER(\''.$db->escape($username).'\') OR UPPER(username)=UPPER(\''.$db->escape(ucp_preg_replace('/[^\p{L}\p{N}]/u', '', $username)).'\')) AND id>1') or error('Unable to fetch user info', __FILE__, __LINE__, $db->error());
 
 				if ($db->num_rows($result))
 				{
@@ -1562,7 +1566,7 @@ switch ($stage)
 <div class="blockform">
 	<h2><span><?php echo $lang_update['Error converting users'] ?></span></h2>
 	<div class="box">
-		<form method="post" action="db_update.php?stage=conv_users_dupe">
+		<form method="post" action="db_update.php?stage=conv_users_dupe&uid=<?php echo $uid ?>">
 			<input type="hidden" name="form_sent" value="1" />
 			<div class="inform">
 				<div class="forminfo">
@@ -1580,7 +1584,7 @@ switch ($stage)
 				<fieldset>
 					<legend><?php echo pun_htmlspecialchars($cur_user['username']); ?></legend>
 					<div class="infldset">
-						<label class="required"><strong><?php echo $lang_update['New username'] ?> <span><?php echo $lang_update['required'] ?></span></strong><br /><input type="text" name="<?php echo 'dupe_users['.$id.']'; ?>" value="<?php if (isset($_POST['dupe_users'][$id])) echo pun_htmlspecialchars($_POST['dupe_users'][$id]); ?>" size="25" maxlength="25" /><br /></label>
+						<label class="required"><strong><?php echo $lang_update['New username'] ?> <span><?php echo $lang_update['Required'] ?></span></strong><br /><input type="text" name="<?php echo 'dupe_users['.$id.']'; ?>" value="<?php if (isset($_POST['dupe_users'][$id])) echo pun_htmlspecialchars($_POST['dupe_users'][$id]); ?>" size="25" maxlength="25" /><br /></label>
 					</div>
 				</fieldset>
 <?php if (!empty($errors[$id])): ?>				<div class="forminfo error-info">
diff --git a/edit.php b/edit.php
index 9d29456..1f35edd 100644
--- a/edit.php
+++ b/edit.php
@@ -61,8 +61,13 @@ if (isset($_POST['form_sent']))
 	{
 		$subject = pun_trim($_POST['req_subject']);
 
+		if ($pun_config['o_censoring'] == '1')
+			$censored_subject = pun_trim(censor_words($subject));
+
 		if ($subject == '')
 			$errors[] = $lang_post['No subject'];
+		else if ($pun_config['o_censoring'] == '1' && $censored_subject == '')
+			$errors[] = $lang_post['No subject after censoring'];
 		else if (pun_strlen($subject) > 70)
 			$errors[] = $lang_post['Too long subject'];
 		else if ($pun_config['p_subject_all_caps'] == '0' && is_all_uppercase($subject) && !$pun_user['is_admmod'])
@@ -85,8 +90,19 @@ if (isset($_POST['form_sent']))
 		$message = preparse_bbcode($message, $errors);
 	}
 
-	if ($message == '')
-		$errors[] = $lang_post['No message'];
+	if (empty($errors))
+	{
+		if ($message == '')
+			$errors[] = $lang_post['No message'];
+		else if ($pun_config['o_censoring'] == '1')
+		{
+			// Censor message to see if that causes problems
+			$censored_message = pun_trim(censor_words($message));
+
+			if ($censored_message == '')
+				$errors[] = $lang_post['No message after censoring'];
+		}
+	}
 
 	$hide_smilies = isset($_POST['hide_smilies']) ? '1' : '0';
 	$stick_topic = isset($_POST['stick_topic']) ? '1' : '0';
@@ -207,7 +223,7 @@ else if (isset($_POST['preview']))
 						<textarea name="req_message" rows="20" cols="95" tabindex="<?php echo $cur_index++ ?>"><?php echo pun_htmlspecialchars(isset($_POST['req_message']) ? $message : $cur_post['message']) ?></textarea><br /></label>
 						<ul class="bblinks">
 							<li><span><a href="help.php#bbcode" onclick="window.open(this.href); return false;"><?php echo $lang_common['BBCode'] ?></a> <?php echo ($pun_config['p_message_bbcode'] == '1') ? $lang_common['on'] : $lang_common['off']; ?></span></li>
-							<li><span><a href="help.php#img" onclick="window.open(this.href); return false;"><?php echo $lang_common['img tag'] ?></a> <?php echo ($pun_config['p_message_img_tag'] == '1') ? $lang_common['on'] : $lang_common['off']; ?></span></li>
+							<li><span><a href="help.php#img" onclick="window.open(this.href); return false;"><?php echo $lang_common['img tag'] ?></a> <?php echo ($pun_config['p_message_bbcode'] == '1' && $pun_config['p_message_img_tag'] == '1') ? $lang_common['on'] : $lang_common['off']; ?></span></li>
 							<li><span><a href="help.php#smilies" onclick="window.open(this.href); return false;"><?php echo $lang_common['Smilies'] ?></a> <?php echo ($pun_config['o_smilies'] == '1') ? $lang_common['on'] : $lang_common['off']; ?></span></li>
 						</ul>
 					</div>
diff --git a/extern.php b/extern.php
index e921889..364229e 100644
--- a/extern.php
+++ b/extern.php
@@ -119,8 +119,9 @@ function output_rss($feed)
 	header('Pragma: public');
 
 	echo '<?xml version="1.0" encoding="utf-8"?>'."\n";
-	echo '<rss version="2.0">'."\n";
+	echo '<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">'."\n";
 	echo "\t".'<channel>'."\n";
+	echo "\t\t".'<atom:link href="'.pun_htmlspecialchars(get_current_url()).'" rel="self" type="application/rss+xml" />'."\n";
 	echo "\t\t".'<title><![CDATA['.escape_cdata($feed['title']).']]></title>'."\n";
 	echo "\t\t".'<link>'.pun_htmlspecialchars($feed['link']).'</link>'."\n";
 	echo "\t\t".'<description><![CDATA['.escape_cdata($feed['description']).']]></description>'."\n";
@@ -381,46 +382,88 @@ if ($action == 'feed')
 				$forum_sql .= ' AND t.forum_id NOT IN('.implode(',', $nfids).')';
 		}
 
-		// Setup the feed
-		$feed = array(
-			'title' 		=>	$pun_config['o_board_title'].$forum_name,
-			'link'			=>	get_base_url(true).'/index.php',
-			'description'	=>	sprintf($lang_common['RSS description'], $pun_config['o_board_title']),
-			'items'			=>	array(),
-			'type'			=>	'topics'
-		);
-
-		// Fetch $show topics
-		$result = $db->query('SELECT t.id, t.poster, t.subject, t.posted, t.last_post, t.last_poster, p.message, p.hide_smilies, u.email_setting, u.email, p.poster_id, p.poster_email FROM '.$db->prefix.'topics AS t INNER JOIN '.$db->prefix.'posts AS p ON p.id='.($order_posted ? 't.first_post_id' : 't.last_post_id').' INNER JOIN '.$db->prefix.'users AS u ON u.id=p.poster_id LEFT JOIN '.$db->prefix.'forum_perms AS fp ON (fp.forum_id=t.forum_id AND fp.group_id='.$pun_user['g_id'].') WHERE (fp.read_forum IS NULL OR fp.read_forum=1) AND t.moved_to IS NULL'.$forum_sql.' ORDER BY '.($order_posted ? 't.posted' : 't.last_post').' DESC LIMIT '.$show) or error('Unable to fetch topic info', __FILE__, __LINE__, $db->error());
-		while ($cur_topic = $db->fetch_assoc($result))
-		{
-			if ($pun_config['o_censoring'] == '1')
-				$cur_topic['subject'] = censor_words($cur_topic['subject']);
+		// Only attempt to cache if caching is enabled and we have all or a single forum
+		if ($pun_config['o_feed_ttl'] > 0 && ($forum_sql == '' || ($forum_name != '' && !isset($_GET['nfid']))))
+			$cache_id = 'feed'.sha1($pun_user['g_id'].'|'.$lang_common['lang_identifier'].'|'.($order_posted ? '1' : '0').($forum_name == '' ? '' : '|'.$fids[0]));
 
-			$cur_topic['message'] = parse_message($cur_topic['message'], $cur_topic['hide_smilies']);
+		// Load cached feed
+		if (isset($cache_id) && file_exists(FORUM_CACHE_DIR.'cache_'.$cache_id.'.php'))
+			include FORUM_CACHE_DIR.'cache_'.$cache_id.'.php';
 
-			$item = array(
-				'id'			=>	$cur_topic['id'],
-				'title'			=>	$cur_topic['subject'],
-				'link'			=>	get_base_url(true).'/viewtopic.php?id='.$cur_topic['id'].($order_posted ? '' : '&action=new'),
-				'description'	=>	$cur_topic['message'],
-				'author'		=>	array(
-					'name'	=> $order_posted ? $cur_topic['poster'] : $cur_topic['last_poster']
-				),
-				'pubdate'		=>	$order_posted ? $cur_topic['posted'] : $cur_topic['last_post']
+		$now = time();
+		if (!isset($feed) || $cache_expire < $now)
+		{
+			// Setup the feed
+			$feed = array(
+				'title' 		=>	$pun_config['o_board_title'].$forum_name,
+				'link'			=>	'/index.php',
+				'description'	=>	sprintf($lang_common['RSS description'], $pun_config['o_board_title']),
+				'items'			=>	array(),
+				'type'			=>	'topics'
 			);
 
-			if ($cur_topic['poster_id'] > 1)
+			// Fetch $show topics
+			$result = $db->query('SELECT t.id, t.poster, t.subject, t.posted, t.last_post, t.last_poster, p.message, p.hide_smilies, u.email_setting, u.email, p.poster_id, p.poster_email FROM '.$db->prefix.'topics AS t INNER JOIN '.$db->prefix.'posts AS p ON p.id='.($order_posted ? 't.first_post_id' : 't.last_post_id').' INNER JOIN '.$db->prefix.'users AS u ON u.id=p.poster_id LEFT JOIN '.$db->prefix.'forum_perms AS fp ON (fp.forum_id=t.forum_id AND fp.group_id='.$pun_user['g_id'].') WHERE (fp.read_forum IS NULL OR fp.read_forum=1) AND t.moved_to IS NULL'.$forum_sql.' ORDER BY '.($order_posted ? 't.posted' : 't.last_post').' DESC LIMIT '.(isset($cache_id) ? 50 : $show)) or error('Unable to fetch topic info', __FILE__, __LINE__, $db->error());
+			while ($cur_topic = $db->fetch_assoc($result))
+			{
+				if ($pun_config['o_censoring'] == '1')
+					$cur_topic['subject'] = censor_words($cur_topic['subject']);
+
+				$cur_topic['message'] = parse_message($cur_topic['message'], $cur_topic['hide_smilies']);
+
+				$item = array(
+					'id'			=>	$cur_topic['id'],
+					'title'			=>	$cur_topic['subject'],
+					'link'			=>	'/viewtopic.php?id='.$cur_topic['id'].($order_posted ? '' : '&action=new'),
+					'description'	=>	$cur_topic['message'],
+					'author'		=>	array(
+						'name'	=> $order_posted ? $cur_topic['poster'] : $cur_topic['last_poster']
+					),
+					'pubdate'		=>	$order_posted ? $cur_topic['posted'] : $cur_topic['last_post']
+				);
+
+				if ($cur_topic['poster_id'] > 1)
+				{
+					if ($cur_topic['email_setting'] == '0' && !$pun_user['is_guest'])
+						$item['author']['email'] = $cur_topic['email'];
+
+					$item['author']['uri'] = '/profile.php?id='.$cur_topic['poster_id'];
+				}
+				else if ($cur_topic['poster_email'] != '' && !$pun_user['is_guest'])
+					$item['author']['email'] = $cur_topic['poster_email'];
+
+				$feed['items'][] = $item;
+			}
+
+			// Output feed as PHP code
+			if (isset($cache_id))
 			{
-				if ($cur_topic['email_setting'] == '0' && !$pun_user['is_guest'])
-					$item['author']['email'] = $cur_topic['email'];
+				$fh = @fopen(FORUM_CACHE_DIR.'cache_'.$cache_id.'.php', 'wb');
+				if (!$fh)
+					error('Unable to write feed cache file to cache directory. Please make sure PHP has write access to the directory \''.pun_htmlspecialchars(FORUM_CACHE_DIR).'\'', __FILE__, __LINE__);
+
+				fwrite($fh, '<?php'."\n\n".'$feed = '.var_export($feed, true).';'."\n\n".'$cache_expire = '.($now + ($pun_config['o_feed_ttl'] * 60)).';'."\n\n".'?>');
+
+				fclose($fh);
 
-				$item['author']['uri'] = get_base_url(true).'/profile.php?id='.$cur_topic['poster_id'];
+				if (function_exists('apc_delete_file'))
+					@apc_delete_file(FORUM_CACHE_DIR.'cache_'.$cache_id.'.php');
 			}
-			else if ($cur_topic['poster_email'] != '' && !$pun_user['is_guest'])
-				$item['author']['email'] = $cur_topic['poster_email'];
+		}
 
-			$feed['items'][] = $item;
+		// If we only want to show a few items but due to caching we have too many
+		if (count($feed['items']) > $show)
+			$feed['items'] = array_slice($feed['items'], 0, $show);
+
+		// Prepend the current base URL onto some links. Done after caching to handle http/https correctly
+		$feed['link'] = get_base_url(true).$feed['link'];
+
+		foreach ($feed['items'] as $key => $item)
+		{
+			$feed['items'][$key]['link'] = get_base_url(true).$item['link'];
+
+			if (isset($item['author']['uri']))
+				$feed['items'][$key]['author']['uri'] = get_base_url(true).$item['author']['uri'];
 		}
 
 		$output_func = 'output_'.$type;
@@ -476,11 +519,17 @@ else if ($action == 'stats')
 	require PUN_ROOT.'lang/'.$pun_config['o_default_lang'].'/index.php';
 
 	// Collect some statistics from the database
-	$result = $db->query('SELECT COUNT(id)-1 FROM '.$db->prefix.'users WHERE group_id!='.PUN_UNVERIFIED) or error('Unable to fetch total user count', __FILE__, __LINE__, $db->error());
-	$stats['total_users'] = $db->result($result);
+	if (file_exists(FORUM_CACHE_DIR.'cache_users_info.php'))
+		include FORUM_CACHE_DIR.'cache_users_info.php';
 
-	$result = $db->query('SELECT id, username FROM '.$db->prefix.'users WHERE group_id!='.PUN_UNVERIFIED.' ORDER BY registered DESC LIMIT 1') or error('Unable to fetch newest registered user', __FILE__, __LINE__, $db->error());
-	$stats['last_user'] = $db->fetch_assoc($result);
+	if (!defined('PUN_USERS_INFO_LOADED'))
+	{
+		if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+			require PUN_ROOT.'include/cache.php';
+
+		generate_users_info_cache();
+		require FORUM_CACHE_DIR.'cache_users_info.php';
+	}
 
 	$result = $db->query('SELECT SUM(num_topics), SUM(num_posts) FROM '.$db->prefix.'forums') or error('Unable to fetch topic/post count', __FILE__, __LINE__, $db->error());
 	list($stats['total_topics'], $stats['total_posts']) = $db->fetch_row($result);
diff --git a/header.php b/header.php
index ed7b0e4..0f300db 100644
--- a/header.php
+++ b/header.php
@@ -180,7 +180,49 @@ $tpl_main = str_replace('<pun_desc>', '<div id="brddesc">'.$pun_config['o_board_
 
 
 // START SUBST - <pun_navlinks>
-$tpl_main = str_replace('<pun_navlinks>','<div id="brdmenu" class="inbox">'."\n\t\t\t". generate_navlinks()."\n\t\t".'</div>', $tpl_main);
+$links = array();
+
+// Index should always be displayed
+$links[] = '<li id="navindex"'.((PUN_ACTIVE_PAGE == 'index') ? ' class="isactive"' : '').'><a href="index.php">'.$lang_common['Index'].'</a></li>';
+
+if ($pun_user['g_read_board'] == '1' && $pun_user['g_view_users'] == '1')
+	$links[] = '<li id="navuserlist"'.((PUN_ACTIVE_PAGE == 'userlist') ? ' class="isactive"' : '').'><a href="userlist.php">'.$lang_common['User list'].'</a></li>';
+
+if ($pun_config['o_rules'] == '1' && (!$pun_user['is_guest'] || $pun_user['g_read_board'] == '1' || $pun_config['o_regs_allow'] == '1'))
+	$links[] = '<li id="navrules"'.((PUN_ACTIVE_PAGE == 'rules') ? ' class="isactive"' : '').'><a href="misc.php?action=rules">'.$lang_common['Rules'].'</a></li>';
+
+if ($pun_user['g_read_board'] == '1' && $pun_user['g_search'] == '1')
+	$links[] = '<li id="navsearch"'.((PUN_ACTIVE_PAGE == 'search') ? ' class="isactive"' : '').'><a href="search.php">'.$lang_common['Search'].'</a></li>';
+
+if ($pun_user['is_guest'])
+{
+	$links[] = '<li id="navregister"'.((PUN_ACTIVE_PAGE == 'register') ? ' class="isactive"' : '').'><a href="register.php">'.$lang_common['Register'].'</a></li>';
+	$links[] = '<li id="navlogin"'.((PUN_ACTIVE_PAGE == 'login') ? ' class="isactive"' : '').'><a href="login.php">'.$lang_common['Login'].'</a></li>';
+}
+else
+{
+	$links[] = '<li id="navprofile"'.((PUN_ACTIVE_PAGE == 'profile') ? ' class="isactive"' : '').'><a href="profile.php?id='.$pun_user['id'].'">'.$lang_common['Profile'].'</a></li>';
+
+	if ($pun_user['is_admmod'])
+		$links[] = '<li id="navadmin"'.((PUN_ACTIVE_PAGE == 'admin') ? ' class="isactive"' : '').'><a href="admin_index.php">'.$lang_common['Admin'].'</a></li>';
+
+	$links[] = '<li id="navlogout"><a href="login.php?action=out&id='.$pun_user['id'].'&csrf_token='.pun_hash($pun_user['id'].pun_hash(get_remote_address())).'">'.$lang_common['Logout'].'</a></li>';
+}
+
+// Are there any additional navlinks we should insert into the array before imploding it?
+if ($pun_user['g_read_board'] == '1' && $pun_config['o_additional_navlinks'] != '')
+{
+	if (preg_match_all('#([0-9]+)\s*=\s*(.*?)\n#s', $pun_config['o_additional_navlinks']."\n", $extra_links))
+	{
+		// Insert any additional links into the $links array (at the correct index)
+		$num_links = count($extra_links[1]);
+		for ($i = 0; $i < $num_links; ++$i)
+			array_splice($links, $extra_links[1][$i], 0, array('<li id="navextra'.($i + 1).'">'.$extra_links[2][$i].'</li>'));
+	}
+}
+
+$tpl_temp = '<div id="brdmenu" class="inbox">'."\n\t\t\t".'<ul>'."\n\t\t\t\t".implode("\n\t\t\t\t", $links)."\n\t\t\t".'</ul>'."\n\t\t".'</div>';
+$tpl_main = str_replace('<pun_navlinks>', $tpl_temp, $tpl_main);
 // END SUBST - <pun_navlinks>
 
 
@@ -237,7 +279,7 @@ else
 	$tpl_temp .= "\n\t\t\t".$page_statusinfo;
 
 // Generate quicklinks
-if (count($page_topicsearches))
+if (!empty($page_topicsearches))
 {
 	$tpl_temp .= "\n\t\t\t".'<ul class="conr">';
 	$tpl_temp .= "\n\t\t\t\t".'<li><span>'.$lang_common['Topic searches'].' '.implode(' | ', $page_topicsearches).'</span></li>';
diff --git a/include/cache.php b/include/cache.php
index 89f6b73..99c2487 100644
--- a/include/cache.php
+++ b/include/cache.php
@@ -133,7 +133,7 @@ function generate_quickjump_cache($group_id = false)
 
 		if ($read_board == '1')
 		{
-			$result = $db->query('SELECT c.id AS cid, c.cat_name, f.id AS fid, f.forum_name, f.redirect_url FROM '.$db->prefix.'categories AS c INNER JOIN '.$db->prefix.'forums AS f ON c.id=f.cat_id LEFT JOIN '.$db->prefix.'forum_perms AS fp ON (fp.forum_id=f.id AND fp.group_id='.$group_id.') WHERE fp.read_forum IS NULL OR fp.read_forum=1 ORDER BY c.disp_position, c.id, f.disp_position', true) or error('Unable to fetch category/forum list', __FILE__, __LINE__, $db->error());
+			$result = $db->query('SELECT c.id AS cid, c.cat_name, f.id AS fid, f.forum_name, f.redirect_url FROM '.$db->prefix.'categories AS c INNER JOIN '.$db->prefix.'forums AS f ON c.id=f.cat_id LEFT JOIN '.$db->prefix.'forum_perms AS fp ON (fp.forum_id=f.id AND fp.group_id='.$group_id.') WHERE fp.read_forum IS NULL OR fp.read_forum=1 ORDER BY c.disp_position, c.id, f.disp_position') or error('Unable to fetch category/forum list', __FILE__, __LINE__, $db->error());
 
 			if ($db->num_rows($result))
 			{
@@ -227,7 +227,7 @@ function generate_stopwords_cache()
 	if (!$fh)
 		error('Unable to write stopwords cache file to cache directory. Please make sure PHP has write access to the directory \''.pun_htmlspecialchars(FORUM_CACHE_DIR).'\'', __FILE__, __LINE__);
 
-	fwrite($fh, '<?php'."\n\n".'define(\'PUN_STOPWORDS_LOADED\', 1);'."\n\n".'$stopwords = '.var_export($stopwords, true).';'."\n\n".'?>');
+	fwrite($fh, '<?php'."\n\n".'$cache_id = \''.generate_stopwords_cache_id().'\';'."\n".'if ($cache_id != generate_stopwords_cache_id()) return;'."\n\n".'define(\'PUN_STOPWORDS_LOADED\', 1);'."\n\n".'$stopwords = '.var_export($stopwords, true).';'."\n\n".'?>');
 
 	fclose($fh);
 
@@ -236,4 +236,48 @@ function generate_stopwords_cache()
 }
 
 
+//
+// Load some information about the latest registered users
+//
+function generate_users_info_cache()
+{
+	global $db;
+
+	$stats = array();
+
+	$result = $db->query('SELECT COUNT(id)-1 FROM '.$db->prefix.'users WHERE group_id!='.PUN_UNVERIFIED) or error('Unable to fetch total user count', __FILE__, __LINE__, $db->error());
+	$stats['total_users'] = $db->result($result);
+
+	$result = $db->query('SELECT id, username FROM '.$db->prefix.'users WHERE group_id!='.PUN_UNVERIFIED.' ORDER BY registered DESC LIMIT 1') or error('Unable to fetch newest registered user', __FILE__, __LINE__, $db->error());
+	$stats['last_user'] = $db->fetch_assoc($result);
+
+	// Output users info as PHP code
+	$fh = @fopen(FORUM_CACHE_DIR.'cache_users_info.php', 'wb');
+	if (!$fh)
+		error('Unable to write users info cache file to cache directory. Please make sure PHP has write access to the directory \''.pun_htmlspecialchars(FORUM_CACHE_DIR).'\'', __FILE__, __LINE__);
+
+	fwrite($fh, '<?php'."\n\n".'define(\'PUN_USERS_INFO_LOADED\', 1);'."\n\n".'$stats = '.var_export($stats, true).';'."\n\n".'?>');
+
+	fclose($fh);
+
+	if (function_exists('apc_delete_file'))
+		@apc_delete_file(FORUM_CACHE_DIR.'cache_users_info.php');
+}
+
+
+//
+// Delete all feed caches
+//
+function clear_feed_cache()
+{
+	$d = dir(FORUM_CACHE_DIR);
+	while (($entry = $d->read()) !== false)
+	{
+		if (substr($entry, 0, 10) == 'cache_feed' && substr($entry, -4) == '.php')
+			@unlink(FORUM_CACHE_DIR.$entry);
+	}
+	$d->close();
+}
+
+
 define('FORUM_CACHE_FUNCTIONS_LOADED', true);
diff --git a/include/common.php b/include/common.php
index 5f0fb8d..371b972 100644
--- a/include/common.php
+++ b/include/common.php
@@ -10,9 +10,9 @@ if (!defined('PUN_ROOT'))
 	exit('The constant PUN_ROOT must be defined and point to a valid FluxBB installation root directory.');
 
 // Define the version and database revision that this code was written for
-define('FORUM_VERSION', '1.4.4');
+define('FORUM_VERSION', '1.4.5');
 
-define('FORUM_DB_REVISION', 10);
+define('FORUM_DB_REVISION', 11);
 define('FORUM_SI_REVISION', 2);
 define('FORUM_PARSER_REVISION', 2);
 
@@ -190,3 +190,6 @@ if (!defined('PUN_SEARCH_MIN_WORD'))
 	define('PUN_SEARCH_MIN_WORD', 3);
 if (!defined('PUN_SEARCH_MAX_WORD'))
 	define('PUN_SEARCH_MAX_WORD', 20);
+
+if (!defined('FORUM_MAX_COOKIE_SIZE'))
+	define('FORUM_MAX_COOKIE_SIZE', 4048);
diff --git a/include/common_admin.php b/include/common_admin.php
index 98fc797..5ce1319 100644
--- a/include/common_admin.php
+++ b/include/common_admin.php
@@ -33,28 +33,42 @@ function generate_admin_menu($page = '')
 ?>
 <div id="adminconsole" class="block2col">
 	<div id="adminmenu" class="blockmenu">
-		<h2><span><?php echo ($is_admin) ? $lang_admin_common['Admin menu'] : $lang_admin_common['Moderator menu'] ?></span></h2>
+		<h2><span><?php echo $lang_admin_common['Moderator menu'] ?></span></h2>
 		<div class="box">
 			<div class="inbox">
 				<ul>
 					<li<?php if ($page == 'index') echo ' class="isactive"'; ?>><a href="admin_index.php"><?php echo $lang_admin_common['Index'] ?></a></li>
-<?php if ($is_admin): ?>					<li<?php if ($page == 'categories') echo ' class="isactive"'; ?>><a href="admin_categories.php"><?php echo $lang_admin_common['Categories'] ?></a></li>
-<?php endif; ?><?php if ($is_admin): ?>					<li<?php if ($page == 'forums') echo ' class="isactive"'; ?>><a href="admin_forums.php"><?php echo $lang_admin_common['Forums'] ?></a></li>
-<?php endif; ?>					<li<?php if ($page == 'users') echo ' class="isactive"'; ?>><a href="admin_users.php"><?php echo $lang_admin_common['Users'] ?></a></li>
-<?php if ($is_admin): ?>					<li<?php if ($page == 'groups') echo ' class="isactive"'; ?>><a href="admin_groups.php"><?php echo $lang_admin_common['User groups'] ?></a></li>
-<?php endif; ?><?php if ($is_admin): ?>					<li<?php if ($page == 'options') echo ' class="isactive"'; ?>><a href="admin_options.php"><?php echo $lang_admin_common['Options'] ?></a></li>
-<?php endif; ?><?php if ($is_admin): ?>					<li<?php if ($page == 'permissions') echo ' class="isactive"'; ?>><a href="admin_permissions.php"><?php echo $lang_admin_common['Permissions'] ?></a></li>
-<?php endif; ?><?php if ($is_admin || $pun_config['o_censoring'] == '1'): ?>					<li<?php if ($page == 'censoring') echo ' class="isactive"'; ?>><a href="admin_censoring.php"><?php echo $lang_admin_common['Censoring'] ?></a></li>
-<?php endif; ?><?php if ($is_admin): ?>					<li<?php if ($page == 'ranks') echo ' class="isactive"'; ?>><a href="admin_ranks.php"><?php echo $lang_admin_common['Ranks'] ?></a></li>
-<?php endif; ?><?php if ($is_admin || $pun_user['g_mod_ban_users'] == '1'): ?>					<li<?php if ($page == 'bans') echo ' class="isactive"'; ?>><a href="admin_bans.php"><?php echo $lang_admin_common['Bans'] ?></a></li>
-<?php endif; ?><?php if ($is_admin): ?>					<li<?php if ($page == 'prune') echo ' class="isactive"'; ?>><a href="admin_prune.php"><?php echo $lang_admin_common['Prune'] ?></a></li>
-<?php endif; ?><?php if ($is_admin): ?>					<li<?php if ($page == 'maintenance') echo ' class="isactive"'; ?>><a href="admin_maintenance.php"><?php echo $lang_admin_common['Maintenance'] ?></a></li>
-<?php endif; ?>					<li<?php if ($page == 'reports') echo ' class="isactive"'; ?>><a href="admin_reports.php"><?php echo $lang_admin_common['Reports'] ?></a></li>
+					<li<?php if ($page == 'users') echo ' class="isactive"'; ?>><a href="admin_users.php"><?php echo $lang_admin_common['Users'] ?></a></li>
+<?php if ($is_admin || $pun_user['g_mod_ban_users'] == '1'): ?>					<li<?php if ($page == 'bans') echo ' class="isactive"'; ?>><a href="admin_bans.php"><?php echo $lang_admin_common['Bans'] ?></a></li>
+<?php endif; if ($is_admin || $pun_config['o_report_method'] == '0' || $pun_config['o_report_method'] == '2'): ?>					<li<?php if ($page == 'reports') echo ' class="isactive"'; ?>><a href="admin_reports.php"><?php echo $lang_admin_common['Reports'] ?></a></li>
+<?php endif; ?>				</ul>
+			</div>
+		</div>
+<?php
+
+	if ($is_admin)
+	{
+
+?>
+		<h2 class="block2"><span><?php echo $lang_admin_common['Admin menu'] ?></span></h2>
+		<div class="box">
+			<div class="inbox">
+				<ul>
+					<li<?php if ($page == 'options') echo ' class="isactive"'; ?>><a href="admin_options.php"><?php echo $lang_admin_common['Options'] ?></a></li>
+					<li<?php if ($page == 'permissions') echo ' class="isactive"'; ?>><a href="admin_permissions.php"><?php echo $lang_admin_common['Permissions'] ?></a></li>
+					<li<?php if ($page == 'categories') echo ' class="isactive"'; ?>><a href="admin_categories.php"><?php echo $lang_admin_common['Categories'] ?></a></li>
+					<li<?php if ($page == 'forums') echo ' class="isactive"'; ?>><a href="admin_forums.php"><?php echo $lang_admin_common['Forums'] ?></a></li>
+					<li<?php if ($page == 'groups') echo ' class="isactive"'; ?>><a href="admin_groups.php"><?php echo $lang_admin_common['User groups'] ?></a></li>
+					<li<?php if ($page == 'censoring') echo ' class="isactive"'; ?>><a href="admin_censoring.php"><?php echo $lang_admin_common['Censoring'] ?></a></li>
+					<li<?php if ($page == 'ranks') echo ' class="isactive"'; ?>><a href="admin_ranks.php"><?php echo $lang_admin_common['Ranks'] ?></a></li>
+					<li<?php if ($page == 'maintenance') echo ' class="isactive"'; ?>><a href="admin_maintenance.php"><?php echo $lang_admin_common['Maintenance'] ?></a></li>
 				</ul>
 			</div>
 		</div>
 <?php
 
+	}
+
 	// See if there are any plugins
 	$plugins = forum_list_plugins($is_admin);
 
diff --git a/include/dblayer/sqlite.php b/include/dblayer/sqlite.php
index 422177c..cf6d4c2 100644
--- a/include/dblayer/sqlite.php
+++ b/include/dblayer/sqlite.php
@@ -182,7 +182,7 @@ class DBLayer
 
 	function affected_rows()
 	{
-		return ($this->query_result) ? @sqlite_changes($this->query_result) : false;
+		return ($this->link_id) ? @sqlite_changes($this->link_id) : false;
 	}
 
 
diff --git a/include/email.php b/include/email.php
index cd7f48d..376172d 100644
--- a/include/email.php
+++ b/include/email.php
@@ -10,6 +10,7 @@
 if (!defined('PUN'))
 	exit;
 
+require PUN_ROOT.'include/utf8/utils/ascii.php';
 
 //
 // Validate an email address
@@ -43,6 +44,18 @@ function is_banned_email($email)
 
 
 //
+// Only encode with base64, if there is at least one unicode character in the string
+//
+function encode_mail_text($str)
+{
+	if (utf8_is_ascii($str))
+		return $str;
+
+	return '=?UTF-8?B?'.base64_encode($str).'?=';
+}
+
+
+//
 // Wrapper for PHP's mail()
 //
 function pun_mail($to, $subject, $message, $reply_to_email = '', $reply_to_name = '')
@@ -62,15 +75,15 @@ function pun_mail($to, $subject, $message, $reply_to_email = '', $reply_to_name
 	$reply_to_name = pun_trim(preg_replace('#[\n\r:]+#s', '', str_replace('"', '', $reply_to_name)));
 
 	// Set up some headers to take advantage of UTF-8
-	$from = "=?UTF-8?B?".base64_encode($from_name)."?=".' <'.$from_email.'>';
-	$subject = "=?UTF-8?B?".base64_encode($subject)."?=";
+	$from = encode_mail_text($from_name).' <'.$from_email.'>';
+	$subject = encode_mail_text($subject);
 
 	$headers = 'From: '.$from."\r\n".'Date: '.gmdate('r')."\r\n".'MIME-Version: 1.0'."\r\n".'Content-transfer-encoding: 8bit'."\r\n".'Content-type: text/plain; charset=utf-8'."\r\n".'X-Mailer: FluxBB Mailer';
 
 	// If we specified a reply-to email, we deal with it here
 	if (!empty($reply_to_email))
 	{
-		$reply_to = "=?UTF-8?B?".base64_encode($reply_to_name)."?=".' <'.$reply_to_email.'>';
+		$reply_to = encode_mail_text($reply_to_name).' <'.$reply_to_email.'>';
 
 		$headers .= "\r\n".'Reply-To: '.$reply_to;
 	}
diff --git a/include/functions.php b/include/functions.php
index 27ef686..7979d4a 100644
--- a/include/functions.php
+++ b/include/functions.php
@@ -174,10 +174,10 @@ function authenticate_user($user, $password, $password_is_hash = false)
 //
 function get_current_url($max_length = 0)
 {
-	$protocol = (!isset($_SERVER['HTTPS']) || strtolower($_SERVER['HTTPS']) == 'off') ? 'http://' : 'https://';
-	$port = (isset($_SERVER['SERVER_PORT']) && (($_SERVER['SERVER_PORT'] != '80' && $protocol == 'http://') || ($_SERVER['SERVER_PORT'] != '443' && $protocol == 'https://')) && strpos($_SERVER['HTTP_HOST'], ':') === false) ? ':'.$_SERVER['SERVER_PORT'] : '';
+	$protocol = get_current_protocol();
+	$port = (isset($_SERVER['SERVER_PORT']) && (($_SERVER['SERVER_PORT'] != '80' && $protocol == 'http') || ($_SERVER['SERVER_PORT'] != '443' && $protocol == 'https')) && strpos($_SERVER['HTTP_HOST'], ':') === false) ? ':'.$_SERVER['SERVER_PORT'] : '';
 
-	$url = urldecode($protocol.$_SERVER['HTTP_HOST'].$port.$_SERVER['REQUEST_URI']);
+	$url = urldecode($protocol.'://'.$_SERVER['HTTP_HOST'].$port.$_SERVER['REQUEST_URI']);
 
 	if (strlen($url) <= $max_length || $max_length == 0)
 		return $url;
@@ -188,6 +188,32 @@ function get_current_url($max_length = 0)
 
 
 //
+// Fetch the current protocol in use - http or https
+//
+function get_current_protocol()
+{
+	$protocol = 'http';
+
+	// Check if the server is claiming to using HTTPS
+	if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
+		$protocol = 'https';
+
+	// If we are behind a reverse proxy try to decide which protocol it is using
+	if (defined('FORUM_BEHIND_REVERSE_PROXY'))
+	{
+		// Check if we are behind a Microsoft based reverse proxy
+		if (!empty($_SERVER['HTTP_FRONT_END_HTTPS']) && strtolower($_SERVER['HTTP_FRONT_END_HTTPS']) != 'off')
+			$protocol = 'https';
+
+		// Check if we're behind a "proper" reverse proxy, and what protocol it's using
+		if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']))
+			$protocol = strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']);
+	}
+
+	return $protocol;
+}
+
+//
 // Fetch the base_url, optionally support HTTPS and HTTP
 //
 function get_base_url($support_https = false)
@@ -201,8 +227,7 @@ function get_base_url($support_https = false)
 	if (!isset($base_url))
 	{
 		// Make sure we are using the correct protocol
-		$protocol = (!isset($_SERVER['HTTPS']) || strtolower($_SERVER['HTTPS']) == 'off') ? 'http://' : 'https://';
-		$base_url = str_replace(array('http://', 'https://'), $protocol, $pun_config['o_base_url']);
+		$base_url = str_replace(array('http://', 'https://'), get_current_protocol().'://', $pun_config['o_base_url']);
 	}
 
 	return $base_url;
@@ -270,7 +295,7 @@ function forum_hmac($data, $key, $raw_output = false)
 
 	// If key size more than blocksize then we hash it once
 	if (strlen($key) > 64)
-		$key = sha1($key, true); // we have to use raw output here to match the standard
+		$key = pack('H*', sha1($key)); // we have to use raw output here to match the standard
 
 	// Ensure we're padded to exactly one block boundary
 	$key = str_pad($key, 64, chr(0x00));
@@ -285,7 +310,13 @@ function forum_hmac($data, $key, $raw_output = false)
 	}
 
 	// Finally, calculate the HMAC
-	return sha1($hmac_opad.sha1($hmac_ipad.$data, true), $raw_output);
+	$hash = sha1($hmac_opad.pack('H*', sha1($hmac_ipad.$data)));
+
+	// If we want raw output then we need to pack the final result
+	if ($raw_output)
+		$hash = pack('H*', $hash);
+
+	return $hash;
 }
 
 
@@ -325,8 +356,8 @@ function check_bans()
 {
 	global $db, $pun_config, $lang_common, $pun_user, $pun_bans;
 
-	// Admins aren't affected
-	if ($pun_user['g_id'] == PUN_ADMIN || !$pun_bans)
+	// Admins and moderators aren't affected
+	if ($pun_user['is_admmod'] || !$pun_bans)
 		return;
 
 	// Add a dot or a colon (depending on IPv4/IPv6) at the end of the IP address to prevent banned address
@@ -420,7 +451,7 @@ function check_username($username, $exclude_id = null)
 	// Check that the username (or a too similar username) is not already registered
 	$query = ($exclude_id) ? ' AND id!='.$exclude_id : '';
 
-	$result = $db->query('SELECT username FROM '.$db->prefix.'users WHERE (UPPER(username)=UPPER(\''.$db->escape($username).'\') OR UPPER(username)=UPPER(\''.$db->escape(preg_replace('/[^\p{L}\p{N}]/u', '', $username)).'\')) AND id>1'.$query) or error('Unable to fetch user info', __FILE__, __LINE__, $db->error());
+	$result = $db->query('SELECT username FROM '.$db->prefix.'users WHERE (UPPER(username)=UPPER(\''.$db->escape($username).'\') OR UPPER(username)=UPPER(\''.$db->escape(ucp_preg_replace('/[^\p{L}\p{N}]/u', '', $username)).'\')) AND id>1'.$query) or error('Unable to fetch user info', __FILE__, __LINE__, $db->error());
 
 	if ($db->num_rows($result))
 	{
@@ -472,65 +503,6 @@ function update_users_online()
 
 
 //
-// Generate the "navigator" that appears at the top of every page
-//
-function generate_navlinks()
-{
-	global $pun_config, $lang_common, $pun_user;
-
-	// Index and Userlist should always be displayed
-	$links[] = '<li id="navindex"'.((PUN_ACTIVE_PAGE == 'index') ? ' class="isactive"' : '').'><a href="index.php">'.$lang_common['Index'].'</a></li>';
-
-	if ($pun_user['g_read_board'] == '1' && $pun_user['g_view_users'] == '1')
-		$links[] = '<li id="navuserlist"'.((PUN_ACTIVE_PAGE == 'userlist') ? ' class="isactive"' : '').'><a href="userlist.php">'.$lang_common['User list'].'</a></li>';
-
-	if ($pun_config['o_rules'] == '1' && (!$pun_user['is_guest'] || $pun_user['g_read_board'] == '1' || $pun_config['o_regs_allow'] == '1'))
-		$links[] = '<li id="navrules"'.((PUN_ACTIVE_PAGE == 'rules') ? ' class="isactive"' : '').'><a href="misc.php?action=rules">'.$lang_common['Rules'].'</a></li>';
-
-	if ($pun_user['is_guest'])
-	{
-		if ($pun_user['g_read_board'] == '1' && $pun_user['g_search'] == '1')
-			$links[] = '<li id="navsearch"'.((PUN_ACTIVE_PAGE == 'search') ? ' class="isactive"' : '').'><a href="search.php">'.$lang_common['Search'].'</a></li>';
-
-		$links[] = '<li id="navregister"'.((PUN_ACTIVE_PAGE == 'register') ? ' class="isactive"' : '').'><a href="register.php">'.$lang_common['Register'].'</a></li>';
-		$links[] = '<li id="navlogin"'.((PUN_ACTIVE_PAGE == 'login') ? ' class="isactive"' : '').'><a href="login.php">'.$lang_common['Login'].'</a></li>';
-	}
-	else
-	{
-		if (!$pun_user['is_admmod'])
-		{
-			if ($pun_user['g_read_board'] == '1' && $pun_user['g_search'] == '1')
-				$links[] = '<li id="navsearch"'.((PUN_ACTIVE_PAGE == 'search') ? ' class="isactive"' : '').'><a href="search.php">'.$lang_common['Search'].'</a></li>';
-
-			$links[] = '<li id="navprofile"'.((PUN_ACTIVE_PAGE == 'profile') ? ' class="isactive"' : '').'><a href="profile.php?id='.$pun_user['id'].'">'.$lang_common['Profile'].'</a></li>';
-			$links[] = '<li id="navlogout"><a href="login.php?action=out&id='.$pun_user['id'].'&csrf_token='.pun_hash($pun_user['id'].pun_hash(get_remote_address())).'">'.$lang_common['Logout'].'</a></li>';
-		}
-		else
-		{
-			$links[] = '<li id="navsearch"'.((PUN_ACTIVE_PAGE == 'search') ? ' class="isactive"' : '').'><a href="search.php">'.$lang_common['Search'].'</a></li>';
-			$links[] = '<li id="navprofile"'.((PUN_ACTIVE_PAGE == 'profile') ? ' class="isactive"' : '').'><a href="profile.php?id='.$pun_user['id'].'">'.$lang_common['Profile'].'</a></li>';
-			$links[] = '<li id="navadmin"'.((PUN_ACTIVE_PAGE == 'admin') ? ' class="isactive"' : '').'><a href="admin_index.php">'.$lang_common['Admin'].'</a></li>';
-			$links[] = '<li id="navlogout"><a href="login.php?action=out&id='.$pun_user['id'].'&csrf_token='.pun_hash($pun_user['id'].pun_hash(get_remote_address())).'">'.$lang_common['Logout'].'</a></li>';
-		}
-	}
-
-	// Are there any additional navlinks we should insert into the array before imploding it?
-	if ($pun_user['g_read_board'] == '1' && $pun_config['o_additional_navlinks'] != '')
-	{
-		if (preg_match_all('#([0-9]+)\s*=\s*(.*?)\n#s', $pun_config['o_additional_navlinks']."\n", $extra_links))
-		{
-			// Insert any additional links into the $links array (at the correct index)
-			$num_links = count($extra_links[1]);
-			for ($i = 0; $i < $num_links; ++$i)
-				array_splice($links, $extra_links[1][$i], 0, array('<li id="navextra'.($i + 1).'">'.$extra_links[2][$i].'</li>'));
-		}
-	}
-
-	return '<ul>'."\n\t\t\t\t".implode("\n\t\t\t\t", $links)."\n\t\t\t".'</ul>';
-}
-
-
-//
 // Display the profile navigation menu
 //
 function generate_profile_menu($page = '')
@@ -646,10 +618,10 @@ function set_tracked_topics($tracked_topics)
 		foreach ($tracked_topics['forums'] as $id => $timestamp)
 			$cookie_data .= 'f'.$id.'='.$timestamp.';';
 
-		// Enforce a 4048 byte size limit (4096 minus some space for the cookie name)
-		if (strlen($cookie_data) > 4048)
+		// Enforce a byte size limit (4096 minus some space for the cookie name - defaults to 4048)
+		if (strlen($cookie_data) > FORUM_MAX_COOKIE_SIZE)
 		{
-			$cookie_data = substr($cookie_data, 0, 4048);
+			$cookie_data = substr($cookie_data, 0, FORUM_MAX_COOKIE_SIZE);
 			$cookie_data = substr($cookie_data, 0, strrpos($cookie_data, ';')).';';
 		}
 	}
@@ -837,7 +809,7 @@ function censor_words($text)
 	}
 
 	if (!empty($search_for))
-		$text = substr(preg_replace($search_for, $replace_with, ' '.$text.' '), 1, -1);
+		$text = substr(ucp_preg_replace($search_for, $replace_with, ' '.$text.' '), 1, -1);
 
 	return $text;
 }
@@ -1081,26 +1053,6 @@ function random_key($len, $readable = false, $hash = false)
 
 
 //
-// If we are running pre PHP 4.3.0, we add our own implementation of file_get_contents
-//
-if (!function_exists('file_get_contents'))
-{
-	function file_get_contents($filename, $use_include_path = 0)
-	{
-		$data = '';
-
-		if ($fh = fopen($filename, 'rb', $use_include_path))
-		{
-			$data = fread($fh, filesize($filename));
-			fclose($fh);
-		}
-
-		return $data;
-	}
-}
-
-
-//
 // Make sure that HTTP_REFERER matches base_url/script
 //
 function confirm_referrer($script, $error_msg = false)
@@ -1151,7 +1103,23 @@ function pun_hash($str)
 //
 function get_remote_address()
 {
-	return $_SERVER['REMOTE_ADDR'];
+	$remote_addr = $_SERVER['REMOTE_ADDR'];
+
+	// If we are behind a reverse proxy try to find the real users IP
+	if (defined('FORUM_BEHIND_REVERSE_PROXY'))
+	{
+		if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
+		{
+			// The general format of the field is:
+			// X-Forwarded-For: client1, proxy1, proxy2
+			// where the value is a comma+space separated list of IP addresses, the left-most being the farthest downstream client,
+			// and each successive proxy that passed the request adding the IP address where it received the request from.
+			$remote_addr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
+			$remote_addr = trim($remote_addr[0]);
+		}
+	}
+
+	return $remote_addr;
 }
 
 
@@ -1251,12 +1219,20 @@ function maintenance_message()
 {
 	global $db, $pun_config, $lang_common, $pun_user;
 
+	// Send no-cache headers
+	header('Expires: Thu, 21 Jul 1977 07:30:00 GMT'); // When yours truly first set eyes on this world! :)
+	header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
+	header('Cache-Control: post-check=0, pre-check=0', false);
+	header('Pragma: no-cache'); // For HTTP/1.0 compatibility
+
+	// Send the Content-type header in case the web server is setup to send something else
+	header('Content-type: text/html; charset=utf-8');
+
 	// Deal with newlines, tabs and multiple spaces
 	$pattern = array("\t", '  ', '  ');
 	$replace = array('    ', '  ', '  ');
 	$message = str_replace($pattern, $replace, $pun_config['o_maintenance_message']);
 
-
 	if (file_exists(PUN_ROOT.'style/'.$pun_user['style'].'/maintenance.tpl'))
 	{
 		$tpl_file = PUN_ROOT.'style/'.$pun_user['style'].'/maintenance.tpl';
@@ -1688,7 +1664,7 @@ function remove_bad_characters($array)
 //
 function file_size($size)
 {
-	$units = array('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB');
+	$units = array('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB');
 
 	for ($i = 0; $size > 1024; $i++)
 		$size /= 1024;
@@ -1746,6 +1722,27 @@ function forum_list_langs()
 
 
 //
+// Generate a cache ID based on the last modification time for all stopwords files
+//
+function generate_stopwords_cache_id()
+{
+	$files = glob(PUN_ROOT.'lang/*/stopwords.txt');
+	if ($files === false)
+		return 'cache_id_error';
+
+	$hash = array();
+
+	foreach ($files as $file)
+	{
+		$hash[] = $file;
+		$hash[] = filemtime($file);
+	}
+
+	return sha1(implode('|', $hash));
+}
+
+
+//
 // Fetch a list of available admin plugins
 //
 function forum_list_plugins($is_admin)
@@ -1804,6 +1801,142 @@ function split_text($text, $start, $end, &$errors, $retab = true)
 	return array($inside, $outside);
 }
 
+//
+// function url_valid($url) {
+//
+// Return associative array of valid URI components, or FALSE if $url is not
+// RFC-3986 compliant. If the passed URL begins with: "www." or "ftp.", then
+// "http://" or "ftp://" is prepended and the corrected full-url is stored in
+// the return array with a key name "url". This value should be used by the caller.
+//
+// Return value: FALSE if $url is not valid, otherwise array of URI components:
+// e.g.
+// Given: "http://www.jmrware.com:80/articles?height=10&width=75#fragone"
+// Array(
+//	  [scheme] => http
+//	  [authority] => www.jmrware.com:80
+//	  [userinfo] =>
+//	  [host] => www.jmrware.com
+//	  [IP_literal] =>
+//	  [IPV6address] =>
+//	  [ls32] =>
+//	  [IPvFuture] =>
+//	  [IPv4address] =>
+//	  [regname] => www.jmrware.com
+//	  [port] => 80
+//	  [path_abempty] => /articles
+//	  [query] => height=10&width=75
+//	  [fragment] => fragone
+//	  [url] => http://www.jmrware.com:80/articles?height=10&width=75#fragone
+// )
+function url_valid($url)
+{
+	if (strpos($url, 'www.') === 0) $url = 'http://'. $url;
+	if (strpos($url, 'ftp.') === 0) $url = 'ftp://'. $url;
+	if (!preg_match('/# Valid absolute URI having a non-empty, valid DNS host.
+		^
+		(?P<scheme>[A-Za-z][A-Za-z0-9+\-.]*):\/\/
+		(?P<authority>
+		  (?:(?P<userinfo>(?:[A-Za-z0-9\-._~!$&\'()*+,;=:]|%[0-9A-Fa-f]{2})*)@)?
+		  (?P<host>
+			(?P<IP_literal>
+			  \[
+			  (?:
+				(?P<IPV6address>
+				  (?:												 (?:[0-9A-Fa-f]{1,4}:){6}
+				  |												   ::(?:[0-9A-Fa-f]{1,4}:){5}
+				  | (?:							 [0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}
+				  | (?:(?:[0-9A-Fa-f]{1,4}:){0,1}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}
+				  | (?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}
+				  | (?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::	[0-9A-Fa-f]{1,4}:
+				  | (?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::
+				  )
+				  (?P<ls32>[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}
+				  | (?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}
+					   (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)
+				  )
+				|	(?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::	[0-9A-Fa-f]{1,4}
+				|	(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::
+				)
+			  | (?P<IPvFuture>[Vv][0-9A-Fa-f]+\.[A-Za-z0-9\-._~!$&\'()*+,;=:]+)
+			  )
+			  \]
+			)
+		  | (?P<IPv4address>(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}
+							   (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))
+		  | (?P<regname>(?:[A-Za-z0-9\-._~!$&\'()*+,;=]|%[0-9A-Fa-f]{2})+)
+		  )
+		  (?::(?P<port>[0-9]*))?
+		)
+		(?P<path_abempty>(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*)
+		(?:\?(?P<query>		  (?:[A-Za-z0-9\-._~!$&\'()*+,;=:@\\/?]|%[0-9A-Fa-f]{2})*))?
+		(?:\#(?P<fragment>	  (?:[A-Za-z0-9\-._~!$&\'()*+,;=:@\\/?]|%[0-9A-Fa-f]{2})*))?
+		$
+		/mx', $url, $m)) return FALSE;
+	switch ($m['scheme'])
+	{
+	case 'https':
+	case 'http':
+		if ($m['userinfo']) return FALSE; // HTTP scheme does not allow userinfo.
+		break;
+	case 'ftps':
+	case 'ftp':
+		break;
+	default:
+		return FALSE;	// Unrecognised URI scheme. Default to FALSE.
+	}
+	// Validate host name conforms to DNS "dot-separated-parts".
+	if ($m{'regname'}) // If host regname specified, check for DNS conformance.
+	{
+		if (!preg_match('/# HTTP DNS host name.
+			^					   # Anchor to beginning of string.
+			(?!.{256})			   # Overall host length is less than 256 chars.
+			(?:					   # Group dot separated host part alternatives.
+			  [0-9A-Za-z]\.		   # Either a single alphanum followed by dot
+			|					   # or... part has more than one char (63 chars max).
+			  [0-9A-Za-z]		   # Part first char is alphanum (no dash).
+			  [\-0-9A-Za-z]{0,61}  # Internal chars are alphanum plus dash.
+			  [0-9A-Za-z]		   # Part last char is alphanum (no dash).
+			  \.				   # Each part followed by literal dot.
+			)*					   # One or more parts before top level domain.
+			(?:					   # Explicitly specify top level domains.
+			  com|edu|gov|int|mil|net|org|biz|
+			  info|name|pro|aero|coop|museum|
+			  asia|cat|jobs|mobi|tel|travel|
+			  [A-Za-z]{2})		   # Country codes are exqactly two alpha chars.
+			$					   # Anchor to end of string.
+			/ix', $m['host'])) return FALSE;
+	}
+	$m['url'] = $url;
+	for ($i = 0; isset($m[$i]); ++$i) unset($m[$i]);
+	return $m; // return TRUE == array of useful named $matches plus the valid $url.
+}
+
+//
+// Replace string matching regular expression
+//
+// This function takes care of possibly disabled unicode properties in PCRE builds
+//
+function ucp_preg_replace($pattern, $replace, $subject)
+{
+	$replaced = preg_replace($pattern, $replace, $subject);
+	
+	// If preg_replace() returns false, this probably means unicode support is not built-in, so we need to modify the pattern a little
+	if ($replaced === false)
+	{
+		if (is_array($pattern))
+		{
+			foreach ($pattern as $cur_key => $cur_pattern)
+				$pattern[$cur_key] = str_replace('\p{L}\p{N}', '\w', $cur_pattern);
+			
+			$replaced = preg_replace($pattern, $replace, $subject);
+		}
+		else
+			$replaced = preg_replace(str_replace('\p{L}\p{N}', '\w', $pattern), $replace, $subject);
+	}
+	
+	return $replaced;
+}
 
 // DEBUG FUNCTIONS BELOW
 
diff --git a/include/parser.php b/include/parser.php
index 2e2bb02..02449b7 100644
--- a/include/parser.php
+++ b/include/parser.php
@@ -714,7 +714,7 @@ function do_bbcode($text, $is_signature = false)
 	if (strpos($text, '[quote') !== false)
 	{
 		$text = preg_replace('#\[quote\]\s*#', '</p><div class="quotebox"><blockquote><div><p>', $text);
-		$text = preg_replace('#\[quote=("|"|\'|)(.*?)\\1\]#se', '"</p><div class=\"quotebox\"><cite>".str_replace(array(\'[\', \'\\"\'), array(\'[\', \'"\'), \'$2\')." ".$lang_common[\'wrote\']."</cite><blockquote><div><p>"', $text);
+		$text = preg_replace('#\[quote=("|&\#039;|"|\'|)(.*?)\\1\]#se', '"</p><div class=\"quotebox\"><cite>".str_replace(array(\'[\', \'\\"\'), array(\'[\', \'"\'), \'$2\')." ".$lang_common[\'wrote\']."</cite><blockquote><div><p>"', $text);
 		$text = preg_replace('#\s*\[\/quote\]#S', '</p></div></blockquote></div><p>', $text);
 	}
 
@@ -784,8 +784,8 @@ function do_clickable($text)
 {
 	$text = ' '.$text;
 
-	$text = preg_replace('#(?<=[\s\]\)])(<)?(\[)?(\()?([\'"]?)(https?|ftp|news){1}://([\p{L}\p{N}\-]+\.([\p{L}\p{N}\-]+\.)*[\p{L}\p{N}]+(:[0-9]+)?(/[^\s\[]*[^\s.,?!\[;:-])?)\4(?(3)(\)))(?(2)(\]))(?(1)(>))(?![^\s]*\[/(?:url|img)\])#uie', 'stripslashes(\'$1$2$3$4\').handle_url_tag(\'$5://$6\', \'$5://$6\', true).stripslashes(\'$4$10$11$12\')', $text);
-	$text = preg_replace('#(?<=[\s\]\)])(<)?(\[)?(\()?([\'"]?)(www|ftp)\.(([\p{L}\p{N}\-]+\.)*[\p{L}\p{N}]+(:[0-9]+)?(/[^\s\[]*[^\s.,?!\[;:-])?)\4(?(3)(\)))(?(2)(\]))(?(1)(>))(?![^\s]*\[/(?:url|img)\])#uie', 'stripslashes(\'$1$2$3$4\').handle_url_tag(\'$5.$6\', \'$5.$6\', true).stripslashes(\'$4$10$11$12\')', $text);
+	$text = ucp_preg_replace('#(?<=[\s\]\)])(<)?(\[)?(\()?([\'"]?)(https?|ftp|news){1}://([\p{L}\p{N}\-]+\.([\p{L}\p{N}\-]+\.)*[\p{L}\p{N}]+(:[0-9]+)?(/[^\s\[]*[^\s.,?!\[;:-])?)\4(?(3)(\)))(?(2)(\]))(?(1)(>))(?![^\s]*\[/(?:url|img)\])#uie', 'stripslashes(\'$1$2$3$4\').handle_url_tag(\'$5://$6\', \'$5://$6\', true).stripslashes(\'$4$10$11$12\')', $text);
+	$text = ucp_preg_replace('#(?<=[\s\]\)])(<)?(\[)?(\()?([\'"]?)(www|ftp)\.(([\p{L}\p{N}\-]+\.)*[\p{L}\p{N}]+(:[0-9]+)?(/[^\s\[]*[^\s.,?!\[;:-])?)\4(?(3)(\)))(?(2)(\]))(?(1)(>))(?![^\s]*\[/(?:url|img)\])#uie', 'stripslashes(\'$1$2$3$4\').handle_url_tag(\'$5.$6\', \'$5.$6\', true).stripslashes(\'$4$10$11$12\')', $text);
 
 	return substr($text, 1);
 }
@@ -803,7 +803,7 @@ function do_smilies($text)
 	foreach ($smilies as $smiley_text => $smiley_img)
 	{
 		if (strpos($text, $smiley_text) !== false)
-			$text = preg_replace('#(?<=[>\s])'.preg_quote($smiley_text, '#').'(?=[^\p{L}\p{N}])#um', '<img src="'.pun_htmlspecialchars(get_base_url(true).'/img/smilies/'.$smiley_img).'" width="15" height="15" alt="'.substr($smiley_img, 0, strrpos($smiley_img, '.')).'" />', $text);
+			$text = ucp_preg_replace('#(?<=[>\s])'.preg_quote($smiley_text, '#').'(?=[^\p{L}\p{N}])#um', '<img src="'.pun_htmlspecialchars(get_base_url(true).'/img/smilies/'.$smiley_img).'" width="15" height="15" alt="'.substr($smiley_img, 0, strrpos($smiley_img, '.')).'" />', $text);
 	}
 
 	return substr($text, 1, -1);
diff --git a/include/search_idx.php b/include/search_idx.php
index eed20a2..7f3a834 100644
--- a/include/search_idx.php
+++ b/include/search_idx.php
@@ -51,10 +51,10 @@ function split_words($text, $idx)
 	$text = preg_replace('/\[\/?(b|u|s|ins|del|em|i|h|colou?r|quote|code|img|url|email|list)(?:\=[^\]]*)?\]/', ' ', $text);
 
 	// Remove any apostrophes or dashes which aren't part of words
-	$text = substr(preg_replace('/((?<=[^\p{L}\p{N}])[\'\-]|[\'\-](?=[^\p{L}\p{N}]))/u', '', ' '.$text.' '), 1, -1);
+	$text = substr(ucp_preg_replace('/((?<=[^\p{L}\p{N}])[\'\-]|[\'\-](?=[^\p{L}\p{N}]))/u', '', ' '.$text.' '), 1, -1);
 
 	// Remove punctuation and symbols (actually anything that isn't a letter or number), allow apostrophes and dashes (and % * if we aren't indexing)
-	$text = preg_replace('/(?![\'\-'.($idx ? '' : '%\*').'])[^\p{L}\p{N}]+/u', ' ', $text);
+	$text = ucp_preg_replace('/(?![\'\-'.($idx ? '' : '%\*').'])[^\p{L}\p{N}]+/u', ' ', $text);
 
 	// Replace multiple whitespace or dashes
 	$text = preg_replace('/(\s){2,}/u', '\1', $text);
diff --git a/index.php b/index.php
index 36495c9..e80e73b 100644
--- a/index.php
+++ b/index.php
@@ -171,13 +171,18 @@ if ($cur_category > 0)
 else
 	echo '<div id="idx0" class="block"><div class="box"><div class="inbox"><p>'.$lang_index['Empty board'].'</p></div></div></div>';
 
-
 // Collect some statistics from the database
-$result = $db->query('SELECT COUNT(id)-1 FROM '.$db->prefix.'users WHERE group_id!='.PUN_UNVERIFIED) or error('Unable to fetch total user count', __FILE__, __LINE__, $db->error());
-$stats['total_users'] = $db->result($result);
+if (file_exists(FORUM_CACHE_DIR.'cache_users_info.php'))
+	include FORUM_CACHE_DIR.'cache_users_info.php';
+
+if (!defined('PUN_USERS_INFO_LOADED'))
+{
+	if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+		require PUN_ROOT.'include/cache.php';
 
-$result = $db->query('SELECT id, username FROM '.$db->prefix.'users WHERE group_id!='.PUN_UNVERIFIED.' ORDER BY registered DESC LIMIT 1') or error('Unable to fetch newest registered user', __FILE__, __LINE__, $db->error());
-$stats['last_user'] = $db->fetch_assoc($result);
+	generate_users_info_cache();
+	require FORUM_CACHE_DIR.'cache_users_info.php';
+}
 
 $result = $db->query('SELECT SUM(num_topics), SUM(num_posts) FROM '.$db->prefix.'forums') or error('Unable to fetch topic/post count', __FILE__, __LINE__, $db->error());
 list($stats['total_topics'], $stats['total_posts']) = $db->fetch_row($result);
diff --git a/lang/English/admin_bans.php b/lang/English/admin_bans.php
index a10a3c0..7d5daa8 100644
--- a/lang/English/admin_bans.php
+++ b/lang/English/admin_bans.php
@@ -5,7 +5,8 @@ $lang_admin_bans = array(
 
 'No user message'			=>	'No user by that username registered. If you want to add a ban not tied to a specific username just leave the username blank.',
 'No user ID message'		=>	'No user by that ID registered.',
-'User is admin message'		=>	'The user %s is an administrator and can\'t be banned. If you want to ban an administrator, you must first demote him/her to moderator or user.',
+'User is admin message'		=>	'The user %s is an administrator and can\'t be banned. If you want to ban an administrator, you must first demote him/her.',
+'User is mod message'		=>	'The user %s is a moderator and can\'t be banned. If you want to ban a moderator, you must first demote him/her.',
 'Must enter message'		=>	'You must enter either a username, an IP address or an email address (at least).',
 'Cannot ban guest message'	=>	'The guest user cannot be banned.',
 'Invalid IP message'		=>	'You entered an invalid IP/IP-range.',
diff --git a/lang/English/admin_maintenance.php b/lang/English/admin_maintenance.php
index aa29f7f..f4d5515 100644
--- a/lang/English/admin_maintenance.php
+++ b/lang/English/admin_maintenance.php
@@ -19,6 +19,22 @@ $lang_admin_maintenance = array(
 'Processing post'				=>	'Processing post <strong>%s</strong> …',
 'Click here'					=>	'Click here',
 'Javascript redirect failed'	=>	'JavaScript redirect unsuccessful. %s to continue …',
-'Must be integer message'		=>	'Posts per cycle must be a positive integer value.',
+'Posts must be integer message'	=>	'Posts per cycle must be a positive integer value.',
+'Days must be integer message'	=>	'Days to prune must be a positive integer value.',
+'No old topics message'			=>	'There are no topics that are %s days old. Please decrease the value of "Days old" and try again.',
+'Posts pruned redirect'			=>	'Posts pruned. Redirecting …',
+'Prune head'					=>	'Prune',
+'Prune subhead'					=>	'Prune old posts',
+'Days old label'				=>	'Days old',
+'Days old help'					=>	'The number of days "old" a topic must be to be pruned. E.g. if you were to enter 30, every topic that didn\'t contain a post dated less than 30 days old would be deleted.',
+'Prune sticky label'			=>	'Prune sticky topics',
+'Prune sticky help'				=>	'When enabled, sticky topics will also be pruned.',
+'Prune from label'				=>	'Prune from forum',
+'All forums'					=>	'All forums',
+'Prune from help'				=>	'The forum from which you want to prune posts.',
+'Prune info'					=>	'Use this feature with caution. <strong>Pruned posts can never be recovered.</strong> For best performance, you should put the forum in %s during pruning.',
+'Confirm prune subhead'			=>	'Confirm prune posts',
+'Confirm prune info'			=>	'Are you sure that you want to prune all topics older than %s days from %s (%s topics).',
+'Confirm prune warn'			=>	'WARNING! Pruning posts deletes them permanently.',
 
 );
diff --git a/lang/English/admin_options.php b/lang/English/admin_options.php
index c852929..00923ce 100644
--- a/lang/English/admin_options.php
+++ b/lang/English/admin_options.php
@@ -137,11 +137,18 @@ $lang_admin_options = array(
 'Search all help'					=>	'When disabled, searches will only be allowed in one forum at a time. Disable if server load is high due to excessive searching.',
 'Menu items label'					=>	'Additional menu items',
 'Menu items help'					=>	'By entering HTML hyperlinks into this textbox, any number of items can be added to the navigation menu at the top of all pages. The format for adding new links is X = <a href="URL">LINK</a> where X is the position at which the link should be inserted (e.g. 0 to insert at the beginning and 2 to insert after "User list"). Separate entries with a linebreak.',
+
+// Feeds section
+'Feed subhead'						=>	'Syndication',
 'Default feed label'				=>	'Default feed type',
 'Default feed help'					=>	'Select the type of syndication feed to display. Note: Choosing none will not disable feeds, only hide them by default.',
 'None'								=>	'None',
 'RSS'								=>	'RSS',
 'Atom'								=>	'Atom',
+'Feed TTL label'					=>	'Duration to cache feeds',
+'Feed TTL help'						=>	'Feeds can be cached to lower the resource usage of feeds.',
+'No cache'							=>	'Don\'t cache',
+'Minutes'							=>	'%d minutes',
 
 // Reports section
 'Reports subhead'					=>	'Reports',
diff --git a/lang/English/admin_reports.php b/lang/English/admin_reports.php
index 21ccb13..0daa8da 100644
--- a/lang/English/admin_reports.php
+++ b/lang/English/admin_reports.php
@@ -7,6 +7,7 @@ $lang_admin_reports = array(
 'New reports head'			=>	'New reports',
 'Deleted user'				=>	'Deleted user',
 'Deleted'					=>	'Deleted',
+'Post ID'					=>	'Post #%s',
 'Report subhead'			=>	'Reported %s',
 'Reported by'				=>	'Reported by %s',
 'Reason'					=>	'Reason',
diff --git a/lang/English/admin_users.php b/lang/English/admin_users.php
index 1e21a34..76d6639 100644
--- a/lang/English/admin_users.php
+++ b/lang/English/admin_users.php
@@ -6,6 +6,37 @@ $lang_admin_users = array(
 'Non numeric message'		=>	'You entered a non-numeric value into a numeric only column.',
 'Invalid date time message'	=>	'You entered an invalid date/time.',
 'Not verified'				=>	'Not verified',
+
+// Actions: mass delete/ban etc.
+'No users selected'			=>	'No users selected.',
+'No move admins message'	=>	'For security reasons, you are not allowed to move multiple administrators to another group. If you want to move these administrators, you can do so on their respective user profiles.',
+'No delete admins message'	=>	'Administrators cannot be deleted. In order to delete administrators, you must first move them to a different user group.',
+'No ban admins message'		=>	'Administrators cannot be banned. In order to ban administrators, you must first move them to a different user group.',
+'No ban mods message'		=>	'Moderators cannot be banned. In order to ban moderators, you must first move them to a different user group.',
+'Move users'				=>	'Change user group',
+'Move users subhead'		=>	'Select new user group',
+'New group label'			=>	'New group',
+'New group help'			=>	'Select the group to which the selected users will be moved. For security reasons, it is not possible to move multiple users to the administrator group.',
+'Invalid group message'		=>	'Invalid group ID.',
+'Users move redirect'		=>	'User group changed. Redirecting …',
+'Delete users'				=>	'Delete users',
+'Confirm delete legend'		=>	'Important: read before deleting users',
+'Confirm delete info'		=>	'Please confirm that you want to delete these users.',
+'Delete posts'				=>	'Delete any posts and topics these users have made.',
+'Delete warning'			=>	'Warning! Deleted users and/or posts cannot be restored. If you choose not to delete the posts made by these users, the posts can only be deleted manually at a later time.',
+'Users delete redirect'		=>	'Users deleted. Redirecting …',
+'Ban users'					=>	'Ban users',
+'Message expiry subhead'	=>	'Ban message and expiry',
+'Ban message label'			=>	'Ban message',
+'Ban message help'			=>	'A message that will be displayed to the banned users when they visit the board.',
+'Expire date label'			=>	'Expire date',
+'Expire date help'			=>	'The date when these bans should be automatically removed (format: yyyy-mm-dd). Leave blank to remove manually.',
+'Ban IP label'				=>	'Ban IP addresses',
+'Ban IP help'				=>	'Also ban the IP addresses of the banned users to make registering a new account more difficult for them.',
+'Invalid date message'		=>	'You entered an invalid expire date.',
+'Invalid date reasons'		=>	'The format should be YYYY-MM-DD and the date must be at least one day in the future.',
+'Users banned redirect'		=>	'Users banned. Redirecting …',
+
 'User search head'			=>	'User search',
 'User search subhead'		=>	'Enter search criteria',
 'User search info'			=>	'Search for users in the database. You can enter one or more terms to search for. Wildcards in the form of asterisks (*) are accepted.',
@@ -60,6 +91,12 @@ $lang_admin_users = array(
 'Results action head'		=>	'Action',
 'Results find more link'	=>	'Find more users for this ip',
 'Results no posts found'	=>	'There are currently no posts by that user in the forum.',
+'Select'					=>	'Select',
+'Select all'				=>	'Select all',
+'Unselect all'				=>	'Unselect all',
+'Ban'						=>	'Ban',
+'Delete'					=>	'Delete',
+'Change group'				=>	'Change group',
 'Bad IP message'			=>	'The supplied IP address is not correctly formatted.',
 'Results view IP link'		=>	'View IP stats',
 'Results show posts link'	=>	'Show posts',
diff --git a/lang/English/misc.php b/lang/English/misc.php
index 71ff526..9bca09c 100644
--- a/lang/English/misc.php
+++ b/lang/English/misc.php
@@ -73,7 +73,7 @@ $lang_misc = array(
 'New subject'					=>	'New subject',
 
 // Split multiple posts in topic
-'Confirm split legend'			=>	'Please confirm split of selected posts.',
+'Confirm split legend'			=>	'Please confirm split of selected posts and select destination of move.',
 'Split posts'					=>	'Split posts',
 'Split posts comply'			=>	'Are you sure you want to split the selected posts?',
 'Split posts redirect'			=>	'Posts have been split. Redirecting …',
diff --git a/lang/English/post.php b/lang/English/post.php
index 0f16aeb..2b3c678 100644
--- a/lang/English/post.php
+++ b/lang/English/post.php
@@ -5,8 +5,10 @@ $lang_post = array(
 
 // Post validation stuff (many are similiar to those in edit.php)
 'No subject'		=>	'Topics must contain a subject.',
+'No subject after censoring'	=>	'Topics must contain a subject. After applying censoring filters, your subject was empty.',
 'Too long subject'	=>	'Subjects cannot be longer than 70 characters.',
 'No message'		=>	'You must enter a message.',
+'No message after censoring'	=>	'You must enter a message. After applying censoring filters, your message was empty.',
 'Too long message'	=>	'Posts cannot be longer that %s bytes.',
 'All caps subject'	=>	'Subjects cannot contain only capital letters.',
 'All caps message'	=>	'Posts cannot contain only capital letters.',
diff --git a/lang/English/profile.php b/lang/English/profile.php
index 2a4e276..b1aa3ab 100644
--- a/lang/English/profile.php
+++ b/lang/English/profile.php
@@ -74,6 +74,7 @@ $lang_profile = array(
 'Posts info'					=>	'Posts: %s',
 'Registered info'				=>	'Registered: %s',
 'Last post info'				=>	'Last post: %s',
+'Last visit info'				=>	'Last visit: %s',
 'Show posts'					=>	'Show all posts',
 'Show topics'					=>	'Show all topics',
 'Show subscriptions'			=>	'Show all subscriptions',
diff --git a/lang/English/search.php b/lang/English/search.php
index 171e022..1fe47cf 100644
--- a/lang/English/search.php
+++ b/lang/English/search.php
@@ -41,13 +41,16 @@ $lang_search = array(
 'Quick search show_new'				=>	'New',
 'Quick search show_recent'			=>	'Active',
 'Quick search show_unanswered'		=>	'Unanswered',
-'Quick search show_replies'			=>	'Yours',
+'Quick search show_replies'			=>	'Posted',
 'Quick search show_user_topics'		=>	'By %s',
 'Quick search show_user_posts'		=>	'By %s',
 'Quick search show_subscriptions'	=>	'Subscribed by %s',
-'By keywords'						=>	'With keywords \'%s\'',
-'By user'							=>	'With posts by %s',
-'By both'							=>	'With keywords \'%s\' in posts by %s',
+'By keywords show as topics'		=>	'With posts with keywords \'%s\'',
+'By keywords show as posts'			=>	'With keywords \'%s\'',
+'By user show as topics'			=>	'With posts by %s',
+'By user show as posts'				=>	'By %s',
+'By both show as topics'			=>	'With keywords \'%s\' in posts by %s',
+'By both show as posts'				=>	'With keywords \'%s\' by %s',
 'No terms'							=>	'You have to enter at least one keyword and/or an author to search for.',
 'No hits'							=>	'Your search returned no hits.',
 'No user posts'						=>	'There are no posts by this user in this forum.',
diff --git a/login.php b/login.php
index 903c351..38cb797 100644
--- a/login.php
+++ b/login.php
@@ -83,8 +83,16 @@ if (isset($_POST['form_sent']) && $action == 'in')
 
 	// Update the status if this is the first time the user logged in
 	if ($cur_user['group_id'] == PUN_UNVERIFIED)
+	{
 		$db->query('UPDATE '.$db->prefix.'users SET group_id='.$pun_config['o_default_user_group'].' WHERE id='.$cur_user['id']) or error('Unable to update user status', __FILE__, __LINE__, $db->error());
 
+		// Regenerate the users info cache
+		if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+			require PUN_ROOT.'include/cache.php';
+
+		generate_users_info_cache();
+	}
+
 	// Remove this users guest entry from the online list
 	$db->query('DELETE FROM '.$db->prefix.'online WHERE ident=\''.$db->escape(get_remote_address()).'\'') or error('Unable to delete from online list', __FILE__, __LINE__, $db->error());
 
diff --git a/moderate.php b/moderate.php
index 2150c22..77b45e2 100644
--- a/moderate.php
+++ b/moderate.php
@@ -156,15 +156,23 @@ if (isset($_GET['tid']))
 			if (@preg_match('/[^0-9,]/', $posts))
 				message($lang_common['Bad request']);
 
+			$move_to_forum = isset($_POST['move_to_forum']) ? intval($_POST['move_to_forum']) : 0;
+			if ($move_to_forum < 1)
+				message($lang_common['Bad request']);
+
 			// How many posts did we just split off?
 			$num_posts_splitted = substr_count($posts, ',') + 1;
 
 			// Verify that the post IDs are valid
 			$result = $db->query('SELECT 1 FROM '.$db->prefix.'posts WHERE id IN('.$posts.') AND topic_id='.$tid) or error('Unable to check posts', __FILE__, __LINE__, $db->error());
-
 			if ($db->num_rows($result) != $num_posts_splitted)
 				message($lang_common['Bad request']);
 
+			// Verify that the move to forum ID is valid
+			$result = $db->query('SELECT 1 FROM '.$db->prefix.'forums AS f LEFT JOIN '.$db->prefix.'forum_perms AS fp ON (fp.group_id='.$pun_user['g_id'].' AND fp.forum_id='.$move_to_forum.') WHERE f.redirect_url IS NULL AND (fp.post_topics IS NULL OR fp.post_topics=1)') or error('Unable to fetch forum permissions', __FILE__, __LINE__, $db->error());
+			if (!$db->num_rows($result))
+				message($lang_common['Bad request']);
+
 			// Load the post.php language file
 			require PUN_ROOT.'lang/'.$pun_user['language'].'/post.php';
 
@@ -181,7 +189,7 @@ if (isset($_GET['tid']))
 			$first_post_data = $db->fetch_assoc($result);
 
 			// Create the new topic
-			$db->query('INSERT INTO '.$db->prefix.'topics (poster, subject, posted, first_post_id, forum_id) VALUES (\''.$db->escape($first_post_data['poster']).'\', \''.$db->escape($new_subject).'\', '.$first_post_data['posted'].', '.$first_post_data['id'].', '.$fid.')') or error('Unable to create new topic', __FILE__, __LINE__, $db->error());
+			$db->query('INSERT INTO '.$db->prefix.'topics (poster, subject, posted, first_post_id, forum_id) VALUES (\''.$db->escape($first_post_data['poster']).'\', \''.$db->escape($new_subject).'\', '.$first_post_data['posted'].', '.$first_post_data['id'].', '.$move_to_forum.')') or error('Unable to create new topic', __FILE__, __LINE__, $db->error());
 			$new_tid = $db->insert_id();
 
 			// Move the posts to the new topic
@@ -198,10 +206,13 @@ if (isset($_GET['tid']))
 			$db->query('UPDATE '.$db->prefix.'topics SET last_post='.$last_post_data['posted'].', last_post_id='.$last_post_data['id'].', last_poster=\''.$db->escape($last_post_data['poster']).'\', num_replies='.($num_posts_splitted-1).' WHERE id='.$new_tid) or error('Unable to update topic', __FILE__, __LINE__, $db->error());
 
 			update_forum($fid);
+			update_forum($move_to_forum);
 
 			redirect('viewtopic.php?id='.$new_tid, $lang_misc['Split posts redirect']);
 		}
 
+		$result = $db->query('SELECT c.id AS cid, c.cat_name, f.id AS fid, f.forum_name FROM '.$db->prefix.'categories AS c INNER JOIN '.$db->prefix.'forums AS f ON c.id=f.cat_id LEFT JOIN '.$db->prefix.'forum_perms AS fp ON (fp.forum_id=f.id AND fp.group_id='.$pun_user['g_id'].') WHERE (fp.post_topics IS NULL OR fp.post_topics=1) AND f.redirect_url IS NULL ORDER BY c.disp_position, c.id, f.disp_position') or error('Unable to fetch category/forum list', __FILE__, __LINE__, $db->error());
+
 		$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_misc['Moderate']);
 		$focus_element = array('subject','new_subject');
 		define('PUN_ACTIVE_PAGE', 'index');
@@ -218,6 +229,29 @@ if (isset($_GET['tid']))
 					<div class="infldset">
 						<input type="hidden" name="posts" value="<?php echo implode(',', array_map('intval', array_keys($posts))) ?>" />
 						<label class="required"><strong><?php echo $lang_misc['New subject'] ?> <span><?php echo $lang_common['Required'] ?></span></strong><br /><input type="text" name="new_subject" size="80" maxlength="70" /><br /></label>
+						<label><?php echo $lang_misc['Move to'] ?>
+						<br /><select name="move_to_forum">
+<?php
+
+	$cur_category = 0;
+	while ($cur_forum = $db->fetch_assoc($result))
+	{
+		if ($cur_forum['cid'] != $cur_category) // A new category since last iteration?
+		{
+			if ($cur_category)
+				echo "\t\t\t\t\t\t\t".'</optgroup>'."\n";
+
+			echo "\t\t\t\t\t\t\t".'<optgroup label="'.pun_htmlspecialchars($cur_forum['cat_name']).'">'."\n";
+			$cur_category = $cur_forum['cid'];
+		}
+
+		echo "\t\t\t\t\t\t\t\t".'<option value="'.$cur_forum['fid'].'"'.($fid == $cur_forum['fid'] ? ' selected="selected"' : '').'>'.pun_htmlspecialchars($cur_forum['forum_name']).'</option>'."\n";
+	}
+
+?>
+							</optgroup>
+						</select>
+						<br /></label>
 						<p><?php echo $lang_misc['Split posts comply'] ?></p>
 					</div>
 				</fieldset>
@@ -357,12 +391,19 @@ if (isset($_GET['tid']))
 
 ?>
 <div class="postlinksb">
-	<div class="inbox">
+	<div class="inbox crumbsplus">
 		<div class="pagepost">
 			<p class="pagelink conl"><?php echo $paging_links ?></p>
 			<p class="conr modbuttons"><input type="submit" name="split_posts" value="<?php echo $lang_misc['Split'] ?>"<?php echo $button_status ?> /> <input type="submit" name="delete_posts" value="<?php echo $lang_misc['Delete'] ?>"<?php echo $button_status ?> /></p>
 			<div class="clearer"></div>
 		</div>
+		<ul class="crumbs">
+			<li><a href="index.php"><?php echo $lang_common['Index'] ?></a></li>
+			<li><span>» </span><a href="viewforum.php?id=<?php echo $fid ?>"><?php echo pun_htmlspecialchars($cur_topic['forum_name']) ?></a></li>
+			<li><span>» </span><a href="viewtopic.php?id=<?php echo $tid ?>"><?php echo pun_htmlspecialchars($cur_topic['subject']) ?></a></li>
+			<li><span>» </span><strong><?php echo $lang_misc['Moderate'] ?></strong></li>
+		</ul>
+		<div class="clearer"></div>
 	</div>
 </div>
 </form>
@@ -393,6 +434,11 @@ if (isset($_REQUEST['move_topics']) || isset($_POST['move_topics_to']))
 		if ($db->num_rows($result) != count($topics))
 			message($lang_common['Bad request']);
 
+		// Verify that the move to forum ID is valid
+		$result = $db->query('SELECT 1 FROM '.$db->prefix.'forums AS f LEFT JOIN '.$db->prefix.'forum_perms AS fp ON (fp.group_id='.$pun_user['g_id'].' AND fp.forum_id='.$move_to_forum.') WHERE f.redirect_url IS NULL AND (fp.post_topics IS NULL OR fp.post_topics=1)') or error('Unable to fetch forum permissions', __FILE__, __LINE__, $db->error());
+		if (!$db->num_rows($result))
+			message($lang_common['Bad request']);
+
 		// Delete any redirect topics if there are any (only if we moved/copied the topic back to where it was once moved from)
 		$db->query('DELETE FROM '.$db->prefix.'topics WHERE forum_id='.$move_to_forum.' AND moved_to IN('.implode(',',$topics).')') or error('Unable to delete redirect topics', __FILE__, __LINE__, $db->error());
 
@@ -438,7 +484,7 @@ if (isset($_REQUEST['move_topics']) || isset($_POST['move_topics_to']))
 		$action = 'single';
 	}
 
-	$result = $db->query('SELECT c.id AS cid, c.cat_name, f.id AS fid, f.forum_name FROM '.$db->prefix.'categories AS c INNER JOIN '.$db->prefix.'forums AS f ON c.id=f.cat_id LEFT JOIN '.$db->prefix.'forum_perms AS fp ON (fp.forum_id=f.id AND fp.group_id='.$pun_user['g_id'].') WHERE (fp.read_forum IS NULL OR fp.read_forum=1) AND f.redirect_url IS NULL ORDER BY c.disp_position, c.id, f.disp_position') or error('Unable to fetch category/forum list', __FILE__, __LINE__, $db->error());
+	$result = $db->query('SELECT c.id AS cid, c.cat_name, f.id AS fid, f.forum_name FROM '.$db->prefix.'categories AS c INNER JOIN '.$db->prefix.'forums AS f ON c.id=f.cat_id LEFT JOIN '.$db->prefix.'forum_perms AS fp ON (fp.forum_id=f.id AND fp.group_id='.$pun_user['g_id'].') WHERE (fp.post_topics IS NULL OR fp.post_topics=1) AND f.redirect_url IS NULL ORDER BY c.disp_position, c.id, f.disp_position') or error('Unable to fetch category/forum list', __FILE__, __LINE__, $db->error());
 	if ($db->num_rows($result) < 2)
 		message($lang_misc['Nowhere to move']);
 
@@ -927,12 +973,18 @@ else
 </div>
 
 <div class="linksb">
-	<div class="inbox">
+	<div class="inbox crumbsplus">
 		<div class="pagepost">
 			<p class="pagelink conl"><?php echo $paging_links ?></p>
 			<p class="conr modbuttons"><input type="submit" name="move_topics" value="<?php echo $lang_misc['Move'] ?>"<?php echo $button_status ?> /> <input type="submit" name="delete_topics" value="<?php echo $lang_misc['Delete'] ?>"<?php echo $button_status ?> /> <input type="submit" name="merge_topics" value="<?php echo $lang_misc['Merge'] ?>"<?php echo $button_status ?> /> <input type="submit" name="open" value="<?php echo $lang_misc['Open'] ?>"<?php echo $button_status ?> /> <input type="submit" name="close" value="<?php echo $lang_misc['Close'] ?>"<?php echo $button_status ?> /></p>
 			<div class="clearer"></div>
 		</div>
+		<ul class="crumbs">
+			<li><a href="index.php"><?php echo $lang_common['Index'] ?></a></li>
+			<li><span>» </span><a href="viewforum.php?id=<?php echo $fid ?>"><?php echo pun_htmlspecialchars($cur_forum['forum_name']) ?></a></li>
+			<li><span>» </span><strong><?php echo $lang_misc['Moderate'] ?></strong></li>
+		</ul>
+		<div class="clearer"></div>
 	</div>
 </div>
 </form>
diff --git a/post.php b/post.php
index f15f3d1..c849b14 100644
--- a/post.php
+++ b/post.php
@@ -72,8 +72,13 @@ if (isset($_POST['form_sent']))
 	{
 		$subject = pun_trim($_POST['req_subject']);
 
+		if ($pun_config['o_censoring'] == '1')
+			$censored_subject = pun_trim(censor_words($subject));
+
 		if ($subject == '')
 			$errors[] = $lang_post['No subject'];
+		else if ($pun_config['o_censoring'] == '1' && $censored_subject == '')
+			$errors[] = $lang_post['No subject after censoring'];
 		else if (pun_strlen($subject) > 70)
 			$errors[] = $lang_post['Too long subject'];
 		else if ($pun_config['p_subject_all_caps'] == '0' && is_all_uppercase($subject) && !$pun_user['is_admmod'])
@@ -135,8 +140,19 @@ if (isset($_POST['form_sent']))
 		$message = preparse_bbcode($message, $errors);
 	}
 
-	if (empty($errors) && $message == '')
-		$errors[] = $lang_post['No message'];
+	if (empty($errors))
+	{
+		if ($message == '')
+			$errors[] = $lang_post['No message'];
+		else if ($pun_config['o_censoring'] == '1')
+		{
+			// Censor message to see if that causes problems
+			$censored_message = pun_trim(censor_words($message));
+
+			if ($censored_message == '')
+				$errors[] = $lang_post['No message after censoring'];
+		}
+	}
 
 	$hide_smilies = isset($_POST['hide_smilies']) ? '1' : '0';
 	$subscribe = isset($_POST['subscribe']) ? '1' : '0';
@@ -236,7 +252,7 @@ if (isset($_POST['form_sent']))
 								$mail_subject_full = str_replace('<topic_subject>', $cur_posting['subject'], $mail_subject_full);
 								$mail_message_full = str_replace('<topic_subject>', $cur_posting['subject'], $mail_message_full);
 								$mail_message_full = str_replace('<replier>', $username, $mail_message_full);
-								$mail_message_full = str_replace('<message>', $message, $mail_message_full);
+								$mail_message_full = str_replace('<message>', $pun_config['o_censoring'] == '1' ? $censored_message : $message, $mail_message_full);
 								$mail_message_full = str_replace('<post_url>', get_base_url().'/viewtopic.php?pid='.$new_pid.'#p'.$new_pid, $mail_message_full);
 								$mail_message_full = str_replace('<unsubscribe_url>', get_base_url().'/misc.php?action=unsubscribe&tid='.$tid, $mail_message_full);
 								$mail_message_full = str_replace('<board_mailer>', $pun_config['o_board_title'].' '.$lang_common['Mailer'], $mail_message_full);
@@ -328,7 +344,7 @@ if (isset($_POST['form_sent']))
 								$mail_message_full = trim(substr($mail_tpl_full, $first_crlf));
 
 								$mail_subject = str_replace('<forum_name>', $cur_posting['forum_name'], $mail_subject);
-								$mail_message = str_replace('<topic_subject>', $subject, $mail_message);
+								$mail_message = str_replace('<topic_subject>', $pun_config['o_censoring'] == '1' ? $censored_subject : $subject, $mail_message);
 								$mail_message = str_replace('<forum_name>', $cur_posting['forum_name'], $mail_message);
 								$mail_message = str_replace('<poster>', $username, $mail_message);
 								$mail_message = str_replace('<topic_url>', get_base_url().'/viewtopic.php?id='.$new_tid, $mail_message);
@@ -336,10 +352,10 @@ if (isset($_POST['form_sent']))
 								$mail_message = str_replace('<board_mailer>', $pun_config['o_board_title'].' '.$lang_common['Mailer'], $mail_message);
 
 								$mail_subject_full = str_replace('<forum_name>', $cur_posting['forum_name'], $mail_subject_full);
-								$mail_message_full = str_replace('<topic_subject>', $subject, $mail_message_full);
+								$mail_message_full = str_replace('<topic_subject>', $pun_config['o_censoring'] == '1' ? $censored_subject : $subject, $mail_message_full);
 								$mail_message_full = str_replace('<forum_name>', $cur_posting['forum_name'], $mail_message_full);
 								$mail_message_full = str_replace('<poster>', $username, $mail_message_full);
-								$mail_message_full = str_replace('<message>', $message, $mail_message_full);
+								$mail_message_full = str_replace('<message>', $pun_config['o_censoring'] == '1' ? $censored_message : $message, $mail_message_full);
 								$mail_message_full = str_replace('<topic_url>', get_base_url().'/viewtopic.php?id='.$new_tid, $mail_message_full);
 								$mail_message_full = str_replace('<unsubscribe_url>', get_base_url().'/misc.php?action=unsubscribe&fid='.$cur_posting['id'], $mail_message_full);
 								$mail_message_full = str_replace('<board_mailer>', $pun_config['o_board_title'].' '.$lang_common['Mailer'], $mail_message_full);
@@ -602,7 +618,7 @@ if ($fid): ?>
 						<textarea name="req_message" rows="20" cols="95" tabindex="<?php echo $cur_index++ ?>"><?php echo isset($_POST['req_message']) ? pun_htmlspecialchars($orig_message) : (isset($quote) ? $quote : ''); ?></textarea><br /></label>
 						<ul class="bblinks">
 							<li><span><a href="help.php#bbcode" onclick="window.open(this.href); return false;"><?php echo $lang_common['BBCode'] ?></a> <?php echo ($pun_config['p_message_bbcode'] == '1') ? $lang_common['on'] : $lang_common['off']; ?></span></li>
-							<li><span><a href="help.php#img" onclick="window.open(this.href); return false;"><?php echo $lang_common['img tag'] ?></a> <?php echo ($pun_config['p_message_img_tag'] == '1') ? $lang_common['on'] : $lang_common['off']; ?></span></li>
+							<li><span><a href="help.php#img" onclick="window.open(this.href); return false;"><?php echo $lang_common['img tag'] ?></a> <?php echo ($pun_config['p_message_bbcode'] == '1' && $pun_config['p_message_img_tag'] == '1') ? $lang_common['on'] : $lang_common['off']; ?></span></li>
 							<li><span><a href="help.php#smilies" onclick="window.open(this.href); return false;"><?php echo $lang_common['Smilies'] ?></a> <?php echo ($pun_config['o_smilies'] == '1') ? $lang_common['on'] : $lang_common['off']; ?></span></li>
 						</ul>
 					</div>
diff --git a/profile.php b/profile.php
index ce9814c..8102a03 100644
--- a/profile.php
+++ b/profile.php
@@ -444,6 +444,12 @@ else if (isset($_POST['update_group_membership']))
 
 	$db->query('UPDATE '.$db->prefix.'users SET group_id='.$new_group_id.' WHERE id='.$id) or error('Unable to change user group', __FILE__, __LINE__, $db->error());
 
+	// Regenerate the users info cache
+	if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+		require PUN_ROOT.'include/cache.php';
+
+	generate_users_info_cache();
+
 	$result = $db->query('SELECT g_moderator FROM '.$db->prefix.'groups WHERE g_id='.$new_group_id) or error('Unable to fetch group', __FILE__, __LINE__, $db->error());
 	$new_group_mod = $db->result($result);
 
@@ -600,6 +606,12 @@ else if (isset($_POST['delete_user']) || isset($_POST['delete_user_comply']))
 		// Delete user avatar
 		delete_avatar($id);
 
+		// Regenerate the users info cache
+		if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+			require PUN_ROOT.'include/cache.php';
+
+		generate_users_info_cache();
+
 		redirect('index.php', $lang_profile['User delete redirect']);
 	}
 
@@ -918,7 +930,7 @@ else if (isset($_POST['form_sent']))
 }
 
 
-$result = $db->query('SELECT u.username, u.email, u.title, u.realname, u.url, u.jabber, u.icq, u.msn, u.aim, u.yahoo, u.location, u.signature, u.disp_topics, u.disp_posts, u.email_setting, u.notify_with_post, u.auto_notify, u.show_smilies, u.show_img, u.show_img_sig, u.show_avatars, u.show_sig, u.timezone, u.dst, u.language, u.style, u.num_posts, u.last_post, u.registered, u.registration_ip, u.admin_note, u.date_format, u.time_format, g.g_id, g.g_user_title, g.g_moderator FROM '.$db->prefix.'users AS u LEFT JOIN '.$db->prefix.'groups AS g ON g.g_id=u.group_id WHERE u.id='.$id) or error('Unable to fetch user info', __FILE__, __LINE__, $db->error());
+$result = $db->query('SELECT u.username, u.email, u.title, u.realname, u.url, u.jabber, u.icq, u.msn, u.aim, u.yahoo, u.location, u.signature, u.disp_topics, u.disp_posts, u.email_setting, u.notify_with_post, u.auto_notify, u.show_smilies, u.show_img, u.show_img_sig, u.show_avatars, u.show_sig, u.timezone, u.dst, u.language, u.style, u.num_posts, u.last_post, u.registered, u.registration_ip, u.admin_note, u.date_format, u.time_format, u.last_visit, g.g_id, g.g_user_title, g.g_moderator FROM '.$db->prefix.'users AS u LEFT JOIN '.$db->prefix.'groups AS g ON g.g_id=u.group_id WHERE u.id='.$id) or error('Unable to fetch user info', __FILE__, __LINE__, $db->error());
 if (!$db->num_rows($result))
 	message($lang_common['Bad request']);
 
@@ -1049,7 +1061,7 @@ if ($pun_user['id'] != $id &&																	// If we arent the user (i.e. edit
 		}
 		if ($pun_user['is_admmod'] && $pun_config['o_topic_subscriptions'] == '1')
 			$quick_searches[] = '<a href="search.php?action=show_subscriptions&user_id='.$id.'">'.$lang_profile['Show subscriptions'].'</a>';
-		
+
 		if (!empty($quick_searches))
 			$posts_field .= (($posts_field != '') ? ' - ' : '').implode(' - ', $quick_searches);
 	}
@@ -1328,6 +1340,7 @@ else
 						<div class="infldset">
 							<p><?php printf($lang_profile['Registered info'], format_time($user['registered'], true).(($pun_user['is_admmod']) ? ' (<a href="moderate.php?get_host='.pun_htmlspecialchars($user['registration_ip']).'">'.pun_htmlspecialchars($user['registration_ip']).'</a>)' : '')) ?></p>
 							<p><?php printf($lang_profile['Last post info'], $last_post) ?></p>
+							<p><?php printf($lang_profile['Last visit info'], format_time($user['last_visit'])) ?></p>
 							<?php echo $posts_field ?>
 <?php if ($pun_user['is_admmod']): ?>							<label><?php echo $lang_profile['Admin note'] ?><br />
 							<input id="admin_note" type="text" name="admin_note" value="<?php echo pun_htmlspecialchars($user['admin_note']) ?>" size="30" maxlength="30" /><br /></label>
@@ -1470,7 +1483,7 @@ else
 							</div>
 							<ul class="bblinks">
 								<li><span><a href="help.php#bbcode" onclick="window.open(this.href); return false;"><?php echo $lang_common['BBCode'] ?></a> <?php echo ($pun_config['p_sig_bbcode'] == '1') ? $lang_common['on'] : $lang_common['off']; ?></span></li>
-								<li><span><a href="help.php#img" onclick="window.open(this.href); return false;"><?php echo $lang_common['img tag'] ?></a> <?php echo ($pun_config['p_sig_img_tag'] == '1') ? $lang_common['on'] : $lang_common['off']; ?></span></li>
+								<li><span><a href="help.php#img" onclick="window.open(this.href); return false;"><?php echo $lang_common['img tag'] ?></a> <?php echo ($pun_config['p_sig_bbcode'] == '1' && $pun_config['p_sig_img_tag'] == '1') ? $lang_common['on'] : $lang_common['off']; ?></span></li>
 								<li><span><a href="help.php#smilies" onclick="window.open(this.href); return false;"><?php echo $lang_common['Smilies'] ?></a> <?php echo ($pun_config['o_smilies_sig'] == '1') ? $lang_common['on'] : $lang_common['off']; ?></span></li>
 							</ul>
 							<?php echo $signature_preview ?>
@@ -1536,7 +1549,7 @@ else
 		}
 
 ?>
-<?php if ($pun_config['o_smilies'] == '1' || $pun_config['o_smilies_sig'] == '1' || $pun_config['o_signatures'] == '1' || $pun_config['o_avatars'] == '1' || $pun_config['p_message_img_tag'] == '1' || $pun_config['p_sig_img_tag'] == '1'): ?>
+<?php if ($pun_config['o_smilies'] == '1' || $pun_config['o_smilies_sig'] == '1' || $pun_config['o_signatures'] == '1' || $pun_config['o_avatars'] == '1' || ($pun_config['p_message_bbcode'] == '1' && $pun_config['p_message_img_tag'] == '1')): ?>
 				<div class="inform">
 					<fieldset>
 						<legend><?php echo $lang_profile['Post display legend'] ?></legend>
@@ -1546,8 +1559,8 @@ else
 <?php if ($pun_config['o_smilies'] == '1' || $pun_config['o_smilies_sig'] == '1'): ?>								<label><input type="checkbox" name="form[show_smilies]" value="1"<?php if ($user['show_smilies'] == '1') echo ' checked="checked"' ?> /><?php echo $lang_profile['Show smilies'] ?><br /></label>
 <?php endif; if ($pun_config['o_signatures'] == '1'): ?>								<label><input type="checkbox" name="form[show_sig]" value="1"<?php if ($user['show_sig'] == '1') echo ' checked="checked"' ?> /><?php echo $lang_profile['Show sigs'] ?><br /></label>
 <?php endif; if ($pun_config['o_avatars'] == '1'): ?>								<label><input type="checkbox" name="form[show_avatars]" value="1"<?php if ($user['show_avatars'] == '1') echo ' checked="checked"' ?> /><?php echo $lang_profile['Show avatars'] ?><br /></label>
-<?php endif; if ($pun_config['p_message_img_tag'] == '1'): ?>								<label><input type="checkbox" name="form[show_img]" value="1"<?php if ($user['show_img'] == '1') echo ' checked="checked"' ?> /><?php echo $lang_profile['Show images'] ?><br /></label>
-<?php endif; if ($pun_config['p_sig_img_tag'] == '1'): ?>								<label><input type="checkbox" name="form[show_img_sig]" value="1"<?php if ($user['show_img_sig'] == '1') echo ' checked="checked"' ?> /><?php echo $lang_profile['Show images sigs'] ?><br /></label>
+<?php endif; if ($pun_config['p_message_bbcode'] == '1' && $pun_config['p_message_img_tag'] == '1'): ?>								<label><input type="checkbox" name="form[show_img]" value="1"<?php if ($user['show_img'] == '1') echo ' checked="checked"' ?> /><?php echo $lang_profile['Show images'] ?><br /></label>
+<?php endif; if ($pun_config['o_signatures'] == '1' && $pun_config['p_sig_bbcode'] == '1' && $pun_config['p_sig_img_tag'] == '1'): ?>								<label><input type="checkbox" name="form[show_img_sig]" value="1"<?php if ($user['show_img_sig'] == '1') echo ' checked="checked"' ?> /><?php echo $lang_profile['Show images sigs'] ?><br /></label>
 <?php endif; ?>
 							</div>
 						</div>
@@ -1632,7 +1645,7 @@ else
 	<div class="blockform">
 		<h2><span><?php echo pun_htmlspecialchars($user['username']).' - '.$lang_profile['Section admin'] ?></span></h2>
 		<div class="box">
-			<form id="profile7" method="post" action="profile.php?section=admin&id=<?php echo $id ?>&action=foo">
+			<form id="profile7" method="post" action="profile.php?section=admin&id=<?php echo $id ?>">
 				<div class="inform">
 				<input type="hidden" name="form_sent" value="1" />
 					<fieldset>
diff --git a/register.php b/register.php
index 123836e..c99c99b 100644
--- a/register.php
+++ b/register.php
@@ -223,6 +223,12 @@ if (isset($_POST['form_sent']))
 			message($lang_register['Reg email'].' <a href="mailto:'.$pun_config['o_admin_email'].'">'.$pun_config['o_admin_email'].'</a>.', true);
 		}
 
+		// Regenerate the users info cache
+		if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+			require PUN_ROOT.'include/cache.php';
+
+		generate_users_info_cache();
+
 		pun_setcookie($new_uid, $password_hash, time() + $pun_config['o_timeout_visit']);
 
 		redirect('index.php', $lang_register['Reg complete']);
diff --git a/search.php b/search.php
index 141c2f5..bdbd71b 100644
--- a/search.php
+++ b/search.php
@@ -283,7 +283,7 @@ if (isset($_GET['action']) || isset($_GET['search_id']))
 			if ($author && $keywords)
 			{
 				$search_ids = array_intersect_assoc($keyword_results, $author_results);
-				$search_type = array('both', array($keywords, $author), $forum, isset($_GET['search_in']) ? $_GET['search_in'] : '');
+				$search_type = array('both', array($keywords, pun_trim($_GET['author'])), $forum, isset($_GET['search_in']) ? $_GET['search_in'] : '');
 			}
 			else if ($keywords)
 			{
@@ -293,7 +293,7 @@ if (isset($_GET['action']) || isset($_GET['search_id']))
 			else
 			{
 				$search_ids = $author_results;
-				$search_type = array('author', $author, $forum, isset($_GET['search_in']) ? $_GET['search_in'] : '');
+				$search_type = array('author', pun_trim($_GET['author']), $forum, isset($_GET['search_in']) ? $_GET['search_in'] : '');
 			}
 
 			unset($keyword_results, $author_results);
@@ -343,7 +343,7 @@ if (isset($_GET['action']) || isset($_GET['search_id']))
 			{
 				$result = $db->query('SELECT t.id FROM '.$db->prefix.'topics AS t INNER JOIN '.$db->prefix.'posts AS p ON t.id=p.topic_id LEFT JOIN '.$db->prefix.'forum_perms AS fp ON (fp.forum_id=t.forum_id AND fp.group_id='.$pun_user['g_id'].') WHERE (fp.read_forum IS NULL OR fp.read_forum=1) AND p.poster_id='.$pun_user['id'].' GROUP BY t.id'.($db_type == 'pgsql' ? ', t.last_post' : '').' ORDER BY t.last_post DESC') or error('Unable to fetch topic list', __FILE__, __LINE__, $db->error());
 				$num_hits = $db->num_rows($result);
-				
+
 				if (!$num_hits)
 					message($lang_search['No user posts']);
 			}
@@ -529,17 +529,17 @@ if (isset($_GET['action']) || isset($_GET['search_id']))
 			if ($search_type[0] == 'both')
 			{
 				list ($keywords, $author) = $search_type[1];
-				$crumbs_text['search_type'] = sprintf($lang_search['By both'], pun_htmlspecialchars($keywords), pun_htmlspecialchars($author));
+				$crumbs_text['search_type'] = sprintf($lang_search['By both show as '.$show_as], pun_htmlspecialchars($keywords), pun_htmlspecialchars($author));
 			}
 			else if ($search_type[0] == 'keywords')
 			{
 				$keywords = $search_type[1];
-				$crumbs_text['search_type'] = sprintf($lang_search['By keywords'], pun_htmlspecialchars($keywords));
+				$crumbs_text['search_type'] = sprintf($lang_search['By keywords show as '.$show_as], pun_htmlspecialchars($keywords));
 			}
 			else if ($search_type[0] == 'author')
 			{
 				$author = $search_type[1];
-				$crumbs_text['search_type'] = sprintf($lang_search['By user'], pun_htmlspecialchars($author));
+				$crumbs_text['search_type'] = sprintf($lang_search['By user show as '.$show_as], pun_htmlspecialchars($author));
 			}
 
 			$crumbs_text['search_type'] = '<a href="search.php?action=search&keywords='.pun_htmlspecialchars($keywords).'&author='.pun_htmlspecialchars($author).'&forum='.pun_htmlspecialchars($search_type[2]).'&search_in='.pun_htmlspecialchars($search_type[3]).'&sort_by='.pun_htmlspecialchars($sort_by).'&sort_dir='.pun_htmlspecialchars($sort_dir).'&show_as='.pun_htmlspecialchars($show_as).'">'.$crumbs_text['search_type'].'</a>';
diff --git a/style/Air.css b/style/Air.css
index 50de49d..59614e9 100644
--- a/style/Air.css
+++ b/style/Air.css
@@ -1185,7 +1185,6 @@ MAIN FORMS
 
 .pun #quickpost .txtarea {
 	padding-right: 12px;
-	padding-left: 200px;
 	position: relative;
 }
 
@@ -1205,15 +1204,11 @@ MAIN FORMS
 }
 
 .pun #quickpost .bblinks {
-	left: 18px;
-	line-height: 1.75em;
-	position:absolute;
-	top: 18px;
-	width: 12em;
+	padding-top: 0;
 }
 
 .pun #quickpost .bblinks li {
-	display: block;
+	display: inline;
 }
 
 .pun #login p.clearb {
diff --git a/style/Air/base_admin.css b/style/Air/base_admin.css
index 60a064e..45f192a 100644
--- a/style/Air/base_admin.css
+++ b/style/Air/base_admin.css
@@ -113,12 +113,16 @@
 
 #punadmin #users1 th, #punadmin #users2 th, #punadmin #bans1 th {
 	font-weight: bold;
-	}
+}
 
 #users2 th, #bans1 th {
 	text-align: left;
 }
 
+#users2 th.tcmod {
+	text-align: center;
+}
+
 #users2 .tcl, #users2 .tc3, #users2 .tc5, #bans1 .tcl, #bans1 .tc3, #bans1 .tc5, #bans1 .tc6 {
 	width: 15%;
 	text-align: left;
diff --git a/style/Earth.css b/style/Earth.css
index 40427ae..7c20aec 100644
--- a/style/Earth.css
+++ b/style/Earth.css
@@ -1185,7 +1185,6 @@ MAIN FORMS
 
 .pun #quickpost .txtarea {
 	padding-right: 12px;
-	padding-left: 200px;
 	position: relative;
 }
 
@@ -1205,15 +1204,11 @@ MAIN FORMS
 }
 
 .pun #quickpost .bblinks {
-	left: 18px;
-	line-height: 1.75em;
-	position:absolute;
-	top: 18px;
-	width: 12em;
+	padding-top: 0;
 }
 
 .pun #quickpost .bblinks li {
-	display: block;
+	display: inline;
 }
 
 .pun #login p.clearb {
diff --git a/style/Earth/base_admin.css b/style/Earth/base_admin.css
index 60a064e..45f192a 100644
--- a/style/Earth/base_admin.css
+++ b/style/Earth/base_admin.css
@@ -113,12 +113,16 @@
 
 #punadmin #users1 th, #punadmin #users2 th, #punadmin #bans1 th {
 	font-weight: bold;
-	}
+}
 
 #users2 th, #bans1 th {
 	text-align: left;
 }
 
+#users2 th.tcmod {
+	text-align: center;
+}
+
 #users2 .tcl, #users2 .tc3, #users2 .tc5, #bans1 .tcl, #bans1 .tc3, #bans1 .tc5, #bans1 .tc6 {
 	width: 15%;
 	text-align: left;
diff --git a/style/Fire.css b/style/Fire.css
index 0050f97..c4951dd 100644
--- a/style/Fire.css
+++ b/style/Fire.css
@@ -1185,7 +1185,6 @@ MAIN FORMS
 
 .pun #quickpost .txtarea {
 	padding-right: 12px;
-	padding-left: 200px;
 	position: relative;
 }
 
@@ -1205,15 +1204,11 @@ MAIN FORMS
 }
 
 .pun #quickpost .bblinks {
-	left: 18px;
-	line-height: 1.75em;
-	position:absolute;
-	top: 18px;
-	width: 12em;
+	padding-top: 0;
 }
 
 .pun #quickpost .bblinks li {
-	display: block;
+	display: inline;
 }
 
 .pun #login p.clearb {
diff --git a/style/Fire/base_admin.css b/style/Fire/base_admin.css
index 60a064e..45f192a 100644
--- a/style/Fire/base_admin.css
+++ b/style/Fire/base_admin.css
@@ -113,12 +113,16 @@
 
 #punadmin #users1 th, #punadmin #users2 th, #punadmin #bans1 th {
 	font-weight: bold;
-	}
+}
 
 #users2 th, #bans1 th {
 	text-align: left;
 }
 
+#users2 th.tcmod {
+	text-align: center;
+}
+
 #users2 .tcl, #users2 .tc3, #users2 .tc5, #bans1 .tcl, #bans1 .tc3, #bans1 .tc5, #bans1 .tc6 {
 	width: 15%;
 	text-align: left;
diff --git a/style/imports/base_admin.css b/style/imports/base_admin.css
index efb51f4..3c3c966 100644
--- a/style/imports/base_admin.css
+++ b/style/imports/base_admin.css
@@ -37,6 +37,7 @@ TABLE#forumperms .atcl {TEXT-ALIGN: left; WIDTH: 15em; WHITE-SPACE: nowrap}
 
 /*** User/Ban Search Result Tables ***/
 #users2 TH, #bans1 TH {TEXT-ALIGN: left}
+#users2 TH.tcmod {TEXT-ALIGN: center}
 #users2 .tcl, #users2 .tc3, #users2 .tc5, #bans1 .tcl, #bans1 .tc3, #bans1 .tc5, #bans1 .tc6 {WIDTH: 15%; TEXT-ALIGN: left; PADDING: 4px 6px}
 #users2 .tc2, #bans1 .tc2 {WIDTH: 22%; TEXT-ALIGN: left; PADDING: 4px 6px}
 #users2 .tc4, #bans1 .tc4 {WIDTH: 8%; TEXT-ALIGN: center}
diff --git a/viewtopic.php b/viewtopic.php
index 9941d33..b77f08c 100644
--- a/viewtopic.php
+++ b/viewtopic.php
@@ -225,11 +225,13 @@ if ($pun_config['o_censoring'] == '1')
 
 $quickpost = false;
 if ($pun_config['o_quickpost'] == '1' &&
-	!$pun_user['is_guest'] &&
 	($cur_topic['post_replies'] == '1' || ($cur_topic['post_replies'] == '' && $pun_user['g_post_replies'] == '1')) &&
 	($cur_topic['closed'] == '0' || $is_admmod))
 {
-	$required_fields = array('req_message' => $lang_common['Message']);
+	// Load the post.php language file
+	require PUN_ROOT.'lang/'.$pun_user['language'].'/post.php';
+
+	$required_fields = array('req_email' => $lang_common['Email'], 'req_message' => $lang_common['Message']);
 	$quickpost = true;
 }
 
@@ -479,6 +481,8 @@ while ($cur_post = $db->fetch_assoc($result))
 if ($quickpost)
 {
 
+$cur_index = 1;
+
 ?>
 <div id="quickpost" class="blockform">
 	<h2><span><?php echo $lang_topic['Quick post'] ?></span></h2>
@@ -491,16 +495,36 @@ if ($quickpost)
 						<input type="hidden" name="form_sent" value="1" />
 						<input type="hidden" name="form_user" value="<?php echo pun_htmlspecialchars($pun_user['username']) ?>" />
 <?php if ($pun_config['o_topic_subscriptions'] == '1' && ($pun_user['auto_notify'] == '1' || $cur_topic['is_subscribed'])): ?>						<input type="hidden" name="subscribe" value="1" />
-<?php endif; ?>						<label><textarea name="req_message" rows="7" cols="75" tabindex="1"></textarea></label>
+<?php endif; ?>
+<?php
+
+if ($pun_user['is_guest'])
+{
+	$email_label = ($pun_config['p_force_guest_email'] == '1') ? '<strong>'.$lang_common['Email'].' <span>'.$lang_common['Required'].'</span></strong>' : $lang_common['Email'];
+	$email_form_name = ($pun_config['p_force_guest_email'] == '1') ? 'req_email' : 'email';
+
+?>
+						<label class="conl required"><strong><?php echo $lang_post['Guest name'] ?> <span><?php echo $lang_common['Required'] ?></span></strong><br /><input type="text" name="req_username" value="<?php if (isset($_POST['req_username'])) echo pun_htmlspecialchars($username); ?>" size="25" maxlength="25" tabindex="<?php echo $cur_index++ ?>" /><br /></label>
+						<label class="conl<?php echo ($pun_config['p_force_guest_email'] == '1') ? ' required' : '' ?>"><?php echo $email_label ?><br /><input type="text" name="<?php echo $email_form_name ?>" value="<?php if (isset($_POST[$email_form_name])) echo pun_htmlspecialchars($email); ?>" size="50" maxlength="80" tabindex="<?php echo $cur_index++ ?>" /><br /></label>
+						<div class="clearer"></div>
+<?php
+
+	echo "\t\t\t\t\t\t".'<label class="required"><strong>'.$lang_common['Message'].' <span>'.$lang_common['Required'].'</span></strong><br />';
+}
+else
+	echo "\t\t\t\t\t\t".'<label>';
+
+?>
+<textarea name="req_message" rows="7" cols="75" tabindex="<?php echo $cur_index++ ?>"></textarea></label>
 						<ul class="bblinks">
 							<li><span><a href="help.php#bbcode" onclick="window.open(this.href); return false;"><?php echo $lang_common['BBCode'] ?></a> <?php echo ($pun_config['p_message_bbcode'] == '1') ? $lang_common['on'] : $lang_common['off']; ?></span></li>
-							<li><span><a href="help.php#img" onclick="window.open(this.href); return false;"><?php echo $lang_common['img tag'] ?></a> <?php echo ($pun_config['p_message_img_tag'] == '1') ? $lang_common['on'] : $lang_common['off']; ?></span></li>
+							<li><span><a href="help.php#img" onclick="window.open(this.href); return false;"><?php echo $lang_common['img tag'] ?></a> <?php echo ($pun_config['p_message_bbcode'] == '1' && $pun_config['p_message_img_tag'] == '1') ? $lang_common['on'] : $lang_common['off']; ?></span></li>
 							<li><span><a href="help.php#smilies" onclick="window.open(this.href); return false;"><?php echo $lang_common['Smilies'] ?></a> <?php echo ($pun_config['o_smilies'] == '1') ? $lang_common['on'] : $lang_common['off']; ?></span></li>
 						</ul>
 					</div>
 				</fieldset>
 			</div>
-			<p class="buttons"><input type="submit" name="submit" tabindex="2" value="<?php echo $lang_common['Submit'] ?>" accesskey="s" /> <input type="submit" name="preview" value="<?php echo $lang_topic['Preview'] ?>" tabindex="3" accesskey="p" /></p>
+			<p class="buttons"><input type="submit" name="submit" tabindex="<?php echo $cur_index++ ?>" value="<?php echo $lang_common['Submit'] ?>" accesskey="s" /> <input type="submit" name="preview" value="<?php echo $lang_topic['Preview'] ?>" tabindex="<?php echo $cur_index++ ?>" accesskey="p" /></p>
 		</form>
 	</div>
 </div>



More information about the Xfce4-commits mailing list