diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..8359151
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+.gitattributes	export-ignore
+.gitignore	export-ignore
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..194371a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+/*.sublime-project
+/.project
+/.settings/
+/nbproject/
+*.sublime-project
+/cache/cache_*.php
+/cache/*.cache
+/cache/db_update.lock
+/modules/
+/include/user/*.php
+/img/avatars/*.png
+/img/avatars/*.jpg
+/img/avatars/*.gif
+/config.php
diff --git a/README b/README
new file mode 100644
index 0000000..d40b397
--- /dev/null
+++ b/README
@@ -0,0 +1,31 @@
+	=====================
+	  FluxBB 1.4 Readme
+	=====================
+
+	   About
+	----------------
+FluxBB is an open source forum application released under the GNU General Public
+Licence. It is free to download and use and will remain so. FluxBB was conceived and
+designed to be fast and light with less of the "not so essential" features that some
+of the other forums have whilst not sacrificing essential functionality or usability.
+
+	  Requirements
+	----------------
+ - A webserver
+ - PHP 4.4.0 or later
+ - A database such as MySQL 4.1.2 or later, PostgreSQL 7.0 or later, or SQLite 2
+
+	  Recommendations
+	-------------------
+ - Make use of a PHP accelerator such as APC or XCache
+ - Make sure PHP has the zlib module installed to allow FluxBB to gzip output
+
+	  Links
+	---------
+
+ - Homepage: http://fluxbb.org
+ - Documentation: http://fluxbb.org/docs/
+ - Community: http://fluxbb.org/forums/
+ - Resources: http://fluxbb.org/resources/
+ - IRC: irc://irc.freenode.net/fluxbb
+ - Development: http://github.com/fluxbb/fluxbb
diff --git a/admin_bans.php b/admin_bans.php
new file mode 100644
index 0000000..c48320d
--- /dev/null
+++ b/admin_bans.php
@@ -0,0 +1,569 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// Tell header.php to use the admin template
+define('PUN_ADMIN_CONSOLE', 1);
+
+define('PUN_ROOT', dirname(__FILE__).'/');
+require PUN_ROOT.'include/common.php';
+require PUN_ROOT.'include/common_admin.php';
+
+
+if ($pun_user['g_id'] != PUN_ADMIN && ($pun_user['g_moderator'] != '1' || $pun_user['g_mod_ban_users'] == '0'))
+	message($lang_common['No permission'], false, '403 Forbidden');
+
+// Load the admin_bans.php language file
+require PUN_ROOT.'lang/'.$admin_language.'/admin_bans.php';
+
+// Add/edit a ban (stage 1)
+if (isset($_REQUEST['add_ban']) || isset($_GET['edit_ban']))
+{
+	if (isset($_GET['add_ban']) || isset($_POST['add_ban']))
+	{
+		// If the ID of the user to ban was provided through GET (a link from profile.php)
+		if (isset($_GET['add_ban']))
+		{
+			$user_id = intval($_GET['add_ban']);
+			if ($user_id < 2)
+				message($lang_common['Bad request']);
+
+			$result = $db->query('SELECT group_id, username, email FROM '.$db->prefix.'users WHERE id='.$user_id) or error('Unable to fetch user info', __FILE__, __LINE__, $db->error());
+			if ($db->num_rows($result))
+				list($group_id, $ban_user, $ban_email) = $db->fetch_row($result);
+			else
+				message($lang_admin_bans['No user ID message']);
+		}
+		else // Otherwise the username is in POST
+		{
+			$ban_user = pun_trim($_POST['new_ban_user']);
+
+			if ($ban_user != '')
+			{
+				$result = $db->query('SELECT id, group_id, username, email 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))
+					list($user_id, $group_id, $ban_user, $ban_email) = $db->fetch_row($result);
+				else
+					message($lang_admin_bans['No user message']);
+			}
+		}
+
+		// 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))
+		{
+			$result = $db->query('SELECT poster_ip FROM '.$db->prefix.'posts WHERE poster_id='.$user_id.' ORDER BY posted DESC LIMIT 1') or error('Unable to fetch post info', __FILE__, __LINE__, $db->error());
+			$ban_ip = ($db->num_rows($result)) ? $db->result($result) : '';
+
+			if ($ban_ip == '')
+			{
+				$result = $db->query('SELECT registration_ip FROM '.$db->prefix.'users WHERE id='.$user_id) or error('Unable to fetch user info', __FILE__, __LINE__, $db->error());
+				$ban_ip = ($db->num_rows($result)) ? $db->result($result) : '';
+			}
+		}
+
+		$mode = 'add';
+	}
+	else // We are editing a ban
+	{
+		$ban_id = intval($_GET['edit_ban']);
+		if ($ban_id < 1)
+			message($lang_common['Bad request']);
+
+		$result = $db->query('SELECT username, ip, email, message, expire FROM '.$db->prefix.'bans WHERE id='.$ban_id) or error('Unable to fetch ban info', __FILE__, __LINE__, $db->error());
+		if ($db->num_rows($result))
+			list($ban_user, $ban_ip, $ban_email, $ban_message, $ban_expire) = $db->fetch_row($result);
+		else
+			message($lang_common['Bad request']);
+
+		$diff = ($pun_user['timezone'] + $pun_user['dst']) * 3600;
+		$ban_expire = ($ban_expire != '') ? gmdate('Y-m-d', $ban_expire + $diff) : '';
+
+		$mode = 'edit';
+	}
+
+	$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_admin_common['Admin'], $lang_admin_common['Bans']);
+	$focus_element = array('bans2', 'ban_user');
+	define('PUN_ACTIVE_PAGE', 'admin');
+	require PUN_ROOT.'header.php';
+
+	generate_admin_menu('bans');
+
+?>
+	<div class="blockform">
+		<h2><span><?php echo $lang_admin_bans['Ban advanced head'] ?></span></h2>
+		<div class="box">
+			<form id="bans2" method="post" action="admin_bans.php">
+				<div class="inform">
+				<input type="hidden" name="mode" value="<?php echo $mode ?>" />
+<?php if ($mode == 'edit'): ?>				<input type="hidden" name="ban_id" value="<?php echo $ban_id ?>" />
+<?php endif; ?>				<fieldset>
+						<legend><?php echo $lang_admin_bans['Ban advanced subhead'] ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_bans['Username label'] ?></th>
+									<td>
+										<input type="text" name="ban_user" size="25" maxlength="25" value="<?php if (isset($ban_user)) echo pun_htmlspecialchars($ban_user); ?>" tabindex="1" />
+										<span><?php echo $lang_admin_bans['Username help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_bans['IP label'] ?></th>
+									<td>
+										<input type="text" name="ban_ip" size="45" maxlength="255" value="<?php if (isset($ban_ip)) echo pun_htmlspecialchars($ban_ip); ?>" tabindex="2" />
+										<span><?php echo $lang_admin_bans['IP help'] ?><?php if ($ban_user != '' && isset($user_id)) printf(' '.$lang_admin_bans['IP help link'], '<a href="admin_users.php?ip_stats='.$user_id.'">'.$lang_admin_common['here'].'</a>') ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_bans['E-mail label'] ?></th>
+									<td>
+										<input type="text" name="ban_email" size="40" maxlength="80" value="<?php if (isset($ban_email)) echo $ban_email; ?>" tabindex="3" />
+										<span><?php echo $lang_admin_bans['E-mail help'] ?></span>
+									</td>
+								</tr>
+							</table>
+							<p class="topspace"><strong class="warntext"><?php echo $lang_admin_bans['Ban IP range info'] ?></strong></p>
+						</div>
+					</fieldset>
+				</div>
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_bans['Message expiry subhead'] ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_bans['Ban message label'] ?></th>
+									<td>
+										<input type="text" name="ban_message" size="50" maxlength="255" value="<?php if (isset($ban_message)) echo pun_htmlspecialchars($ban_message); ?>" tabindex="4" />
+										<span><?php echo $lang_admin_bans['Ban message help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_bans['Expire date label'] ?></th>
+									<td>
+										<input type="text" name="ban_expire" size="17" maxlength="10" value="<?php if (isset($ban_expire)) echo $ban_expire; ?>" tabindex="5" />
+										<span><?php echo $lang_admin_bans['Expire date help'] ?></span>
+									</td>
+								</tr>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+				<p class="submitend"><input type="submit" name="add_edit_ban" value="<?php echo $lang_admin_common['Save'] ?>" tabindex="6" /></p>
+			</form>
+		</div>
+	</div>
+	<div class="clearer"></div>
+</div>
+<?php
+
+	require PUN_ROOT.'footer.php';
+}
+
+// Add/edit a ban (stage 2)
+else if (isset($_POST['add_edit_ban']))
+{
+	confirm_referrer('admin_bans.php');
+
+	$ban_user = pun_trim($_POST['ban_user']);
+	$ban_ip = pun_trim($_POST['ban_ip']);
+	$ban_email = strtolower(pun_trim($_POST['ban_email']));
+	$ban_message = pun_trim($_POST['ban_message']);
+	$ban_expire = pun_trim($_POST['ban_expire']);
+
+	if ($ban_user == '' && $ban_ip == '' && $ban_email == '')
+		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 != '')
+	{
+		$ban_ip = preg_replace('%\s{2,}%S', ' ', $ban_ip);
+		$addresses = explode(' ', $ban_ip);
+		$addresses = array_map('pun_trim', $addresses);
+
+		for ($i = 0; $i < count($addresses); ++$i)
+		{
+			if (strpos($addresses[$i], ':') !== false)
+			{
+				$octets = explode(':', $addresses[$i]);
+
+				for ($c = 0; $c < count($octets); ++$c)
+				{
+					$octets[$c] = ltrim($octets[$c], "0");
+
+					if ($c > 7 || (!empty($octets[$c]) && !ctype_xdigit($octets[$c])) || intval($octets[$c], 16) > 65535)
+						message($lang_admin_bans['Invalid IP message']);
+				}
+
+				$cur_address = implode(':', $octets);
+				$addresses[$i] = $cur_address;
+			}
+			else
+			{
+				$octets = explode('.', $addresses[$i]);
+
+				for ($c = 0; $c < count($octets); ++$c)
+				{
+					$octets[$c] = (strlen($octets[$c]) > 1) ? ltrim($octets[$c], "0") : $octets[$c];
+
+					if ($c > 3 || preg_match('%[^0-9]%', $octets[$c]) || intval($octets[$c]) > 255)
+						message($lang_admin_bans['Invalid IP message']);
+				}
+
+				$cur_address = implode('.', $octets);
+				$addresses[$i] = $cur_address;
+			}
+		}
+
+		$ban_ip = implode(' ', $addresses);
+	}
+
+	require PUN_ROOT.'include/email.php';
+	if ($ban_email != '' && !is_valid_email($ban_email))
+	{
+		if (!preg_match('%^[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$%', $ban_email))
+			message($lang_admin_bans['Invalid e-mail message']);
+	}
+
+	if ($ban_expire != '' && $ban_expire != 'Never')
+	{
+		$ban_expire = strtotime($ban_expire.' GMT');
+
+		if ($ban_expire == -1 || !$ban_expire)
+			message($lang_admin_bans['Invalid date message'].' '.$lang_admin_bans['Invalid date reasons']);
+
+		$diff = ($pun_user['timezone'] + $pun_user['dst']) * 3600;
+		$ban_expire -= $diff;
+
+		if ($ban_expire <= time())
+			message($lang_admin_bans['Invalid date message'].' '.$lang_admin_bans['Invalid date reasons']);
+	}
+	else
+		$ban_expire = 'NULL';
+
+	$ban_user = ($ban_user != '') ? '\''.$db->escape($ban_user).'\'' : 'NULL';
+	$ban_ip = ($ban_ip != '') ? '\''.$db->escape($ban_ip).'\'' : 'NULL';
+	$ban_email = ($ban_email != '') ? '\''.$db->escape($ban_email).'\'' : 'NULL';
+	$ban_message = ($ban_message != '') ? '\''.$db->escape($ban_message).'\'' : 'NULL';
+
+	if ($_POST['mode'] == 'add')
+		$db->query('INSERT INTO '.$db->prefix.'bans (username, ip, email, message, expire, ban_creator) VALUES('.$ban_user.', '.$ban_ip.', '.$ban_email.', '.$ban_message.', '.$ban_expire.', '.$pun_user['id'].')') or error('Unable to add ban', __FILE__, __LINE__, $db->error());
+	else
+		$db->query('UPDATE '.$db->prefix.'bans SET username='.$ban_user.', ip='.$ban_ip.', email='.$ban_email.', message='.$ban_message.', expire='.$ban_expire.' WHERE id='.intval($_POST['ban_id'])) or error('Unable to update ban', __FILE__, __LINE__, $db->error());
+
+	// Regenerate the bans cache
+	if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+		require PUN_ROOT.'include/cache.php';
+
+	generate_bans_cache();
+
+	if ($_POST['mode'] == 'edit')
+		redirect('admin_bans.php', $lang_admin_bans['Ban edited redirect']);
+	else
+		redirect('admin_bans.php', $lang_admin_bans['Ban added redirect']);
+}
+
+// Remove a ban
+else if (isset($_GET['del_ban']))
+{
+	confirm_referrer('admin_bans.php');
+
+	$ban_id = intval($_GET['del_ban']);
+	if ($ban_id < 1)
+		message($lang_common['Bad request']);
+
+	$db->query('DELETE FROM '.$db->prefix.'bans WHERE id='.$ban_id) or error('Unable to delete 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_bans.php', $lang_admin_bans['Ban removed redirect']);
+}
+
+// Find bans
+else if (isset($_GET['find_ban']))
+{
+	$form = isset($_GET['form']) ? $_GET['form'] : array();
+
+	// trim() all elements in $form
+	$form = array_map('pun_trim', $form);
+	$conditions = $query_str = array();
+
+	$expire_after = isset($_GET['expire_after']) ? pun_trim($_GET['expire_after']) : '';
+	$expire_before = isset($_GET['expire_before']) ? pun_trim($_GET['expire_before']) : '';
+	$order_by = isset($_GET['order_by']) && in_array($_GET['order_by'], array('username', 'ip', 'email', 'expire')) ? 'b.'.$_GET['order_by'] : 'b.username';
+	$direction = isset($_GET['direction']) && $_GET['direction'] == 'DESC' ? 'DESC' : 'ASC';
+
+	$query_str[] = 'order_by='.$order_by;
+	$query_str[] = 'direction='.$direction;
+
+	// Try to convert date/time to timestamps
+	if ($expire_after != '')
+	{
+		$query_str[] = 'expire_after='.$expire_after;
+
+		$expire_after = strtotime($expire_after);
+		if ($expire_after === false || $expire_after == -1)
+			message($lang_admin_bans['Invalid date message']);
+
+		$conditions[] = 'b.expire>'.$expire_after;
+	}
+	if ($expire_before != '')
+	{
+		$query_str[] = 'expire_before='.$expire_before;
+
+		$expire_before = strtotime($expire_before);
+		if ($expire_before === false || $expire_before == -1)
+			message($lang_admin_bans['Invalid date message']);
+
+		$conditions[] = 'b.expire<'.$expire_before;
+	}
+
+	$like_command = ($db_type == 'pgsql') ? 'ILIKE' : 'LIKE';
+	foreach ($form as $key => $input)
+	{
+		if ($input != '' && in_array($key, array('username', 'ip', 'email', 'message')))
+		{
+			$conditions[] = 'b.'.$db->escape($key).' '.$like_command.' \''.$db->escape(str_replace('*', '%', $input)).'\'';
+			$query_str[] = 'form%5B'.$key.'%5D='.urlencode($input);
+		}
+	}
+
+	// Fetch ban count
+	$result = $db->query('SELECT COUNT(id) FROM '.$db->prefix.'bans as b WHERE b.id>0'.(!empty($conditions) ? ' AND '.implode(' AND ', $conditions) : '')) or error('Unable to fetch ban list', __FILE__, __LINE__, $db->error());
+	$num_bans = $db->result($result);
+
+	// Determine the ban offset (based on $_GET['p'])
+	$num_pages = ceil($num_bans / 50);
+
+	$p = (!isset($_GET['p']) || $_GET['p'] <= 1 || $_GET['p'] > $num_pages) ? 1 : intval($_GET['p']);
+	$start_from = 50 * ($p - 1);
+
+	// Generate paging links
+	$paging_links = '<span class="pages-label">'.$lang_common['Pages'].' </span>'.paginate($num_pages, $p, 'admin_bans.php?find_ban=&amp;'.implode('&amp;', $query_str));
+
+	$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_admin_common['Admin'], $lang_admin_common['Bans'], $lang_admin_bans['Results head']);
+	define('PUN_ACTIVE_PAGE', 'admin');
+	require PUN_ROOT.'header.php';
+
+?>
+<div class="linkst">
+	<div class="inbox crumbsplus">
+		<ul class="crumbs">
+			<li><a href="admin_index.php"><?php echo $lang_admin_common['Admin'].' '.$lang_admin_common['Index'] ?></a></li>
+			<li><span>»&#160;</span><a href="admin_bans.php"><?php echo $lang_admin_common['Bans'] ?></a></li>
+			<li><span>»&#160;</span><strong><?php echo $lang_admin_bans['Results head'] ?></strong></li>
+		</ul>
+		<div class="pagepost">
+			<p class="pagelink"><?php echo $paging_links ?></p>
+		</div>
+		<div class="clearer"></div>
+	</div>
+</div>
+
+
+<div id="bans1" class="blocktable">
+	<h2><span><?php echo $lang_admin_bans['Results head'] ?></span></h2>
+	<div class="box">
+		<div class="inbox">
+			<table cellspacing="0">
+			<thead>
+				<tr>
+					<th class="tcl" scope="col"><?php echo $lang_admin_bans['Results username head'] ?></th>
+					<th class="tc2" scope="col"><?php echo $lang_admin_bans['Results e-mail head'] ?></th>
+					<th class="tc3" scope="col"><?php echo $lang_admin_bans['Results IP address head'] ?></th>
+					<th class="tc4" scope="col"><?php echo $lang_admin_bans['Results expire head'] ?></th>
+					<th class="tc5" scope="col"><?php echo $lang_admin_bans['Results message head'] ?></th>
+					<th class="tc6" scope="col"><?php echo $lang_admin_bans['Results banned by head'] ?></th>
+					<th class="tcr" scope="col"><?php echo $lang_admin_bans['Results actions head'] ?></th>
+				</tr>
+			</thead>
+			<tbody>
+<?php
+
+	$result = $db->query('SELECT b.id, b.username, b.ip, b.email, b.message, b.expire, b.ban_creator, u.username AS ban_creator_username FROM '.$db->prefix.'bans AS b LEFT JOIN '.$db->prefix.'users AS u ON b.ban_creator=u.id WHERE b.id>0'.(!empty($conditions) ? ' AND '.implode(' AND ', $conditions) : '').' ORDER BY '.$db->escape($order_by).' '.$db->escape($direction).' LIMIT '.$start_from.', 50') or error('Unable to fetch ban list', __FILE__, __LINE__, $db->error());
+	if ($db->num_rows($result))
+	{
+		while ($ban_data = $db->fetch_assoc($result))
+		{
+
+			$actions = '<a href="admin_bans.php?edit_ban='.$ban_data['id'].'">'.$lang_admin_common['Edit'].'</a> | <a href="admin_bans.php?del_ban='.$ban_data['id'].'">'.$lang_admin_common['Remove'].'</a>';
+			$expire = format_time($ban_data['expire'], true);
+
+?>
+				<tr>
+					<td class="tcl"><?php echo ($ban_data['username'] != '') ? pun_htmlspecialchars($ban_data['username']) : '&#160;' ?></td>
+					<td class="tc2"><?php echo ($ban_data['email'] != '') ? $ban_data['email'] : '&#160;' ?></td>
+					<td class="tc3"><?php echo ($ban_data['ip'] != '') ? pun_htmlspecialchars($ban_data['ip']) : '&#160;' ?></td>
+					<td class="tc4"><?php echo $expire ?></td>
+					<td class="tc5"><?php echo ($ban_data['message'] != '') ? pun_htmlspecialchars($ban_data['message']) : '&#160;' ?></td>
+					<td class="tc6"><?php echo ($ban_data['ban_creator_username'] != '') ? '<a href="profile.php?id='.$ban_data['ban_creator'].'">'.pun_htmlspecialchars($ban_data['ban_creator_username']).'</a>' : $lang_admin_bans['Unknown'] ?></td>
+					<td class="tcr"><?php echo $actions ?></td>
+				</tr>
+<?php
+
+		}
+	}
+	else
+		echo "\t\t\t\t".'<tr><td class="tcl" colspan="7">'.$lang_admin_bans['No match'].'</td></tr>'."\n";
+
+?>
+			</tbody>
+			</table>
+		</div>
+	</div>
+</div>
+
+<div class="linksb">
+	<div class="inbox crumbsplus">
+		<div class="pagepost">
+			<p class="pagelink"><?php echo $paging_links ?></p>
+		</div>
+		<ul class="crumbs">
+			<li><a href="admin_index.php"><?php echo $lang_admin_common['Admin'].' '.$lang_admin_common['Index'] ?></a></li>
+			<li><span>»&#160;</span><a href="admin_bans.php"><?php echo $lang_admin_common['Bans'] ?></a></li>
+			<li><span>»&#160;</span><strong><?php echo $lang_admin_bans['Results head'] ?></strong></li>
+		</ul>
+		<div class="clearer"></div>
+	</div>
+</div>
+<?php
+
+	require PUN_ROOT.'footer.php';
+}
+
+$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_admin_common['Admin'], $lang_admin_common['Bans']);
+$focus_element = array('bans', 'new_ban_user');
+define('PUN_ACTIVE_PAGE', 'admin');
+require PUN_ROOT.'header.php';
+
+generate_admin_menu('bans');
+
+?>
+	<div class="blockform">
+		<h2><span><?php echo $lang_admin_bans['New ban head'] ?></span></h2>
+		<div class="box">
+			<form id="bans" method="post" action="admin_bans.php?action=more">
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_bans['Add ban subhead'] ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_bans['Username label'] ?><div><input type="submit" name="add_ban" value="<?php echo $lang_admin_common['Add'] ?>" tabindex="2" /></div></th>
+									<td>
+										<input type="text" name="new_ban_user" size="25" maxlength="25" tabindex="1" />
+										<span><?php echo $lang_admin_bans['Username advanced help'] ?></span>
+									</td>
+								</tr>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+			</form>
+		</div>
+
+		<h2 class="block2"><span><?php echo $lang_admin_bans['Ban search head'] ?></span></h2>
+		<div class="box">
+			<form id="find_band" method="get" action="admin_bans.php">
+				<p class="submittop"><input type="submit" name="find_ban" value="<?php echo $lang_admin_bans['Submit search'] ?>" tabindex="3" /></p>
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_bans['Ban search subhead'] ?></legend>
+						<div class="infldset">
+							<p><?php echo $lang_admin_bans['Ban search info'] ?></p>
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_bans['Username label'] ?></th>
+									<td><input type="text" name="form[username]" size="25" maxlength="25" tabindex="4" /></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_bans['IP label'] ?></th>
+									<td><input type="text" name="form[ip]" size="30" maxlength="255" tabindex="5" /></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_bans['E-mail label'] ?></th>
+									<td><input type="text" name="form[email]" size="30" maxlength="80" tabindex="6" /></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_bans['Message label'] ?></th>
+									<td><input type="text" name="form[message]" size="30" maxlength="255" tabindex="7" /></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_bans['Expire after label'] ?></th>
+									<td><input type="text" name="expire_after" size="10" maxlength="10" tabindex="8" />
+									<span><?php echo $lang_admin_bans['Date help'] ?></span></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_bans['Expire before label'] ?></th>
+									<td><input type="text" name="expire_before" size="10" maxlength="10" tabindex="9" />
+									<span><?php echo $lang_admin_bans['Date help'] ?></span></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_bans['Order by label'] ?></th>
+									<td>
+										<select name="order_by" tabindex="10">
+											<option value="username" selected="selected"><?php echo $lang_admin_bans['Order by username'] ?></option>
+											<option value="ip"><?php echo $lang_admin_bans['Order by ip'] ?></option>
+											<option value="email"><?php echo $lang_admin_bans['Order by e-mail'] ?></option>
+											<option value="expire"><?php echo $lang_admin_bans['Order by expire'] ?></option>
+										</select>&#160;&#160;&#160;<select name="direction" tabindex="11">
+											<option value="ASC" selected="selected"><?php echo $lang_admin_bans['Ascending'] ?></option>
+											<option value="DESC"><?php echo $lang_admin_bans['Descending'] ?></option>
+										</select>
+									</td>
+								</tr>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+				<p class="submitend"><input type="submit" name="find_ban" value="<?php echo $lang_admin_bans['Submit search'] ?>" tabindex="12" /></p>
+			</form>
+		</div>
+	</div>
+	<div class="clearer"></div>
+</div>
+<?php
+
+require PUN_ROOT.'footer.php';
diff --git a/admin_categories.php b/admin_categories.php
new file mode 100644
index 0000000..ab4d430
--- /dev/null
+++ b/admin_categories.php
@@ -0,0 +1,266 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// Tell header.php to use the admin template
+define('PUN_ADMIN_CONSOLE', 1);
+
+define('PUN_ROOT', dirname(__FILE__).'/');
+require PUN_ROOT.'include/common.php';
+require PUN_ROOT.'include/common_admin.php';
+
+
+if ($pun_user['g_id'] != PUN_ADMIN)
+	message($lang_common['No permission'], false, '403 Forbidden');
+
+// Load the admin_categories.php language file
+require PUN_ROOT.'lang/'.$admin_language.'/admin_categories.php';
+
+// Add a new category
+if (isset($_POST['add_cat']))
+{
+	confirm_referrer('admin_categories.php');
+
+	$new_cat_name = pun_trim($_POST['new_cat_name']);
+	if ($new_cat_name == '')
+		message($lang_admin_categories['Must enter name message']);
+
+	$db->query('INSERT INTO '.$db->prefix.'categories (cat_name) VALUES(\''.$db->escape($new_cat_name).'\')') or error('Unable to create category', __FILE__, __LINE__, $db->error());
+
+	redirect('admin_categories.php', $lang_admin_categories['Category added redirect']);
+}
+
+// Delete a category
+else if (isset($_POST['del_cat']) || isset($_POST['del_cat_comply']))
+{
+	confirm_referrer('admin_categories.php');
+
+	$cat_to_delete = intval($_POST['cat_to_delete']);
+	if ($cat_to_delete < 1)
+		message($lang_common['Bad request']);
+
+	if (isset($_POST['del_cat_comply'])) // Delete a category with all forums and posts
+	{
+		@set_time_limit(0);
+
+		$result = $db->query('SELECT id FROM '.$db->prefix.'forums WHERE cat_id='.$cat_to_delete) or error('Unable to fetch forum list', __FILE__, __LINE__, $db->error());
+		$num_forums = $db->num_rows($result);
+
+		for ($i = 0; $i < $num_forums; ++$i)
+		{
+			$cur_forum = $db->result($result, $i);
+
+			// Prune all posts and topics
+			prune($cur_forum, 1, -1);
+
+			// Delete the forum
+			$db->query('DELETE FROM '.$db->prefix.'forums WHERE id='.$cur_forum) or error('Unable to delete forum', __FILE__, __LINE__, $db->error());
+		}
+
+		// 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());
+		}
+
+		// Delete the category
+		$db->query('DELETE FROM '.$db->prefix.'categories WHERE id='.$cat_to_delete) or error('Unable to delete category', __FILE__, __LINE__, $db->error());
+
+		// Regenerate the quick jump cache
+		if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+			require PUN_ROOT.'include/cache.php';
+
+		generate_quickjump_cache();
+
+		redirect('admin_categories.php', $lang_admin_categories['Category deleted redirect']);
+	}
+	else // If the user hasn't comfirmed the delete
+	{
+		$result = $db->query('SELECT cat_name FROM '.$db->prefix.'categories WHERE id='.$cat_to_delete) or error('Unable to fetch category info', __FILE__, __LINE__, $db->error());
+		$cat_name = $db->result($result);
+
+		$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_admin_common['Admin'], $lang_admin_common['Categories']);
+		define('PUN_ACTIVE_PAGE', 'admin');
+		require PUN_ROOT.'header.php';
+
+		generate_admin_menu('categories');
+
+?>
+	<div class="blockform">
+		<h2><span><?php echo $lang_admin_categories['Delete category head'] ?></span></h2>
+		<div class="box">
+			<form method="post" action="admin_categories.php">
+				<div class="inform">
+				<input type="hidden" name="cat_to_delete" value="<?php echo $cat_to_delete ?>" />
+					<fieldset>
+						<legend><?php echo $lang_admin_categories['Confirm delete subhead'] ?></legend>
+						<div class="infldset">
+							<p><?php printf($lang_admin_categories['Confirm delete info'], pun_htmlspecialchars($cat_name)) ?></p>
+							<p class="warntext"><?php echo $lang_admin_categories['Delete category warn'] ?></p>
+						</div>
+					</fieldset>
+				</div>
+				<p class="buttons"><input type="submit" name="del_cat_comply" value="<?php echo $lang_admin_common['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';
+	}
+}
+
+else if (isset($_POST['update'])) // Change position and name of the categories
+{
+	confirm_referrer('admin_categories.php');
+
+	$categories = $_POST['cat'];
+	if (empty($categories))
+		message($lang_common['Bad request']);
+
+	foreach ($categories as $cat_id => $cur_cat)
+	{
+		$cur_cat['name'] = pun_trim($cur_cat['name']);
+		$cur_cat['order'] = pun_trim($cur_cat['order']);
+
+		if ($cur_cat['name'] == '')
+			message($lang_admin_categories['Must enter name message']);
+
+		if ($cur_cat['order'] == '' || preg_match('%[^0-9]%', $cur_cat['order']))
+			message($lang_admin_categories['Must enter integer message']);
+
+		$db->query('UPDATE '.$db->prefix.'categories SET cat_name=\''.$db->escape($cur_cat['name']).'\', disp_position='.$cur_cat['order'].' WHERE id='.intval($cat_id)) or error('Unable to update category', __FILE__, __LINE__, $db->error());
+	}
+
+	// Regenerate the quick jump cache
+	if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+		require PUN_ROOT.'include/cache.php';
+
+	generate_quickjump_cache();
+
+	redirect('admin_categories.php', $lang_admin_categories['Categories updated redirect']);
+}
+
+// Generate an array with all categories
+$result = $db->query('SELECT id, cat_name, disp_position FROM '.$db->prefix.'categories ORDER BY disp_position') or error('Unable to fetch category list', __FILE__, __LINE__, $db->error());
+$num_cats = $db->num_rows($result);
+
+for ($i = 0; $i < $num_cats; ++$i)
+	$cat_list[] = $db->fetch_assoc($result);
+
+$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_admin_common['Admin'], $lang_admin_common['Categories']);
+define('PUN_ACTIVE_PAGE', 'admin');
+require PUN_ROOT.'header.php';
+
+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">
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_categories['Add categories subhead'] ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_categories['Add category label'] ?><div><input type="submit" name="add_cat" value="<?php echo $lang_admin_categories['Add new submit'] ?>" tabindex="2" /></div></th>
+									<td>
+										<input type="text" name="new_cat_name" size="35" maxlength="80" tabindex="1" />
+										<span><?php printf($lang_admin_categories['Add category help'], '<a href="admin_forums.php">'.$lang_admin_common['Forums'].'</a>') ?></span>
+									</td>
+								</tr>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+			</form>
+		</div>
+
+<?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">
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_categories['Delete categories subhead'] ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_categories['Delete category label'] ?><div><input type="submit" name="del_cat" value="<?php echo $lang_admin_common['Delete'] ?>" tabindex="4" /></div></th>
+									<td>
+										<select name="cat_to_delete" tabindex="3">
+<?php
+
+	foreach ($cat_list as $cur_cat)
+		echo "\t\t\t\t\t\t\t\t\t\t\t".'<option value="'.$cur_cat['id'].'">'.pun_htmlspecialchars($cur_cat['cat_name']).'</option>'."\n";
+
+?>
+										</select>
+										<span><?php echo $lang_admin_categories['Delete category help'] ?></span>
+									</td>
+								</tr>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+			</form>
+		</div>
+<?php endif; ?>
+
+<?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">
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_categories['Edit categories subhead'] ?></legend>
+						<div class="infldset">
+							<table id="categoryedit" cellspacing="0" >
+							<thead>
+								<tr>
+									<th class="tcl" scope="col"><?php echo $lang_admin_categories['Category name label'] ?></th>
+									<th scope="col"><?php echo $lang_admin_categories['Category position label'] ?></th>
+								</tr>
+							</thead>
+							<tbody>
+<?php
+
+	foreach ($cat_list as $cur_cat)
+	{
+
+?>
+								<tr>
+									<td class="tcl"><input type="text" name="cat[<?php echo $cur_cat['id'] ?>][name]" value="<?php echo pun_htmlspecialchars($cur_cat['cat_name']) ?>" size="35" maxlength="80" /></td>
+									<td><input type="text" name="cat[<?php echo $cur_cat['id'] ?>][order]" value="<?php echo $cur_cat['disp_position'] ?>" size="3" maxlength="3" /></td>
+								</tr>
+<?php
+
+	}
+
+?>
+							</tbody>
+							</table>
+							<div class="fsetsubmit"><input type="submit" name="update" value="<?php echo $lang_admin_common['Update'] ?>" /></div>
+						</div>
+					</fieldset>
+				</div>
+			</form>
+		</div>
+<?php endif; ?>	</div>
+	<div class="clearer"></div>
+</div>
+<?php
+
+require PUN_ROOT.'footer.php';
diff --git a/admin_censoring.php b/admin_censoring.php
new file mode 100644
index 0000000..420ca62
--- /dev/null
+++ b/admin_censoring.php
@@ -0,0 +1,168 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// Tell header.php to use the admin template
+define('PUN_ADMIN_CONSOLE', 1);
+
+define('PUN_ROOT', dirname(__FILE__).'/');
+require PUN_ROOT.'include/common.php';
+require PUN_ROOT.'include/common_admin.php';
+
+
+if ($pun_user['g_id'] != PUN_ADMIN)
+	message($lang_common['No permission'], false, '403 Forbidden');
+
+// Load the admin_censoring.php language file
+require PUN_ROOT.'lang/'.$admin_language.'/admin_censoring.php';
+
+// Add a censor word
+if (isset($_POST['add_word']))
+{
+	confirm_referrer('admin_censoring.php');
+
+	$search_for = pun_trim($_POST['new_search_for']);
+	$replace_with = pun_trim($_POST['new_replace_with']);
+
+	if ($search_for == '')
+		message($lang_admin_censoring['Must enter word message']);
+
+	$db->query('INSERT INTO '.$db->prefix.'censoring (search_for, replace_with) VALUES (\''.$db->escape($search_for).'\', \''.$db->escape($replace_with).'\')') or error('Unable to add censor word', __FILE__, __LINE__, $db->error());
+
+	// Regenerate the censoring cache
+	if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+		require PUN_ROOT.'include/cache.php';
+
+	generate_censoring_cache();
+
+	redirect('admin_censoring.php', $lang_admin_censoring['Word added redirect']);
+}
+
+// Update a censor word
+else if (isset($_POST['update']))
+{
+	confirm_referrer('admin_censoring.php');
+
+	$id = intval(key($_POST['update']));
+
+	$search_for = pun_trim($_POST['search_for'][$id]);
+	$replace_with = pun_trim($_POST['replace_with'][$id]);
+
+	if ($search_for == '')
+		message($lang_admin_censoring['Must enter word message']);
+
+	$db->query('UPDATE '.$db->prefix.'censoring SET search_for=\''.$db->escape($search_for).'\', replace_with=\''.$db->escape($replace_with).'\' WHERE id='.$id) or error('Unable to update censor word', __FILE__, __LINE__, $db->error());
+
+	// Regenerate the censoring cache
+	if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+		require PUN_ROOT.'include/cache.php';
+
+	generate_censoring_cache();
+
+	redirect('admin_censoring.php', $lang_admin_censoring['Word updated redirect']);
+}
+
+// Remove a censor word
+else if (isset($_POST['remove']))
+{
+	confirm_referrer('admin_censoring.php');
+
+	$id = intval(key($_POST['remove']));
+
+	$db->query('DELETE FROM '.$db->prefix.'censoring WHERE id='.$id) or error('Unable to delete censor word', __FILE__, __LINE__, $db->error());
+
+	// Regenerate the censoring cache
+	if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+		require PUN_ROOT.'include/cache.php';
+
+	generate_censoring_cache();
+
+	redirect('admin_censoring.php',  $lang_admin_censoring['Word removed redirect']);
+}
+
+$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_admin_common['Admin'], $lang_admin_common['Censoring']);
+$focus_element = array('censoring', 'new_search_for');
+define('PUN_ACTIVE_PAGE', 'admin');
+require PUN_ROOT.'header.php';
+
+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">
+				<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_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>
+									<th class="tcl" scope="col"><?php echo $lang_admin_censoring['Censored word label'] ?></th>
+									<th class="tc2" scope="col"><?php echo $lang_admin_censoring['Replacement label'] ?></th>
+									<th class="hidehead" scope="col"><?php echo $lang_admin_censoring['Action label'] ?></th>
+								</tr>
+							</thead>
+							<tbody>
+								<tr>
+									<td class="tcl"><input type="text" name="new_search_for" size="24" maxlength="60" tabindex="1" /></td>
+									<td class="tc2"><input type="text" name="new_replace_with" size="24" maxlength="60" tabindex="2" /></td>
+									<td><input type="submit" name="add_word" value="<?php echo $lang_admin_common['Add'] ?>" tabindex="3" /></td>
+								</tr>
+							</tbody>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_censoring['Edit remove subhead'] ?></legend>
+						<div class="infldset">
+<?php
+
+$result = $db->query('SELECT id, search_for, replace_with FROM '.$db->prefix.'censoring ORDER BY id') or error('Unable to fetch censor word list', __FILE__, __LINE__, $db->error());
+if ($db->num_rows($result))
+{
+
+?>
+							<table cellspacing="0" >
+							<thead>
+								<tr>
+									<th class="tcl" scope="col"><?php echo $lang_admin_censoring['Censored word label'] ?></th>
+									<th class="tc2" scope="col"><?php echo $lang_admin_censoring['Replacement label'] ?></th>
+									<th class="hidehead" scope="col"><?php echo $lang_admin_censoring['Action label'] ?></th>
+								</tr>
+							</thead>
+							<tbody>
+<?php
+
+	while ($cur_word = $db->fetch_assoc($result))
+		echo "\t\t\t\t\t\t\t\t".'<tr><td class="tcl"><input type="text" name="search_for['.$cur_word['id'].']" value="'.pun_htmlspecialchars($cur_word['search_for']).'" size="24" maxlength="60" /></td><td class="tc2"><input type="text" name="replace_with['.$cur_word['id'].']" value="'.pun_htmlspecialchars($cur_word['replace_with']).'" size="24" maxlength="60" /></td><td><input type="submit" name="update['.$cur_word['id'].']" value="'.$lang_admin_common['Update'].'" />&#160;<input type="submit" name="remove['.$cur_word['id'].']" value="'.$lang_admin_common['Remove'].'" /></td></tr>'."\n";
+
+?>
+							</tbody>
+							</table>
+<?php
+
+}
+else
+	echo "\t\t\t\t\t\t\t".'<p>'.$lang_admin_censoring['No words in list'].'</p>'."\n";
+
+?>
+						</div>
+					</fieldset>
+				</div>
+			</form>
+		</div>
+	</div>
+	<div class="clearer"></div>
+</div>
+<?php
+
+require PUN_ROOT.'footer.php';
diff --git a/admin_forums.php b/admin_forums.php
new file mode 100644
index 0000000..1f1c13f
--- /dev/null
+++ b/admin_forums.php
@@ -0,0 +1,478 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// Tell header.php to use the admin template
+define('PUN_ADMIN_CONSOLE', 1);
+
+define('PUN_ROOT', dirname(__FILE__).'/');
+require PUN_ROOT.'include/common.php';
+require PUN_ROOT.'include/common_admin.php';
+
+
+if ($pun_user['g_id'] != PUN_ADMIN)
+	message($lang_common['No permission'], false, '403 Forbidden');
+
+// Load the admin_forums.php language file
+require PUN_ROOT.'lang/'.$admin_language.'/admin_forums.php';
+
+// Add a "default" forum
+if (isset($_POST['add_forum']))
+{
+	confirm_referrer('admin_forums.php');
+
+	$add_to_cat = intval($_POST['add_to_cat']);
+	if ($add_to_cat < 1)
+		message($lang_common['Bad request']);
+
+	$db->query('INSERT INTO '.$db->prefix.'forums (forum_name, cat_id) VALUES(\''.$db->escape($lang_admin_forums['New forum']).'\', '.$add_to_cat.')') or error('Unable to create forum', __FILE__, __LINE__, $db->error());
+
+	// Regenerate the quick jump cache
+	if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+		require PUN_ROOT.'include/cache.php';
+
+	generate_quickjump_cache();
+
+	redirect('admin_forums.php', $lang_admin_forums['Forum added redirect']);
+}
+
+// Delete a forum
+else if (isset($_GET['del_forum']))
+{
+	confirm_referrer('admin_forums.php');
+
+	$forum_id = intval($_GET['del_forum']);
+	if ($forum_id < 1)
+		message($lang_common['Bad request']);
+
+	if (isset($_POST['del_forum_comply'])) // Delete a forum with all posts
+	{
+		@set_time_limit(0);
+
+		// Prune all posts and topics
+		prune($forum_id, 1, -1);
+
+		// 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());
+		}
+
+		// Delete the forum and any forum specific group permissions
+		$db->query('DELETE FROM '.$db->prefix.'forums WHERE id='.$forum_id) or error('Unable to delete forum', __FILE__, __LINE__, $db->error());
+		$db->query('DELETE FROM '.$db->prefix.'forum_perms WHERE forum_id='.$forum_id) or error('Unable to delete group forum permissions', __FILE__, __LINE__, $db->error());
+
+		// Delete any subscriptions for this forum
+		$db->query('DELETE FROM '.$db->prefix.'forum_subscriptions WHERE forum_id='.$forum_id) or error('Unable to delete subscriptions', __FILE__, __LINE__, $db->error());
+
+		// Regenerate the quick jump cache
+		if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+			require PUN_ROOT.'include/cache.php';
+
+		generate_quickjump_cache();
+
+		redirect('admin_forums.php', $lang_admin_forums['Forum deleted redirect']);
+	}
+	else // If the user hasn't confirmed the delete
+	{
+		$result = $db->query('SELECT forum_name FROM '.$db->prefix.'forums WHERE id='.$forum_id) or error('Unable to fetch forum info', __FILE__, __LINE__, $db->error());
+		$forum_name = pun_htmlspecialchars($db->result($result));
+
+		$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_admin_common['Admin'], $lang_admin_common['Forums']);
+		define('PUN_ACTIVE_PAGE', 'admin');
+		require PUN_ROOT.'header.php';
+
+		generate_admin_menu('forums');
+
+?>
+	<div class="blockform">
+		<h2><span><?php echo $lang_admin_forums['Confirm delete head'] ?></span></h2>
+		<div class="box">
+			<form method="post" action="admin_forums.php?del_forum=<?php echo $forum_id ?>">
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_forums['Confirm delete subhead'] ?></legend>
+						<div class="infldset">
+							<p><?php printf($lang_admin_forums['Confirm delete info'], $forum_name) ?></p>
+							<p class="warntext"><?php echo $lang_admin_forums['Confirm delete warn'] ?></p>
+						</div>
+					</fieldset>
+				</div>
+				<p class="buttons"><input type="submit" name="del_forum_comply" value="<?php echo $lang_admin_common['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';
+	}
+}
+
+// Update forum positions
+else if (isset($_POST['update_positions']))
+{
+	confirm_referrer('admin_forums.php');
+
+	foreach ($_POST['position'] as $forum_id => $disp_position)
+	{
+		$disp_position = trim($disp_position);
+		if ($disp_position == '' || preg_match('%[^0-9]%', $disp_position))
+			message($lang_admin_forums['Must be integer message']);
+
+		$db->query('UPDATE '.$db->prefix.'forums SET disp_position='.$disp_position.' WHERE id='.intval($forum_id)) or error('Unable to update forum', __FILE__, __LINE__, $db->error());
+	}
+
+	// Regenerate the quick jump cache
+	if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+		require PUN_ROOT.'include/cache.php';
+
+	generate_quickjump_cache();
+
+	redirect('admin_forums.php', $lang_admin_forums['Forums updated redirect']);
+}
+
+else if (isset($_GET['edit_forum']))
+{
+	$forum_id = intval($_GET['edit_forum']);
+	if ($forum_id < 1)
+		message($lang_common['Bad request']);
+
+	// Update group permissions for $forum_id
+	if (isset($_POST['save']))
+	{
+		confirm_referrer('admin_forums.php');
+
+		// Start with the forum details
+		$forum_name = pun_trim($_POST['forum_name']);
+		$forum_desc = pun_linebreaks(pun_trim($_POST['forum_desc']));
+		$cat_id = intval($_POST['cat_id']);
+		$sort_by = intval($_POST['sort_by']);
+		$redirect_url = isset($_POST['redirect_url']) ? pun_trim($_POST['redirect_url']) : null;
+
+		if ($forum_name == '')
+			message($lang_admin_forums['Must enter name message']);
+
+		if ($cat_id < 1)
+			message($lang_common['Bad request']);
+
+		$forum_desc = ($forum_desc != '') ? '\''.$db->escape($forum_desc).'\'' : 'NULL';
+		$redirect_url = ($redirect_url != '') ? '\''.$db->escape($redirect_url).'\'' : 'NULL';
+
+		$db->query('UPDATE '.$db->prefix.'forums SET forum_name=\''.$db->escape($forum_name).'\', forum_desc='.$forum_desc.', redirect_url='.$redirect_url.', sort_by='.$sort_by.', cat_id='.$cat_id.' WHERE id='.$forum_id) or error('Unable to update forum', __FILE__, __LINE__, $db->error());
+
+		// Now let's deal with the permissions
+		if (isset($_POST['read_forum_old']))
+		{
+			$result = $db->query('SELECT g_id, g_read_board, g_post_replies, g_post_topics FROM '.$db->prefix.'groups WHERE g_id!='.PUN_ADMIN) or error('Unable to fetch user group list', __FILE__, __LINE__, $db->error());
+			while ($cur_group = $db->fetch_assoc($result))
+			{
+				$read_forum_new = ($cur_group['g_read_board'] == '1') ? isset($_POST['read_forum_new'][$cur_group['g_id']]) ? '1' : '0' : intval($_POST['read_forum_old'][$cur_group['g_id']]);
+				$post_replies_new = isset($_POST['post_replies_new'][$cur_group['g_id']]) ? '1' : '0';
+				$post_topics_new = isset($_POST['post_topics_new'][$cur_group['g_id']]) ? '1' : '0';
+
+				// Check if the new settings differ from the old
+				if ($read_forum_new != $_POST['read_forum_old'][$cur_group['g_id']] || $post_replies_new != $_POST['post_replies_old'][$cur_group['g_id']] || $post_topics_new != $_POST['post_topics_old'][$cur_group['g_id']])
+				{
+					// If the new settings are identical to the default settings for this group, delete it's row in forum_perms
+					if ($read_forum_new == '1' && $post_replies_new == $cur_group['g_post_replies'] && $post_topics_new == $cur_group['g_post_topics'])
+						$db->query('DELETE FROM '.$db->prefix.'forum_perms WHERE group_id='.$cur_group['g_id'].' AND forum_id='.$forum_id) or error('Unable to delete group forum permissions', __FILE__, __LINE__, $db->error());
+					else
+					{
+						// Run an UPDATE and see if it affected a row, if not, INSERT
+						$db->query('UPDATE '.$db->prefix.'forum_perms SET read_forum='.$read_forum_new.', post_replies='.$post_replies_new.', post_topics='.$post_topics_new.' WHERE group_id='.$cur_group['g_id'].' AND forum_id='.$forum_id) or error('Unable to insert group forum permissions', __FILE__, __LINE__, $db->error());
+						if (!$db->affected_rows())
+							$db->query('INSERT INTO '.$db->prefix.'forum_perms (group_id, forum_id, read_forum, post_replies, post_topics) VALUES('.$cur_group['g_id'].', '.$forum_id.', '.$read_forum_new.', '.$post_replies_new.', '.$post_topics_new.')') or error('Unable to insert group forum permissions', __FILE__, __LINE__, $db->error());
+					}
+				}
+			}
+		}
+
+		// Regenerate the quick jump cache
+		if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+			require PUN_ROOT.'include/cache.php';
+
+		generate_quickjump_cache();
+
+		redirect('admin_forums.php', $lang_admin_forums['Forum updated redirect']);
+	}
+	else if (isset($_POST['revert_perms']))
+	{
+		confirm_referrer('admin_forums.php');
+
+		$db->query('DELETE FROM '.$db->prefix.'forum_perms WHERE forum_id='.$forum_id) or error('Unable to delete group forum permissions', __FILE__, __LINE__, $db->error());
+
+		// Regenerate the quick jump cache
+		if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+			require PUN_ROOT.'include/cache.php';
+
+		generate_quickjump_cache();
+
+		redirect('admin_forums.php?edit_forum='.$forum_id, $lang_admin_forums['Perms reverted redirect']);
+	}
+
+	// Fetch forum info
+	$result = $db->query('SELECT id, forum_name, forum_desc, redirect_url, num_topics, sort_by, cat_id FROM '.$db->prefix.'forums WHERE id='.$forum_id) or error('Unable to fetch forum info', __FILE__, __LINE__, $db->error());
+	if (!$db->num_rows($result))
+		message($lang_common['Bad request']);
+
+	$cur_forum = $db->fetch_assoc($result);
+
+	$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_admin_common['Admin'], $lang_admin_common['Forums']);
+	define('PUN_ACTIVE_PAGE', 'admin');
+	require PUN_ROOT.'header.php';
+
+	generate_admin_menu('forums');
+
+?>
+	<div class="blockform">
+		<h2><span><?php echo $lang_admin_forums['Edit forum head'] ?></span></h2>
+		<div class="box">
+			<form id="edit_forum" method="post" action="admin_forums.php?edit_forum=<?php echo $forum_id ?>">
+				<p class="submittop"><input type="submit" name="save" value="<?php echo $lang_admin_common['Save changes'] ?>" tabindex="6" /></p>
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_forums['Edit details subhead'] ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_forums['Forum name label'] ?></th>
+									<td><input type="text" name="forum_name" size="35" maxlength="80" value="<?php echo pun_htmlspecialchars($cur_forum['forum_name']) ?>" tabindex="1" /></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_forums['Forum description label'] ?></th>
+									<td><textarea name="forum_desc" rows="3" cols="50" tabindex="2"><?php echo pun_htmlspecialchars($cur_forum['forum_desc']) ?></textarea></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_forums['Category label'] ?></th>
+									<td>
+										<select name="cat_id" tabindex="3">
+<?php
+
+	$result = $db->query('SELECT id, cat_name FROM '.$db->prefix.'categories ORDER BY disp_position') or error('Unable to fetch category list', __FILE__, __LINE__, $db->error());
+	while ($cur_cat = $db->fetch_assoc($result))
+	{
+		$selected = ($cur_cat['id'] == $cur_forum['cat_id']) ? ' selected="selected"' : '';
+		echo "\t\t\t\t\t\t\t\t\t\t\t".'<option value="'.$cur_cat['id'].'"'.$selected.'>'.pun_htmlspecialchars($cur_cat['cat_name']).'</option>'."\n";
+	}
+
+?>
+										</select>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_forums['Sort by label'] ?></th>
+									<td>
+										<select name="sort_by" tabindex="4">
+											<option value="0"<?php if ($cur_forum['sort_by'] == '0') echo ' selected="selected"' ?>><?php echo $lang_admin_forums['Last post'] ?></option>
+											<option value="1"<?php if ($cur_forum['sort_by'] == '1') echo ' selected="selected"' ?>><?php echo $lang_admin_forums['Topic start'] ?></option>
+											<option value="2"<?php if ($cur_forum['sort_by'] == '2') echo ' selected="selected"' ?>><?php echo $lang_admin_forums['Subject'] ?></option>
+										</select>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_forums['Redirect label'] ?></th>
+									<td><?php echo ($cur_forum['num_topics']) ? $lang_admin_forums['Redirect help'] : '<input type="text" name="redirect_url" size="45" maxlength="100" value="'.pun_htmlspecialchars($cur_forum['redirect_url']).'" tabindex="5" />'; ?></td>
+								</tr>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_forums['Group permissions subhead'] ?></legend>
+						<div class="infldset">
+							<p><?php printf($lang_admin_forums['Group permissions info'], '<a href="admin_groups.php">'.$lang_admin_common['User groups'].'</a>') ?></p>
+							<table id="forumperms" cellspacing="0">
+							<thead>
+								<tr>
+									<th class="atcl">&#160;</th>
+									<th><?php echo $lang_admin_forums['Read forum label'] ?></th>
+									<th><?php echo $lang_admin_forums['Post replies label'] ?></th>
+									<th><?php echo $lang_admin_forums['Post topics label'] ?></th>
+								</tr>
+							</thead>
+							<tbody>
+<?php
+
+	$result = $db->query('SELECT g.g_id, g.g_title, g.g_read_board, g.g_post_replies, g.g_post_topics, fp.read_forum, fp.post_replies, fp.post_topics FROM '.$db->prefix.'groups AS g LEFT JOIN '.$db->prefix.'forum_perms AS fp ON (g.g_id=fp.group_id AND fp.forum_id='.$forum_id.') WHERE g.g_id!='.PUN_ADMIN.' ORDER BY g.g_id') or error('Unable to fetch group forum permission list', __FILE__, __LINE__, $db->error());
+
+	$cur_index = 7;
+
+	while ($cur_perm = $db->fetch_assoc($result))
+	{
+		$read_forum = ($cur_perm['read_forum'] != '0') ? true : false;
+		$post_replies = (($cur_perm['g_post_replies'] == '0' && $cur_perm['post_replies'] == '1') || ($cur_perm['g_post_replies'] == '1' && $cur_perm['post_replies'] != '0')) ? true : false;
+		$post_topics = (($cur_perm['g_post_topics'] == '0' && $cur_perm['post_topics'] == '1') || ($cur_perm['g_post_topics'] == '1' && $cur_perm['post_topics'] != '0')) ? true : false;
+
+		// Determine if the current settings differ from the default or not
+		$read_forum_def = ($cur_perm['read_forum'] == '0') ? false : true;
+		$post_replies_def = (($post_replies && $cur_perm['g_post_replies'] == '0') || (!$post_replies && ($cur_perm['g_post_replies'] == '' || $cur_perm['g_post_replies'] == '1'))) ? false : true;
+		$post_topics_def = (($post_topics && $cur_perm['g_post_topics'] == '0') || (!$post_topics && ($cur_perm['g_post_topics'] == '' || $cur_perm['g_post_topics'] == '1'))) ? false : true;
+
+?>
+								<tr>
+									<th class="atcl"><?php echo pun_htmlspecialchars($cur_perm['g_title']) ?></th>
+									<td<?php if (!$read_forum_def) echo ' class="nodefault"'; ?>>
+										<input type="hidden" name="read_forum_old[<?php echo $cur_perm['g_id'] ?>]" value="<?php echo ($read_forum) ? '1' : '0'; ?>" />
+										<input type="checkbox" name="read_forum_new[<?php echo $cur_perm['g_id'] ?>]" value="1"<?php echo ($read_forum) ? ' checked="checked"' : ''; ?><?php echo ($cur_perm['g_read_board'] == '0') ? ' disabled="disabled"' : ''; ?> tabindex="<?php echo $cur_index++ ?>" />
+									</td>
+									<td<?php if (!$post_replies_def && $cur_forum['redirect_url'] == '') echo ' class="nodefault"'; ?>>
+										<input type="hidden" name="post_replies_old[<?php echo $cur_perm['g_id'] ?>]" value="<?php echo ($post_replies) ? '1' : '0'; ?>" />
+										<input type="checkbox" name="post_replies_new[<?php echo $cur_perm['g_id'] ?>]" value="1"<?php echo ($post_replies) ? ' checked="checked"' : ''; ?><?php echo ($cur_forum['redirect_url'] != '') ? ' disabled="disabled"' : ''; ?> tabindex="<?php echo $cur_index++ ?>" />
+									</td>
+									<td<?php if (!$post_topics_def && $cur_forum['redirect_url'] == '') echo ' class="nodefault"'; ?>>
+										<input type="hidden" name="post_topics_old[<?php echo $cur_perm['g_id'] ?>]" value="<?php echo ($post_topics) ? '1' : '0'; ?>" />
+										<input type="checkbox" name="post_topics_new[<?php echo $cur_perm['g_id'] ?>]" value="1"<?php echo ($post_topics) ? ' checked="checked"' : ''; ?><?php echo ($cur_forum['redirect_url'] != '') ? ' disabled="disabled"' : ''; ?> tabindex="<?php echo $cur_index++ ?>" />
+									</td>
+								</tr>
+<?php
+
+	}
+
+?>
+							</tbody>
+							</table>
+							<div class="fsetsubmit"><input type="submit" name="revert_perms" value="<?php echo $lang_admin_forums['Revert to default'] ?>" tabindex="<?php echo $cur_index++ ?>" /></div>
+						</div>
+					</fieldset>
+				</div>
+				<p class="submitend"><input type="submit" name="save" value="<?php echo $lang_admin_common['Save changes'] ?>" tabindex="<?php echo $cur_index++ ?>" /></p>
+			</form>
+		</div>
+	</div>
+	<div class="clearer"></div>
+</div>
+
+<?php
+
+	require PUN_ROOT.'footer.php';
+}
+
+$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_admin_common['Admin'], $lang_admin_common['Forums']);
+define('PUN_ACTIVE_PAGE', 'admin');
+require PUN_ROOT.'header.php';
+
+generate_admin_menu('forums');
+
+?>
+	<div class="blockform">
+		<h2><span><?php echo $lang_admin_forums['Add forum head'] ?></span></h2>
+		<div class="box">
+			<form method="post" action="admin_forums.php?action=adddel">
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_forums['Create new subhead'] ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_forums['Add forum label'] ?><div><input type="submit" name="add_forum" value="<?php echo $lang_admin_forums['Add forum'] ?>" tabindex="2" /></div></th>
+									<td>
+										<select name="add_to_cat" tabindex="1">
+<?php
+
+	$result = $db->query('SELECT id, cat_name FROM '.$db->prefix.'categories ORDER BY disp_position') or error('Unable to fetch category list', __FILE__, __LINE__, $db->error());
+	if ($db->num_rows($result) > 0)
+	{
+		while ($cur_cat = $db->fetch_assoc($result))
+			echo "\t\t\t\t\t\t\t\t\t\t\t".'<option value="'.$cur_cat['id'].'">'.pun_htmlspecialchars($cur_cat['cat_name']).'</option>'."\n";
+	}
+	else
+		echo "\t\t\t\t\t\t\t\t\t\t\t".'<option value="0" disabled="disabled">'.$lang_admin_forums['No categories exist'].'</option>'."\n";
+
+?>
+										</select>
+										<span><?php echo $lang_admin_forums['Add forum help'] ?></span>
+									</td>
+								</tr>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+			</form>
+		</div>
+<?php
+
+// Display all the categories and forums
+$result = $db->query('SELECT c.id AS cid, c.cat_name, f.id AS fid, f.forum_name, f.disp_position FROM '.$db->prefix.'categories AS c INNER JOIN '.$db->prefix.'forums AS f ON c.id=f.cat_id 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) > 0)
+{
+
+?>
+		<h2 class="block2"><span><?php echo $lang_admin_forums['Edit forums head'] ?></span></h2>
+		<div class="box">
+			<form id="edforum" method="post" action="admin_forums.php?action=edit">
+				<p class="submittop"><input type="submit" name="update_positions" value="<?php echo $lang_admin_forums['Update positions'] ?>" tabindex="3" /></p>
+<?php
+
+$cur_index = 4;
+
+$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 != 0)
+			echo "\t\t\t\t\t\t\t".'</tbody>'."\n\t\t\t\t\t\t\t".'</table>'."\n\t\t\t\t\t\t".'</div>'."\n\t\t\t\t\t".'</fieldset>'."\n\t\t\t\t".'</div>'."\n";
+
+?>
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_forums['Category subhead'] ?> <?php echo pun_htmlspecialchars($cur_forum['cat_name']) ?></legend>
+						<div class="infldset">
+							<table cellspacing="0">
+							<thead>
+								<tr>
+									<th class="tcl"><?php echo $lang_admin_common['Action'] ?></th>
+									<th class="tc2"><?php echo $lang_admin_forums['Position label'] ?></th>
+									<th class="tcr"><?php echo $lang_admin_forums['Forum label'] ?></th>
+								</tr>
+							</thead>
+							<tbody>
+<?php
+
+		$cur_category = $cur_forum['cid'];
+	}
+
+?>
+								<tr>
+									<td class="tcl"><a href="admin_forums.php?edit_forum=<?php echo $cur_forum['fid'] ?>" tabindex="<?php echo $cur_index++ ?>"><?php echo $lang_admin_forums['Edit link'] ?></a> | <a href="admin_forums.php?del_forum=<?php echo $cur_forum['fid'] ?>" tabindex="<?php echo $cur_index++ ?>"><?php echo $lang_admin_forums['Delete link'] ?></a></td>
+									<td class="tc2"><input type="text" name="position[<?php echo $cur_forum['fid'] ?>]" size="3" maxlength="3" value="<?php echo $cur_forum['disp_position'] ?>" tabindex="<?php echo $cur_index++ ?>" /></td>
+									<td class="tcr"><strong><?php echo pun_htmlspecialchars($cur_forum['forum_name']) ?></strong></td>
+								</tr>
+<?php
+
+}
+
+?>
+							</tbody>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+				<p class="submitend"><input type="submit" name="update_positions" value="<?php echo $lang_admin_forums['Update positions'] ?>" tabindex="<?php echo $cur_index++ ?>" /></p>
+			</form>
+		</div>
+<?php
+
+}
+
+?>
+	</div>
+	<div class="clearer"></div>
+</div>
+<?php
+
+require PUN_ROOT.'footer.php';
diff --git a/admin_groups.php b/admin_groups.php
new file mode 100644
index 0000000..888befc
--- /dev/null
+++ b/admin_groups.php
@@ -0,0 +1,570 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// Tell header.php to use the admin template
+define('PUN_ADMIN_CONSOLE', 1);
+
+define('PUN_ROOT', dirname(__FILE__).'/');
+require PUN_ROOT.'include/common.php';
+require PUN_ROOT.'include/common_admin.php';
+
+
+if ($pun_user['g_id'] != PUN_ADMIN)
+	message($lang_common['No permission'], false, '403 Forbidden');
+
+// Load the admin_censoring.php language file
+require PUN_ROOT.'lang/'.$admin_language.'/admin_groups.php';
+
+// Add/edit a group (stage 1)
+if (isset($_POST['add_group']) || isset($_GET['edit_group']))
+{
+	if (isset($_POST['add_group']))
+	{
+		$base_group = intval($_POST['base_group']);
+
+		$result = $db->query('SELECT * FROM '.$db->prefix.'groups WHERE g_id='.$base_group) or error('Unable to fetch user group info', __FILE__, __LINE__, $db->error());
+		$group = $db->fetch_assoc($result);
+
+		$mode = 'add';
+	}
+	else // We are editing a group
+	{
+		$group_id = intval($_GET['edit_group']);
+		if ($group_id < 1)
+			message($lang_common['Bad request']);
+
+		$result = $db->query('SELECT * FROM '.$db->prefix.'groups WHERE g_id='.$group_id) or error('Unable to fetch user group info', __FILE__, __LINE__, $db->error());
+		if (!$db->num_rows($result))
+			message($lang_common['Bad request']);
+
+		$group = $db->fetch_assoc($result);
+
+		$mode = 'edit';
+	}
+
+
+	$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_admin_common['Admin'], $lang_admin_common['User groups']);
+	$required_fields = array('req_title' => $lang_admin_groups['Group title label']);
+	$focus_element = array('groups2', 'req_title');
+	define('PUN_ACTIVE_PAGE', 'admin');
+	require PUN_ROOT.'header.php';
+
+	generate_admin_menu('groups');
+
+?>
+	<div class="blockform">
+		<h2><span><?php echo $lang_admin_groups['Group settings head'] ?></span></h2>
+		<div class="box">
+			<form id="groups2" method="post" action="admin_groups.php" onsubmit="return process_form(this)">
+				<p class="submittop"><input type="submit" name="add_edit_group" value="<?php echo $lang_admin_common['Save'] ?>" /></p>
+				<div class="inform">
+					<input type="hidden" name="mode" value="<?php echo $mode ?>" />
+<?php if ($mode == 'edit'): ?>					<input type="hidden" name="group_id" value="<?php echo $group_id ?>" />
+<?php endif; ?><?php if ($mode == 'add'): ?>					<input type="hidden" name="base_group" value="<?php echo $base_group ?>" />
+<?php endif; ?>					<fieldset>
+						<legend><?php echo $lang_admin_groups['Group settings subhead'] ?></legend>
+						<div class="infldset">
+							<p><?php echo $lang_admin_groups['Group settings info'] ?></p>
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_groups['Group title label'] ?></th>
+									<td>
+										<input type="text" name="req_title" size="25" maxlength="50" value="<?php if ($mode == 'edit') echo pun_htmlspecialchars($group['g_title']); ?>" tabindex="1" />
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_groups['User title label'] ?></th>
+									<td>
+										<input type="text" name="user_title" size="25" maxlength="50" value="<?php echo pun_htmlspecialchars($group['g_user_title']) ?>" tabindex="2" />
+										<span><?php echo $lang_admin_groups['User title help'] ?></span>
+									</td>
+								</tr>
+<?php if ($group['g_id'] != PUN_ADMIN): if ($group['g_id'] != PUN_GUEST): if ($mode != 'edit' || $pun_config['o_default_user_group'] != $group['g_id']): ?>								<tr>
+									<th scope="row"> <?php echo $lang_admin_groups['Mod privileges label'] ?></th>
+									<td>
+										<input type="radio" name="moderator" value="1"<?php if ($group['g_moderator'] == '1') echo ' checked="checked"' ?> tabindex="3" />&#160;<strong><?php echo $lang_admin_common['Yes'] ?></strong>&#160;&#160;&#160;<input type="radio" name="moderator" value="0"<?php if ($group['g_moderator'] == '0') echo ' checked="checked"' ?> tabindex="4" />&#160;<strong><?php echo $lang_admin_common['No'] ?></strong>
+										<span><?php echo $lang_admin_groups['Mod privileges help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_groups['Edit profile label'] ?></th>
+									<td>
+										<input type="radio" name="mod_edit_users" value="1"<?php if ($group['g_mod_edit_users'] == '1') echo ' checked="checked"' ?> tabindex="5" />&#160;<strong><?php echo $lang_admin_common['Yes'] ?></strong>&#160;&#160;&#160;<input type="radio" name="mod_edit_users" value="0"<?php if ($group['g_mod_edit_users'] == '0') echo ' checked="checked"' ?> tabindex="6" />&#160;<strong><?php echo $lang_admin_common['No'] ?></strong>
+										<span><?php echo $lang_admin_groups['Edit profile help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_groups['Rename users label'] ?></th>
+									<td>
+										<input type="radio" name="mod_rename_users" value="1"<?php if ($group['g_mod_rename_users'] == '1') echo ' checked="checked"' ?> tabindex="7" />&#160;<strong><?php echo $lang_admin_common['Yes'] ?></strong>&#160;&#160;&#160;<input type="radio" name="mod_rename_users" value="0"<?php if ($group['g_mod_rename_users'] == '0') echo ' checked="checked"' ?> tabindex="8" />&#160;<strong><?php echo $lang_admin_common['No'] ?></strong>
+										<span><?php echo $lang_admin_groups['Rename users help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_groups['Change passwords label'] ?></th>
+									<td>
+										<input type="radio" name="mod_change_passwords" value="1"<?php if ($group['g_mod_change_passwords'] == '1') echo ' checked="checked"' ?> tabindex="9" />&#160;<strong><?php echo $lang_admin_common['Yes'] ?></strong>&#160;&#160;&#160;<input type="radio" name="mod_change_passwords" value="0"<?php if ($group['g_mod_change_passwords'] == '0') echo ' checked="checked"' ?> tabindex="10" />&#160;<strong><?php echo $lang_admin_common['No'] ?></strong>
+										<span><?php echo $lang_admin_groups['Change passwords help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_groups['Ban users label'] ?></th>
+									<td>
+										<input type="radio" name="mod_ban_users" value="1"<?php if ($group['g_mod_ban_users'] == '1') echo ' checked="checked"' ?> tabindex="11" />&#160;<strong><?php echo $lang_admin_common['Yes'] ?></strong>&#160;&#160;&#160;<input type="radio" name="mod_ban_users" value="0"<?php if ($group['g_mod_ban_users'] == '0') echo ' checked="checked"' ?> tabindex="12" />&#160;<strong><?php echo $lang_admin_common['No'] ?></strong>
+										<span><?php echo $lang_admin_groups['Ban users help'] ?></span>
+									</td>
+								</tr>
+<?php endif; endif; ?>								<tr>
+									<th scope="row"><?php echo $lang_admin_groups['Read board label'] ?></th>
+									<td>
+										<input type="radio" name="read_board" value="1"<?php if ($group['g_read_board'] == '1') echo ' checked="checked"' ?> tabindex="13" />&#160;<strong><?php echo $lang_admin_common['Yes'] ?></strong>&#160;&#160;&#160;<input type="radio" name="read_board" value="0"<?php if ($group['g_read_board'] == '0') echo ' checked="checked"' ?> tabindex="14" />&#160;<strong><?php echo $lang_admin_common['No'] ?></strong>
+										<span><?php echo $lang_admin_groups['Read board help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_groups['View user info label'] ?></th>
+									<td>
+										<input type="radio" name="view_users" value="1"<?php if ($group['g_view_users'] == '1') echo ' checked="checked"' ?> tabindex="15" />&#160;<strong><?php echo $lang_admin_common['Yes'] ?></strong>&#160;&#160;&#160;<input type="radio" name="view_users" value="0"<?php if ($group['g_view_users'] == '0') echo ' checked="checked"' ?> tabindex="16" />&#160;<strong><?php echo $lang_admin_common['No'] ?></strong>
+										<span><?php echo $lang_admin_groups['View user info help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_groups['Post replies label'] ?></th>
+									<td>
+										<input type="radio" name="post_replies" value="1"<?php if ($group['g_post_replies'] == '1') echo ' checked="checked"' ?> tabindex="17" />&#160;<strong><?php echo $lang_admin_common['Yes'] ?></strong>&#160;&#160;&#160;<input type="radio" name="post_replies" value="0"<?php if ($group['g_post_replies'] == '0') echo ' checked="checked"' ?> tabindex="18" />&#160;<strong><?php echo $lang_admin_common['No'] ?></strong>
+										<span><?php echo $lang_admin_groups['Post replies help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_groups['Post topics label'] ?></th>
+									<td>
+										<input type="radio" name="post_topics" value="1"<?php if ($group['g_post_topics'] == '1') echo ' checked="checked"' ?> tabindex="19" />&#160;<strong><?php echo $lang_admin_common['Yes'] ?></strong>&#160;&#160;&#160;<input type="radio" name="post_topics" value="0"<?php if ($group['g_post_topics'] == '0') echo ' checked="checked"' ?> tabindex="20" />&#160;<strong><?php echo $lang_admin_common['No'] ?></strong>
+										<span><?php echo $lang_admin_groups['Post topics help'] ?></span>
+									</td>
+								</tr>
+<?php if ($group['g_id'] != PUN_GUEST): ?>								<tr>
+									<th scope="row"><?php echo $lang_admin_groups['Edit posts label'] ?></th>
+									<td>
+										<input type="radio" name="edit_posts" value="1"<?php if ($group['g_edit_posts'] == '1') echo ' checked="checked"' ?> tabindex="21" />&#160;<strong><?php echo $lang_admin_common['Yes'] ?></strong>&#160;&#160;&#160;<input type="radio" name="edit_posts" value="0"<?php if ($group['g_edit_posts'] == '0') echo ' checked="checked"' ?> tabindex="22" />&#160;<strong><?php echo $lang_admin_common['No'] ?></strong>
+										<span><?php echo $lang_admin_groups['Edit posts help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_groups['Delete posts label'] ?></th>
+									<td>
+										<input type="radio" name="delete_posts" value="1"<?php if ($group['g_delete_posts'] == '1') echo ' checked="checked"' ?> tabindex="23" />&#160;<strong><?php echo $lang_admin_common['Yes'] ?></strong>&#160;&#160;&#160;<input type="radio" name="delete_posts" value="0"<?php if ($group['g_delete_posts'] == '0') echo ' checked="checked"' ?> tabindex="24" />&#160;<strong><?php echo $lang_admin_common['No'] ?></strong>
+										<span><?php echo $lang_admin_groups['Delete posts help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_groups['Delete topics label'] ?></th>
+									<td>
+										<input type="radio" name="delete_topics" value="1"<?php if ($group['g_delete_topics'] == '1') echo ' checked="checked"' ?> tabindex="25" />&#160;<strong><?php echo $lang_admin_common['Yes'] ?></strong>&#160;&#160;&#160;<input type="radio" name="delete_topics" value="0"<?php if ($group['g_delete_topics'] == '0') echo ' checked="checked"' ?> tabindex="26" />&#160;<strong><?php echo $lang_admin_common['No'] ?></strong>
+										<span><?php echo $lang_admin_groups['Delete topics help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_groups['Set own title label'] ?></th>
+									<td>
+										<input type="radio" name="set_title" value="1"<?php if ($group['g_set_title'] == '1') echo ' checked="checked"' ?> tabindex="27" />&#160;<strong><?php echo $lang_admin_common['Yes'] ?></strong>&#160;&#160;&#160;<input type="radio" name="set_title" value="0"<?php if ($group['g_set_title'] == '0') echo ' checked="checked"' ?> tabindex="28" />&#160;<strong><?php echo $lang_admin_common['No'] ?></strong>
+										<span><?php echo $lang_admin_groups['Set own title help'] ?></span>
+									</td>
+								</tr>
+<?php endif; ?>								<tr>
+									<th scope="row"><?php echo $lang_admin_groups['User search label'] ?></th>
+									<td>
+										<input type="radio" name="search" value="1"<?php if ($group['g_search'] == '1') echo ' checked="checked"' ?> tabindex="29" />&#160;<strong><?php echo $lang_admin_common['Yes'] ?></strong>&#160;&#160;&#160;<input type="radio" name="search" value="0"<?php if ($group['g_search'] == '0') echo ' checked="checked"' ?> tabindex="30" />&#160;<strong><?php echo $lang_admin_common['No'] ?></strong>
+										<span><?php echo $lang_admin_groups['User search help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_groups['User list search label'] ?></th>
+									<td>
+										<input type="radio" name="search_users" value="1"<?php if ($group['g_search_users'] == '1') echo ' checked="checked"' ?> tabindex="31" />&#160;<strong><?php echo $lang_admin_common['Yes'] ?></strong>&#160;&#160;&#160;<input type="radio" name="search_users" value="0"<?php if ($group['g_search_users'] == '0') echo ' checked="checked"' ?> tabindex="32" />&#160;<strong><?php echo $lang_admin_common['No'] ?></strong>
+										<span><?php echo $lang_admin_groups['User list search help'] ?></span>
+									</td>
+								</tr>
+<?php if ($group['g_id'] != PUN_GUEST): ?>								<tr>
+									<th scope="row"><?php echo $lang_admin_groups['Send e-mails label'] ?></th>
+									<td>
+										<input type="radio" name="send_email" value="1"<?php if ($group['g_send_email'] == '1') echo ' checked="checked"' ?> tabindex="33" />&#160;<strong><?php echo $lang_admin_common['Yes'] ?></strong>&#160;&#160;&#160;<input type="radio" name="send_email" value="0"<?php if ($group['g_send_email'] == '0') echo ' checked="checked"' ?> tabindex="34" />&#160;<strong><?php echo $lang_admin_common['No'] ?></strong>
+										<span><?php echo $lang_admin_groups['Send e-mails help'] ?></span>
+									</td>
+								</tr>
+<?php endif; ?>								<tr>
+									<th scope="row"><?php echo $lang_admin_groups['Post flood label'] ?></th>
+									<td>
+										<input type="text" name="post_flood" size="5" maxlength="4" value="<?php echo $group['g_post_flood'] ?>" tabindex="35" />
+										<span><?php echo $lang_admin_groups['Post flood help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_groups['Search flood label'] ?></th>
+									<td>
+										<input type="text" name="search_flood" size="5" maxlength="4" value="<?php echo $group['g_search_flood'] ?>" tabindex="36" />
+										<span><?php echo $lang_admin_groups['Search flood help'] ?></span>
+									</td>
+								</tr>
+<?php if ($group['g_id'] != PUN_GUEST): ?>								<tr>
+									<th scope="row"><?php echo $lang_admin_groups['E-mail flood label'] ?></th>
+									<td>
+										<input type="text" name="email_flood" size="5" maxlength="4" value="<?php echo $group['g_email_flood'] ?>" tabindex="37" />
+										<span><?php echo $lang_admin_groups['E-mail flood help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_groups['Report flood label'] ?></th>
+									<td>
+										<input type="text" name="report_flood" size="5" maxlength="4" value="<?php echo $group['g_report_flood'] ?>" tabindex="38" />
+										<span><?php echo $lang_admin_groups['Report flood help'] ?></span>
+									</td>
+								</tr>
+<?php endif; endif; ?>							</table>
+<?php if ($group['g_moderator'] == '1' ): ?>							<p class="warntext"><?php echo $lang_admin_groups['Moderator info'] ?></p>
+<?php endif; ?>						</div>
+					</fieldset>
+				</div>
+				<p class="submitend"><input type="submit" name="add_edit_group" value="<?php echo $lang_admin_common['Save'] ?>" tabindex="39" /></p>
+			</form>
+		</div>
+	</div>
+	<div class="clearer"></div>
+</div>
+<?php
+
+	require PUN_ROOT.'footer.php';
+}
+
+
+// Add/edit a group (stage 2)
+else if (isset($_POST['add_edit_group']))
+{
+	confirm_referrer('admin_groups.php');
+
+	// Is this the admin group? (special rules apply)
+	$is_admin_group = (isset($_POST['group_id']) && $_POST['group_id'] == PUN_ADMIN) ? true : false;
+
+	$title = pun_trim($_POST['req_title']);
+	$user_title = pun_trim($_POST['user_title']);
+	$moderator = isset($_POST['moderator']) && $_POST['moderator'] == '1' ? '1' : '0';
+	$mod_edit_users = $moderator == '1' && isset($_POST['mod_edit_users']) && $_POST['mod_edit_users'] == '1' ? '1' : '0';
+	$mod_rename_users = $moderator == '1' && isset($_POST['mod_rename_users']) && $_POST['mod_rename_users'] == '1' ? '1' : '0';
+	$mod_change_passwords = $moderator == '1' && isset($_POST['mod_change_passwords']) && $_POST['mod_change_passwords'] == '1' ? '1' : '0';
+	$mod_ban_users = $moderator == '1' && isset($_POST['mod_ban_users']) && $_POST['mod_ban_users'] == '1' ? '1' : '0';
+	$read_board = isset($_POST['read_board']) ? intval($_POST['read_board']) : '1';
+	$view_users = (isset($_POST['view_users']) && $_POST['view_users'] == '1') || $is_admin_group ? '1' : '0';
+	$post_replies = isset($_POST['post_replies']) ? intval($_POST['post_replies']) : '1';
+	$post_topics = isset($_POST['post_topics']) ? intval($_POST['post_topics']) : '1';
+	$edit_posts = isset($_POST['edit_posts']) ? intval($_POST['edit_posts']) : ($is_admin_group) ? '1' : '0';
+	$delete_posts = isset($_POST['delete_posts']) ? intval($_POST['delete_posts']) : ($is_admin_group) ? '1' : '0';
+	$delete_topics = isset($_POST['delete_topics']) ? intval($_POST['delete_topics']) : ($is_admin_group) ? '1' : '0';
+	$set_title = isset($_POST['set_title']) ? intval($_POST['set_title']) : ($is_admin_group) ? '1' : '0';
+	$search = isset($_POST['search']) ? intval($_POST['search']) : '1';
+	$search_users = isset($_POST['search_users']) ? intval($_POST['search_users']) : '1';
+	$send_email = (isset($_POST['send_email']) && $_POST['send_email'] == '1') || $is_admin_group ? '1' : '0';
+	$post_flood = (isset($_POST['post_flood']) && $_POST['post_flood'] >= 0) ? intval($_POST['post_flood']) : '0';
+	$search_flood = (isset($_POST['search_flood']) && $_POST['search_flood'] >= 0) ? intval($_POST['search_flood']) : '0';
+	$email_flood = (isset($_POST['email_flood']) && $_POST['email_flood'] >= 0) ? intval($_POST['email_flood']) : '0';
+	$report_flood = (isset($_POST['report_flood']) && $_POST['report_flood'] >= 0) ? intval($_POST['report_flood']) : '0';
+
+	if ($title == '')
+		message($lang_admin_groups['Must enter title message']);
+
+	$user_title = ($user_title != '') ? '\''.$db->escape($user_title).'\'' : 'NULL';
+
+	if ($_POST['mode'] == 'add')
+	{
+		$result = $db->query('SELECT 1 FROM '.$db->prefix.'groups WHERE g_title=\''.$db->escape($title).'\'') or error('Unable to check group title collision', __FILE__, __LINE__, $db->error());
+		if ($db->num_rows($result))
+			message(sprintf($lang_admin_groups['Title already exists message'], pun_htmlspecialchars($title)));
+
+		$db->query('INSERT INTO '.$db->prefix.'groups (g_title, g_user_title, g_moderator, g_mod_edit_users, g_mod_rename_users, g_mod_change_passwords, g_mod_ban_users, g_read_board, g_view_users, g_post_replies, g_post_topics, g_edit_posts, g_delete_posts, g_delete_topics, g_set_title, g_search, g_search_users, g_send_email, g_post_flood, g_search_flood, g_email_flood, g_report_flood) VALUES(\''.$db->escape($title).'\', '.$user_title.', '.$moderator.', '.$mod_edit_users.', '.$mod_rename_users.', '.$mod_change_passwords.', '.$mod_ban_users.', '.$read_board.', '.$view_users.', '.$post_replies.', '.$post_topics.', '.$edit_posts.', '.$delete_posts.', '.$delete_topics.', '.$set_title.', '.$search.', '.$search_users.', '.$send_email.', '.$post_flood.', '.$search_flood.', '.$email_flood.', '.$report_flood.')') or error('Unable to add group', __FILE__, __LINE__, $db->error());
+		$new_group_id = $db->insert_id();
+
+		// Now lets copy the forum specific permissions from the group which this group is based on
+		$result = $db->query('SELECT forum_id, read_forum, post_replies, post_topics FROM '.$db->prefix.'forum_perms WHERE group_id='.intval($_POST['base_group'])) or error('Unable to fetch group forum permission list', __FILE__, __LINE__, $db->error());
+		while ($cur_forum_perm = $db->fetch_assoc($result))
+			$db->query('INSERT INTO '.$db->prefix.'forum_perms (group_id, forum_id, read_forum, post_replies, post_topics) VALUES('.$new_group_id.', '.$cur_forum_perm['forum_id'].', '.$cur_forum_perm['read_forum'].', '.$cur_forum_perm['post_replies'].', '.$cur_forum_perm['post_topics'].')') or error('Unable to insert group forum permissions', __FILE__, __LINE__, $db->error());
+	}
+	else
+	{
+		$result = $db->query('SELECT 1 FROM '.$db->prefix.'groups WHERE g_title=\''.$db->escape($title).'\' AND g_id!='.intval($_POST['group_id'])) or error('Unable to check group title collision', __FILE__, __LINE__, $db->error());
+		if ($db->num_rows($result))
+			message(sprintf($lang_admin_groups['Title already exists message'], pun_htmlspecialchars($title)));
+
+		$db->query('UPDATE '.$db->prefix.'groups SET g_title=\''.$db->escape($title).'\', g_user_title='.$user_title.', g_moderator='.$moderator.', g_mod_edit_users='.$mod_edit_users.', g_mod_rename_users='.$mod_rename_users.', g_mod_change_passwords='.$mod_change_passwords.', g_mod_ban_users='.$mod_ban_users.', g_read_board='.$read_board.', g_view_users='.$view_users.', g_post_replies='.$post_replies.', g_post_topics='.$post_topics.', g_edit_posts='.$edit_posts.', g_delete_posts='.$delete_posts.', g_delete_topics='.$delete_topics.', g_set_title='.$set_title.', g_search='.$search.', g_search_users='.$search_users.', g_send_email='.$send_email.', g_post_flood='.$post_flood.', g_search_flood='.$search_flood.', g_email_flood='.$email_flood.', g_report_flood='.$report_flood.' WHERE g_id='.intval($_POST['group_id'])) or error('Unable to update group', __FILE__, __LINE__, $db->error());
+	}
+
+	// Regenerate the quick jump cache
+	if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+		require PUN_ROOT.'include/cache.php';
+
+	$group_id = $_POST['mode'] == 'add' ? $new_group_id : intval($_POST['group_id']);
+	generate_quickjump_cache($group_id);
+
+	if ($_POST['mode'] == 'edit')
+		redirect('admin_groups.php', $lang_admin_groups['Group edited redirect']);
+	else
+		redirect('admin_groups.php', $lang_admin_groups['Group added redirect']);
+}
+
+
+// Set default group
+else if (isset($_POST['set_default_group']))
+{
+	confirm_referrer('admin_groups.php');
+
+	$group_id = intval($_POST['default_group']);
+
+	// Make sure it's not the admin or guest groups
+	if ($group_id == PUN_ADMIN || $group_id == PUN_GUEST)
+		message($lang_common['Bad request']);
+
+	// Make sure it's not a moderator group
+	$result = $db->query('SELECT 1 FROM '.$db->prefix.'groups WHERE g_id='.$group_id.' AND g_moderator=0') or error('Unable to check group moderator status', __FILE__, __LINE__, $db->error());
+	if (!$db->num_rows($result))
+		message($lang_common['Bad request']);
+
+	$db->query('UPDATE '.$db->prefix.'config SET conf_value='.$group_id.' WHERE conf_name=\'o_default_user_group\'') or error('Unable to update board config', __FILE__, __LINE__, $db->error());
+
+	// Regenerate the config cache
+	if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+		require PUN_ROOT.'include/cache.php';
+
+	generate_config_cache();
+
+	redirect('admin_groups.php', $lang_admin_groups['Default group redirect']);
+}
+
+
+// Remove a group
+else if (isset($_GET['del_group']))
+{
+	confirm_referrer('admin_groups.php');
+
+	$group_id = isset($_POST['group_to_delete']) ? intval($_POST['group_to_delete']) : intval($_GET['del_group']);
+	if ($group_id < 5)
+		message($lang_common['Bad request']);
+
+	// Make sure we don't remove the default group
+	if ($group_id == $pun_config['o_default_user_group'])
+		message($lang_admin_groups['Cannot remove default message']);
+
+	// Check if this group has any members
+	$result = $db->query('SELECT g.g_title, COUNT(u.id) FROM '.$db->prefix.'groups AS g INNER JOIN '.$db->prefix.'users AS u ON g.g_id=u.group_id WHERE g.g_id='.$group_id.' GROUP BY g.g_id, g_title') or error('Unable to fetch group info', __FILE__, __LINE__, $db->error());
+
+	// If the group doesn't have any members or if we've already selected a group to move the members to
+	if (!$db->num_rows($result) || isset($_POST['del_group']))
+	{
+		if (isset($_POST['del_group_comply']) || isset($_POST['del_group']))
+		{
+			if (isset($_POST['del_group']))
+			{
+				$move_to_group = intval($_POST['move_to_group']);
+				$db->query('UPDATE '.$db->prefix.'users SET group_id='.$move_to_group.' WHERE group_id='.$group_id) or error('Unable to move users into group', __FILE__, __LINE__, $db->error());
+			}
+
+			// Delete the group and any forum specific permissions
+			$db->query('DELETE FROM '.$db->prefix.'groups WHERE g_id='.$group_id) or error('Unable to delete group', __FILE__, __LINE__, $db->error());
+			$db->query('DELETE FROM '.$db->prefix.'forum_perms WHERE group_id='.$group_id) or error('Unable to delete group forum permissions', __FILE__, __LINE__, $db->error());
+
+			redirect('admin_groups.php', $lang_admin_groups['Group removed redirect']);
+		}
+		else
+		{
+			$result = $db->query('SELECT g_title FROM '.$db->prefix.'groups WHERE g_id='.$group_id) or error('Unable to fetch group title', __FILE__, __LINE__, $db->error());
+			$group_title = $db->result($result);
+
+			$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_admin_common['Admin'], $lang_admin_common['User groups']);
+			define('PUN_ACTIVE_PAGE', 'admin');
+			require PUN_ROOT.'header.php';
+
+			generate_admin_menu('groups');
+
+?>
+	<div class="blockform">
+		<h2><span><?php echo $lang_admin_groups['Group delete head'] ?></span></h2>
+		<div class="box">
+			<form method="post" action="admin_groups.php?del_group=<?php echo $group_id ?>">
+				<div class="inform">
+				<input type="hidden" name="group_to_delete" value="<?php echo $group_id ?>" />
+					<fieldset>
+						<legend><?php echo $lang_admin_groups['Confirm delete subhead'] ?></legend>
+						<div class="infldset">
+							<p><?php printf($lang_admin_groups['Confirm delete info'], pun_htmlspecialchars($group_title)) ?></p>
+							<p class="warntext"><?php echo $lang_admin_groups['Confirm delete warn'] ?></p>
+						</div>
+					</fieldset>
+				</div>
+				<p class="buttons"><input type="submit" name="del_group_comply" value="<?php echo $lang_admin_common['Delete'] ?>" tabindex="1" /><a href="javascript:history.go(-1)" tabindex="2"><?php echo $lang_admin_common['Go back'] ?></a></p>
+			</form>
+		</div>
+	</div>
+	<div class="clearer"></div>
+</div>
+<?php
+
+			require PUN_ROOT.'footer.php';
+		}
+	}
+
+	list($group_title, $group_members) = $db->fetch_row($result);
+
+	$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_admin_common['Admin'], $lang_admin_common['User groups']);
+	define('PUN_ACTIVE_PAGE', 'admin');
+	require PUN_ROOT.'header.php';
+
+	generate_admin_menu('groups');
+
+?>
+	<div class="blockform">
+		<h2><span><?php echo $lang_admin_groups['Delete group head'] ?></span></h2>
+		<div class="box">
+			<form id="groups" method="post" action="admin_groups.php?del_group=<?php echo $group_id ?>">
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_groups['Move users subhead'] ?></legend>
+						<div class="infldset">
+							<p><?php printf($lang_admin_groups['Move users info'], pun_htmlspecialchars($group_title), forum_number_format($group_members)) ?></p>
+							<label><?php echo $lang_admin_groups['Move users label'] ?>
+							<select name="move_to_group">
+<?php
+
+	$result = $db->query('SELECT g_id, g_title FROM '.$db->prefix.'groups WHERE g_id!='.PUN_GUEST.' AND g_id!='.$group_id.' ORDER BY g_title') or error('Unable to fetch user group list', __FILE__, __LINE__, $db->error());
+
+	while ($cur_group = $db->fetch_assoc($result))
+	{
+		if ($cur_group['g_id'] == PUN_MEMBER) // Pre-select the pre-defined Members group
+			echo "\t\t\t\t\t\t\t\t\t\t".'<option value="'.$cur_group['g_id'].'" selected="selected">'.pun_htmlspecialchars($cur_group['g_title']).'</option>'."\n";
+		else
+			echo "\t\t\t\t\t\t\t\t\t\t".'<option value="'.$cur_group['g_id'].'">'.pun_htmlspecialchars($cur_group['g_title']).'</option>'."\n";
+	}
+
+?>
+							</select>
+							<br /></label>
+						</div>
+					</fieldset>
+				</div>
+				<p class="buttons"><input type="submit" name="del_group" value="<?php echo $lang_admin_groups['Delete group'] ?>" /><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';
+}
+
+
+$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_admin_common['Admin'], $lang_admin_common['User groups']);
+define('PUN_ACTIVE_PAGE', 'admin');
+require PUN_ROOT.'header.php';
+
+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">
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_groups['Add group subhead'] ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_groups['New group label'] ?><div><input type="submit" name="add_group" value="<?php echo $lang_admin_common['Add'] ?>" tabindex="2" /></div></th>
+									<td>
+										<select id="base_group" name="base_group" tabindex="1">
+<?php
+
+$result = $db->query('SELECT g_id, g_title FROM '.$db->prefix.'groups WHERE g_id!='.PUN_ADMIN.' AND g_id!='.PUN_GUEST.' ORDER BY g_title') or error('Unable to fetch user group list', __FILE__, __LINE__, $db->error());
+
+while ($cur_group = $db->fetch_assoc($result))
+{
+	if ($cur_group['g_id'] == $pun_config['o_default_user_group'])
+		echo "\t\t\t\t\t\t\t\t\t\t\t".'<option value="'.$cur_group['g_id'].'" selected="selected">'.pun_htmlspecialchars($cur_group['g_title']).'</option>'."\n";
+	else
+		echo "\t\t\t\t\t\t\t\t\t\t\t".'<option value="'.$cur_group['g_id'].'">'.pun_htmlspecialchars($cur_group['g_title']).'</option>'."\n";
+}
+
+?>
+										</select>
+										<span><?php echo $lang_admin_groups['New group help'] ?></span>
+									</td>
+								</tr>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_groups['Default group subhead'] ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_groups['Default group label'] ?><div><input type="submit" name="set_default_group" value="<?php echo $lang_admin_common['Save'] ?>" tabindex="4" /></div></th>
+									<td>
+										<select id="default_group" name="default_group" tabindex="3">
+<?php
+
+$result = $db->query('SELECT g_id, g_title FROM '.$db->prefix.'groups WHERE g_id>'.PUN_GUEST.' AND g_moderator=0 ORDER BY g_title') or error('Unable to fetch user group list', __FILE__, __LINE__, $db->error());
+
+while ($cur_group = $db->fetch_assoc($result))
+{
+	if ($cur_group['g_id'] == $pun_config['o_default_user_group'])
+		echo "\t\t\t\t\t\t\t\t\t\t\t".'<option value="'.$cur_group['g_id'].'" selected="selected">'.pun_htmlspecialchars($cur_group['g_title']).'</option>'."\n";
+	else
+		echo "\t\t\t\t\t\t\t\t\t\t\t".'<option value="'.$cur_group['g_id'].'">'.pun_htmlspecialchars($cur_group['g_title']).'</option>'."\n";
+}
+
+?>
+										</select>
+										<span><?php echo $lang_admin_groups['Default group help'] ?></span>
+									</td>
+								</tr>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+			</form>
+		</div>
+
+		<h2 class="block2"><span><?php echo $lang_admin_groups['Existing groups head'] ?></span></h2>
+		<div class="box">
+			<div class="fakeform">
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_groups['Edit groups subhead'] ?></legend>
+						<div class="infldset">
+							<p><?php echo $lang_admin_groups['Edit groups info'] ?></p>
+							<table cellspacing="0">
+<?php
+
+$cur_index = 5;
+
+$result = $db->query('SELECT g_id, g_title FROM '.$db->prefix.'groups ORDER BY g_id') or error('Unable to fetch user group list', __FILE__, __LINE__, $db->error());
+
+while ($cur_group = $db->fetch_assoc($result))
+	echo "\t\t\t\t\t\t\t\t".'<tr><th scope="row"><a href="admin_groups.php?edit_group='.$cur_group['g_id'].'" tabindex="'.$cur_index++.'">'.$lang_admin_groups['Edit link'].'</a>'.(($cur_group['g_id'] > PUN_MEMBER) ? ' | <a href="admin_groups.php?del_group='.$cur_group['g_id'].'" tabindex="'.$cur_index++.'">'.$lang_admin_groups['Delete link'].'</a>' : '').'</th><td>'.pun_htmlspecialchars($cur_group['g_title']).'</td></tr>'."\n";
+
+?>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+			</div>
+		</div>
+	</div>
+	<div class="clearer"></div>
+</div>
+<?php
+
+require PUN_ROOT.'footer.php';
diff --git a/admin_index.php b/admin_index.php
new file mode 100644
index 0000000..5d82031
--- /dev/null
+++ b/admin_index.php
@@ -0,0 +1,177 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// Tell header.php to use the admin template
+define('PUN_ADMIN_CONSOLE', 1);
+
+define('PUN_ROOT', dirname(__FILE__).'/');
+require PUN_ROOT.'include/common.php';
+require PUN_ROOT.'include/common_admin.php';
+
+
+if (!$pun_user['is_admmod'])
+	message($lang_common['No permission'], false, '403 Forbidden');
+
+// Load the admin_index.php language file
+require PUN_ROOT.'lang/'.$admin_language.'/admin_index.php';
+
+$action = isset($_GET['action']) ? $_GET['action'] : null;
+
+// Check for upgrade
+if ($action == 'check_upgrade')
+{
+	if (!ini_get('allow_url_fopen'))
+		message($lang_admin_index['fopen disabled message']);
+
+	$latest_version = trim(@file_get_contents('http://fluxbb.org/latest_version'));
+	if (empty($latest_version))
+		message($lang_admin_index['Upgrade check failed message']);
+
+	if (version_compare($pun_config['o_cur_version'], $latest_version, '>='))
+		message($lang_admin_index['Running latest version message']);
+	else
+		message(sprintf($lang_admin_index['New version available message'], '<a href="http://fluxbb.org/">FluxBB.org</a>'));
+}
+
+
+// Show phpinfo() output
+else if ($action == 'phpinfo' && $pun_user['g_id'] == PUN_ADMIN)
+{
+	// Is phpinfo() a disabled function?
+	if (strpos(strtolower((string) ini_get('disable_functions')), 'phpinfo') !== false)
+		message($lang_admin_index['PHPinfo disabled message']);
+
+	phpinfo();
+	exit;
+}
+
+
+// Get the server load averages (if possible)
+if (@file_exists('/proc/loadavg') && is_readable('/proc/loadavg'))
+{
+	// We use @ just in case
+	$fh = @fopen('/proc/loadavg', 'r');
+	$load_averages = @fread($fh, 64);
+	@fclose($fh);
+
+	if (($fh = @fopen('/proc/loadavg', 'r')))
+	{
+		$load_averages = fread($fh, 64);
+		fclose($fh);
+	}
+	else
+		$load_averages = '';
+
+	$load_averages = @explode(' ', $load_averages);
+	$server_load = isset($load_averages[2]) ? $load_averages[0].' '.$load_averages[1].' '.$load_averages[2] : $lang_admin_index['Not available'];
+}
+else if (!in_array(PHP_OS, array('WINNT', 'WIN32')) && preg_match('%averages?: ([0-9\.]+),?\s+([0-9\.]+),?\s+([0-9\.]+)%i', @exec('uptime'), $load_averages))
+	$server_load = $load_averages[1].' '.$load_averages[2].' '.$load_averages[3];
+else
+	$server_load = $lang_admin_index['Not available'];
+
+
+// Get number of current visitors
+$result = $db->query('SELECT COUNT(user_id) FROM '.$db->prefix.'online WHERE idle=0') or error('Unable to fetch online count', __FILE__, __LINE__, $db->error());
+$num_online = $db->result($result);
+
+
+// Collect some additional info about MySQL
+if ($db_type == 'mysql' || $db_type == 'mysqli' || $db_type == 'mysql_innodb' || $db_type == 'mysqli_innodb')
+{
+	// Calculate total db size/row count
+	$result = $db->query('SHOW TABLE STATUS LIKE \''.$db->prefix.'%\'') or error('Unable to fetch table status', __FILE__, __LINE__, $db->error());
+
+	$total_records = $total_size = 0;
+	while ($status = $db->fetch_assoc($result))
+	{
+		$total_records += $status['Rows'];
+		$total_size += $status['Data_length'] + $status['Index_length'];
+	}
+
+	$total_size = file_size($total_size);
+}
+
+
+// Check for the existence of various PHP opcode caches/optimizers
+if (function_exists('mmcache'))
+	$php_accelerator = '<a href="http://'.$lang_admin_index['Turck MMCache link'].'">'.$lang_admin_index['Turck MMCache'].'</a>';
+else if (isset($_PHPA))
+	$php_accelerator = '<a href="http://'.$lang_admin_index['ionCube PHP Accelerator link'].'">'.$lang_admin_index['ionCube PHP Accelerator'].'</a>';
+else if (ini_get('apc.enabled'))
+	$php_accelerator ='<a href="http://'.$lang_admin_index['Alternative PHP Cache (APC) link'].'">'.$lang_admin_index['Alternative PHP Cache (APC)'].'</a>';
+else if (ini_get('zend_optimizer.optimization_level'))
+	$php_accelerator = '<a href="http://'.$lang_admin_index['Zend Optimizer link'].'">'.$lang_admin_index['Zend Optimizer'].'</a>';
+else if (ini_get('eaccelerator.enable'))
+	$php_accelerator = '<a href="http://'.$lang_admin_index['eAccelerator link'].'">'.$lang_admin_index['eAccelerator'].'</a>';
+else if (ini_get('xcache.cacher'))
+	$php_accelerator = '<a href="http://'.$lang_admin_index['XCache link'].'">'.$lang_admin_index['XCache'].'</a>';
+else
+	$php_accelerator = $lang_admin_index['NA'];
+
+
+$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_admin_common['Admin'], $lang_admin_common['Index']);
+define('PUN_ACTIVE_PAGE', 'admin');
+require PUN_ROOT.'header.php';
+
+generate_admin_menu('index');
+
+?>
+	<div class="block">
+		<h2><span><?php echo $lang_admin_index['Forum admin head'] ?></span></h2>
+		<div id="adintro" class="box">
+			<div class="inbox">
+				<p><?php echo $lang_admin_index['Welcome to admin'] ?></p>
+				<ul>
+					<li><span><?php echo $lang_admin_index['Welcome 1'] ?></span></li>
+					<li><span><?php echo $lang_admin_index['Welcome 2'] ?></span></li>
+					<li><span><?php echo $lang_admin_index['Welcome 3'] ?></span></li>
+					<li><span><?php echo $lang_admin_index['Welcome 4'] ?></span></li>
+					<li><span><?php echo $lang_admin_index['Welcome 5'] ?></span></li>
+					<li><span><?php echo $lang_admin_index['Welcome 6'] ?></span></li>
+					<li><span><?php echo $lang_admin_index['Welcome 7'] ?></span></li>
+					<li><span><?php echo $lang_admin_index['Welcome 8'] ?></span></li>
+					<li><span><?php echo $lang_admin_index['Welcome 9'] ?></span></li>
+				</ul>
+			</div>
+		</div>
+
+		<h2 class="block2"><span><?php echo $lang_admin_index['Statistics head'] ?></span></h2>
+		<div id="adstats" class="box">
+			<div class="inbox">
+				<dl>
+					<dt><?php echo $lang_admin_index['FluxBB version label'] ?></dt>
+					<dd>
+						<?php printf($lang_admin_index['FluxBB version data']."\n", $pun_config['o_cur_version'], '<a href="admin_index.php?action=check_upgrade">'.$lang_admin_index['Check for upgrade'].'</a>') ?>
+					</dd>
+					<dt><?php echo $lang_admin_index['Server load label'] ?></dt>
+					<dd>
+						<?php printf($lang_admin_index['Server load data']."\n", $server_load, $num_online) ?>
+					</dd>
+<?php if ($pun_user['g_id'] == PUN_ADMIN): ?>					<dt><?php echo $lang_admin_index['Environment label'] ?></dt>
+					<dd>
+						<?php printf($lang_admin_index['Environment data OS'], PHP_OS) ?><br />
+						<?php printf($lang_admin_index['Environment data version'], phpversion(), '<a href="admin_index.php?action=phpinfo">'.$lang_admin_index['Show info'].'</a>') ?><br />
+						<?php printf($lang_admin_index['Environment data acc']."\n", $php_accelerator) ?>
+					</dd>
+					<dt><?php echo $lang_admin_index['Database label'] ?></dt>
+					<dd>
+						<?php echo implode(' ', $db->get_version())."\n" ?>
+<?php if (isset($total_records) && isset($total_size)): ?>						<br /><?php printf($lang_admin_index['Database data rows']."\n", forum_number_format($total_records)) ?>
+						<br /><?php printf($lang_admin_index['Database data size']."\n", $total_size) ?>
+<?php endif; ?>					</dd>
+<?php endif; ?>
+				</dl>
+			</div>
+		</div>
+	</div>
+	<div class="clearer"></div>
+</div>
+<?php
+
+require PUN_ROOT.'footer.php';
diff --git a/admin_loader.php b/admin_loader.php
new file mode 100644
index 0000000..0eedc47
--- /dev/null
+++ b/admin_loader.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// Tell header.php to use the admin template
+define('PUN_ADMIN_CONSOLE', 1);
+
+define('PUN_ROOT', dirname(__FILE__).'/');
+require PUN_ROOT.'include/common.php';
+require PUN_ROOT.'include/common_admin.php';
+
+
+if (!$pun_user['is_admmod'])
+	message($lang_common['No permission'], false, '403 Forbidden');
+
+// The plugin to load should be supplied via GET
+$plugin = isset($_GET['plugin']) ? $_GET['plugin'] : '';
+if (!preg_match('%^AM?P_(\w*?)\.php$%i', $plugin))
+	message($lang_common['Bad request']);
+
+// AP_ == Admins only, AMP_ == admins and moderators
+$prefix = substr($plugin, 0, strpos($plugin, '_'));
+if ($pun_user['g_moderator'] == '1' && $prefix == 'AP')
+	message($lang_common['No permission'], false, '403 Forbidden');
+
+// Make sure the file actually exists
+if (!file_exists(PUN_ROOT.'plugins/'.$plugin))
+	message(sprintf($lang_admin_common['No plugin message'], $plugin));
+
+// Construct REQUEST_URI if it isn't set
+if (!isset($_SERVER['REQUEST_URI']))
+	$_SERVER['REQUEST_URI'] = (isset($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : '').'?'.(isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '');
+
+$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_common['Admin'], str_replace('_', ' ', substr($plugin, strpos($plugin, '_') + 1, -4)));
+define('PUN_ACTIVE_PAGE', 'admin');
+require PUN_ROOT.'header.php';
+
+// Attempt to load the plugin. We don't use @ here to supress error messages,
+// because if we did and a parse error occurred in the plugin, we would only
+// get the "blank page of death"
+include PUN_ROOT.'plugins/'.$plugin;
+if (!defined('PUN_PLUGIN_LOADED'))
+	message(sprintf($lang_admin_common['Plugin failed message'], $plugin));
+
+// Output the clearer div
+?>
+	<div class="clearer"></div>
+</div>
+<?php
+
+require PUN_ROOT.'footer.php';
diff --git a/admin_maintenance.php b/admin_maintenance.php
new file mode 100644
index 0000000..aff2958
--- /dev/null
+++ b/admin_maintenance.php
@@ -0,0 +1,358 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// Tell header.php to use the admin template
+define('PUN_ADMIN_CONSOLE', 1);
+// Tell common.php that we don't want output buffering
+define('PUN_DISABLE_BUFFERING', 1);
+
+define('PUN_ROOT', dirname(__FILE__).'/');
+require PUN_ROOT.'include/common.php';
+require PUN_ROOT.'include/common_admin.php';
+
+
+if ($pun_user['g_id'] != PUN_ADMIN)
+	message($lang_common['No permission'], false, '403 Forbidden');
+
+// Load the admin_maintenance.php language file
+require PUN_ROOT.'lang/'.$admin_language.'/admin_maintenance.php';
+
+$action = isset($_REQUEST['action']) ? pun_trim($_REQUEST['action']) : '';
+
+if ($action == 'rebuild')
+{
+	$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['Posts must be integer message']);
+
+	@set_time_limit(0);
+
+	// If this is the first cycle of posts we empty the search index before we proceed
+	if (isset($_GET['i_empty_index']))
+	{
+		// This is the only potentially "dangerous" thing we can do here, so we check the referer
+		confirm_referrer('admin_maintenance.php');
+
+		$db->truncate_table('search_matches') or error('Unable to empty search index match table', __FILE__, __LINE__, $db->error());
+		$db->truncate_table('search_words') or error('Unable to empty search index words table', __FILE__, __LINE__, $db->error());
+
+		// Reset the sequence for the search words (not needed for SQLite)
+		switch ($db_type)
+		{
+			case 'mysql':
+			case 'mysqli':
+			case 'mysql_innodb':
+			case 'mysqli_innodb':
+				$result = $db->query('ALTER TABLE '.$db->prefix.'search_words auto_increment=1') or error('Unable to update table auto_increment', __FILE__, __LINE__, $db->error());
+				break;
+
+			case 'pgsql';
+				$result = $db->query('SELECT setval(\''.$db->prefix.'search_words_id_seq\', 1, false)') or error('Unable to update sequence', __FILE__, __LINE__, $db->error());
+		}
+	}
+
+	$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_admin_maintenance['Rebuilding search index']);
+
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<title><?php echo generate_page_title($page_title) ?></title>
+<style type="text/css">
+body {
+	font: 12px Verdana, Arial, Helvetica, sans-serif;
+	color: #333333;
+	background-color: #FFFFFF
+}
+
+h1 {
+	font-size: 16px;
+	font-weight: normal;
+}
+</style>
+</head>
+<body>
+
+<h1><?php echo $lang_admin_maintenance['Rebuilding index info'] ?></h1>
+<hr />
+
+<?php
+
+	$query_str = '';
+
+	require PUN_ROOT.'include/search_idx.php';
+
+	// Fetch posts to process this cycle
+	$result = $db->query('SELECT p.id, p.message, t.subject, t.first_post_id FROM '.$db->prefix.'posts AS p INNER JOIN '.$db->prefix.'topics AS t ON t.id=p.topic_id WHERE p.id >= '.$start_at.' ORDER BY p.id ASC LIMIT '.$per_page) or error('Unable to fetch posts', __FILE__, __LINE__, $db->error());
+
+	$end_at = 0;
+	while ($cur_item = $db->fetch_assoc($result))
+	{
+		echo '<p><span>'.sprintf($lang_admin_maintenance['Processing post'], $cur_item['id']).'</span></p>'."\n";
+
+		if ($cur_item['id'] == $cur_item['first_post_id'])
+			update_search_index('post', $cur_item['id'], $cur_item['message'], $cur_item['subject']);
+		else
+			update_search_index('post', $cur_item['id'], $cur_item['message']);
+
+		$end_at = $cur_item['id'];
+	}
+
+	// Check if there is more work to do
+	if ($end_at > 0)
+	{
+		$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 = '?action=rebuild&i_per_page='.$per_page.'&i_start_at='.$db->result($result);
+	}
+
+	$db->end_transaction();
+	$db->close();
+
+	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 = pun_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 = pun_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());
+if ($db->num_rows($result))
+	$first_id = $db->result($result);
+
+$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_admin_common['Admin'], $lang_admin_common['Maintenance']);
+define('PUN_ACTIVE_PAGE', 'admin');
+require PUN_ROOT.'header.php';
+
+generate_admin_menu('maintenance');
+
+?>
+	<div class="blockform">
+		<h2><span><?php echo $lang_admin_maintenance['Maintenance head'] ?></span></h2>
+		<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">
+							<p><?php printf($lang_admin_maintenance['Rebuild index info'], '<a href="admin_options.php#maintenance">'.$lang_admin_common['Maintenance mode'].'</a>') ?></p>
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_maintenance['Posts per cycle label'] ?></th>
+									<td>
+										<input type="text" name="i_per_page" size="7" maxlength="7" value="300" tabindex="1" />
+										<span><?php echo $lang_admin_maintenance['Posts per cycle help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_maintenance['Starting post label'] ?></th>
+									<td>
+										<input type="text" name="i_start_at" size="7" maxlength="7" value="<?php echo (isset($first_id)) ? $first_id : 0 ?>" tabindex="2" />
+										<span><?php echo $lang_admin_maintenance['Starting post help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_maintenance['Empty index label'] ?></th>
+									<td class="inputadmin">
+										<span><input type="checkbox" name="i_empty_index" value="1" tabindex="3" checked="checked" />&#160;&#160;<?php echo $lang_admin_maintenance['Empty index help'] ?></span>
+									</td>
+								</tr>
+							</table>
+							<p class="topspace"><?php echo $lang_admin_maintenance['Rebuild completed info'] ?></p>
+							<div class="fsetsubmit"><input type="submit" name="rebuild_index" value="<?php echo $lang_admin_maintenance['Rebuild index'] ?>" tabindex="4" /></div>
+						</div>
+					</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" />&#160;<strong><?php echo $lang_admin_common['Yes'] ?></strong>&#160;&#160;&#160;<input type="radio" name="prune_sticky" value="0" />&#160;<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>
+</div>
+<?php
+
+require PUN_ROOT.'footer.php';
diff --git a/admin_options.php b/admin_options.php
new file mode 100644
index 0000000..48e2244
--- /dev/null
+++ b/admin_options.php
@@ -0,0 +1,853 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// Tell header.php to use the admin template
+define('PUN_ADMIN_CONSOLE', 1);
+
+define('PUN_ROOT', dirname(__FILE__).'/');
+require PUN_ROOT.'include/common.php';
+require PUN_ROOT.'include/common_admin.php';
+
+
+if ($pun_user['g_id'] != PUN_ADMIN)
+	message($lang_common['No permission'], false, '403 Forbidden');
+
+// Load the admin_options.php language file
+require PUN_ROOT.'lang/'.$admin_language.'/admin_options.php';
+
+if (isset($_POST['form_sent']))
+{
+	confirm_referrer('admin_options.php', $lang_admin_options['Bad HTTP Referer message']);
+
+	$form = array(
+		'board_title'			=> pun_trim($_POST['form']['board_title']),
+		'board_desc'			=> pun_trim($_POST['form']['board_desc']),
+		'base_url'				=> pun_trim($_POST['form']['base_url']),
+		'default_timezone'		=> floatval($_POST['form']['default_timezone']),
+		'default_dst'			=> $_POST['form']['default_dst'] != '1' ? '0' : '1',
+		'default_lang'			=> pun_trim($_POST['form']['default_lang']),
+		'default_style'			=> pun_trim($_POST['form']['default_style']),
+		'time_format'			=> pun_trim($_POST['form']['time_format']),
+		'date_format'			=> pun_trim($_POST['form']['date_format']),
+		'timeout_visit'			=> (intval($_POST['form']['timeout_visit']) > 0) ? intval($_POST['form']['timeout_visit']) : 1,
+		'timeout_online'		=> (intval($_POST['form']['timeout_online']) > 0) ? intval($_POST['form']['timeout_online']) : 1,
+		'redirect_delay'		=> (intval($_POST['form']['redirect_delay']) >= 0) ? intval($_POST['form']['redirect_delay']) : 0,
+		'show_version'			=> $_POST['form']['show_version'] != '1' ? '0' : '1',
+		'show_user_info'		=> $_POST['form']['show_user_info'] != '1' ? '0' : '1',
+		'show_post_count'		=> $_POST['form']['show_post_count'] != '1' ? '0' : '1',
+		'smilies'				=> $_POST['form']['smilies'] != '1' ? '0' : '1',
+		'smilies_sig'			=> $_POST['form']['smilies_sig'] != '1' ? '0' : '1',
+		'make_links'			=> $_POST['form']['make_links'] != '1' ? '0' : '1',
+		'topic_review'			=> (intval($_POST['form']['topic_review']) >= 0) ? intval($_POST['form']['topic_review']) : 0,
+		'disp_topics_default'	=> intval($_POST['form']['disp_topics_default']),
+		'disp_posts_default'	=> intval($_POST['form']['disp_posts_default']),
+		'indent_num_spaces'		=> (intval($_POST['form']['indent_num_spaces']) >= 0) ? intval($_POST['form']['indent_num_spaces']) : 0,
+		'quote_depth'			=> (intval($_POST['form']['quote_depth']) > 0) ? intval($_POST['form']['quote_depth']) : 1,
+		'quickpost'				=> $_POST['form']['quickpost'] != '1' ? '0' : '1',
+		'users_online'			=> $_POST['form']['users_online'] != '1' ? '0' : '1',
+		'censoring'				=> $_POST['form']['censoring'] != '1' ? '0' : '1',
+		'signatures'			=> $_POST['form']['signatures'] != '1' ? '0' : '1',
+		'ranks'					=> $_POST['form']['ranks'] != '1' ? '0' : '1',
+		'show_dot'				=> $_POST['form']['show_dot'] != '1' ? '0' : '1',
+		'topic_views'			=> $_POST['form']['topic_views'] != '1' ? '0' : '1',
+		'quickjump'				=> $_POST['form']['quickjump'] != '1' ? '0' : '1',
+		'gzip'					=> $_POST['form']['gzip'] != '1' ? '0' : '1',
+		'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',
+		'avatars_dir'			=> pun_trim($_POST['form']['avatars_dir']),
+		'avatars_width'			=> (intval($_POST['form']['avatars_width']) > 0) ? intval($_POST['form']['avatars_width']) : 1,
+		'avatars_height'		=> (intval($_POST['form']['avatars_height']) > 0) ? intval($_POST['form']['avatars_height']) : 1,
+		'avatars_size'			=> (intval($_POST['form']['avatars_size']) > 0) ? intval($_POST['form']['avatars_size']) : 1,
+		'admin_email'			=> strtolower(pun_trim($_POST['form']['admin_email'])),
+		'webmaster_email'		=> strtolower(pun_trim($_POST['form']['webmaster_email'])),
+		'forum_subscriptions'	=> $_POST['form']['forum_subscriptions'] != '1' ? '0' : '1',
+		'topic_subscriptions'	=> $_POST['form']['topic_subscriptions'] != '1' ? '0' : '1',
+		'smtp_host'				=> pun_trim($_POST['form']['smtp_host']),
+		'smtp_user'				=> pun_trim($_POST['form']['smtp_user']),
+		'smtp_ssl'				=> $_POST['form']['smtp_ssl'] != '1' ? '0' : '1',
+		'regs_allow'			=> $_POST['form']['regs_allow'] != '1' ? '0' : '1',
+		'regs_verify'			=> $_POST['form']['regs_verify'] != '1' ? '0' : '1',
+		'regs_report'			=> $_POST['form']['regs_report'] != '1' ? '0' : '1',
+		'rules'					=> $_POST['form']['rules'] != '1' ? '0' : '1',
+		'rules_message'			=> pun_trim($_POST['form']['rules_message']),
+		'default_email_setting'	=> intval($_POST['form']['default_email_setting']),
+		'announcement'			=> $_POST['form']['announcement'] != '1' ? '0' : '1',
+		'announcement_message'	=> pun_trim($_POST['form']['announcement_message']),
+		'maintenance'			=> $_POST['form']['maintenance'] != '1' ? '0' : '1',
+		'maintenance_message'	=> pun_trim($_POST['form']['maintenance_message']),
+	);
+
+	if ($form['board_title'] == '')
+		message($lang_admin_options['Must enter title message']);
+
+	// Make sure base_url doesn't end with a slash
+	if (substr($form['base_url'], -1) == '/')
+		$form['base_url'] = substr($form['base_url'], 0, -1);
+
+	$languages = forum_list_langs();
+	if (!in_array($form['default_lang'], $languages))
+		message($lang_common['Bad request']);
+
+	$styles = forum_list_styles();
+	if (!in_array($form['default_style'], $styles))
+		message($lang_common['Bad request']);
+
+	if ($form['time_format'] == '')
+		$form['time_format'] = 'H:i:s';
+
+	if ($form['date_format'] == '')
+		$form['date_format'] = 'Y-m-d';
+
+
+	require PUN_ROOT.'include/email.php';
+
+	if (!is_valid_email($form['admin_email']))
+		message($lang_admin_options['Invalid e-mail message']);
+
+	if (!is_valid_email($form['webmaster_email']))
+		message($lang_admin_options['Invalid webmaster e-mail message']);
+
+	if ($form['mailing_list'] != '')
+		$form['mailing_list'] = strtolower(preg_replace('%\s%S', '', $form['mailing_list']));
+
+	// Make sure avatars_dir doesn't end with a slash
+	if (substr($form['avatars_dir'], -1) == '/')
+		$form['avatars_dir'] = substr($form['avatars_dir'], 0, -1);
+
+	if ($form['additional_navlinks'] != '')
+		$form['additional_navlinks'] = pun_trim(pun_linebreaks($form['additional_navlinks']));
+
+	// Change or enter a SMTP password
+	if (isset($_POST['form']['smtp_change_pass']))
+	{
+		$smtp_pass1 = isset($_POST['form']['smtp_pass1']) ? pun_trim($_POST['form']['smtp_pass1']) : '';
+		$smtp_pass2 = isset($_POST['form']['smtp_pass2']) ? pun_trim($_POST['form']['smtp_pass2']) : '';
+
+		if ($smtp_pass1 == $smtp_pass2)
+			$form['smtp_pass'] = $smtp_pass1;
+		else
+			message($lang_admin_options['SMTP passwords did not match']);
+	}
+
+	if ($form['announcement_message'] != '')
+		$form['announcement_message'] = pun_linebreaks($form['announcement_message']);
+	else
+	{
+		$form['announcement_message'] = $lang_admin_options['Enter announcement here'];
+		$form['announcement'] = '0';
+	}
+
+	if ($form['rules_message'] != '')
+		$form['rules_message'] = pun_linebreaks($form['rules_message']);
+	else
+	{
+		$form['rules_message'] = $lang_admin_options['Enter rules here'];
+		$form['rules'] = '0';
+	}
+
+	if ($form['maintenance_message'] != '')
+		$form['maintenance_message'] = pun_linebreaks($form['maintenance_message']);
+	else
+	{
+		$form['maintenance_message'] = $lang_admin_options['Default maintenance message'];
+		$form['maintenance'] = '0';
+	}
+
+	// Make sure the number of displayed topics and posts is between 3 and 75
+	if ($form['disp_topics_default'] < 3)
+		$form['disp_topics_default'] = 3;
+	else if ($form['disp_topics_default'] > 75)
+		$form['disp_topics_default'] = 75;
+
+	if ($form['disp_posts_default'] < 3)
+		$form['disp_posts_default'] = 3;
+	else if ($form['disp_posts_default'] > 75)
+		$form['disp_posts_default'] = 75;
+
+	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']);
+
+	if ($form['default_email_setting'] < 0 || $form['default_email_setting'] > 2)
+		message($lang_common['Bad request']);
+
+	if ($form['timeout_online'] >= $form['timeout_visit'])
+		message($lang_admin_options['Timeout error message']);
+
+	foreach ($form as $key => $input)
+	{
+		// Only update values that have changed
+		if (array_key_exists('o_'.$key, $pun_config) && $pun_config['o_'.$key] != $input)
+		{
+			if ($input != '' || is_int($input))
+				$value = '\''.$db->escape($input).'\'';
+			else
+				$value = 'NULL';
+
+			$db->query('UPDATE '.$db->prefix.'config SET conf_value='.$value.' WHERE conf_name=\'o_'.$db->escape($key).'\'') or error('Unable to update board config', __FILE__, __LINE__, $db->error());
+		}
+	}
+
+	// Regenerate the config cache
+	if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+		require PUN_ROOT.'include/cache.php';
+
+	generate_config_cache();
+	clear_feed_cache();
+
+	redirect('admin_options.php', $lang_admin_options['Options updated redirect']);
+}
+
+$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_admin_common['Admin'], $lang_admin_common['Options']);
+define('PUN_ACTIVE_PAGE', 'admin');
+require PUN_ROOT.'header.php';
+
+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">
+				<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" />
+					<fieldset>
+						<legend><?php echo $lang_admin_options['Essentials subhead'] ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Board title label'] ?></th>
+									<td>
+										<input type="text" name="form[board_title]" size="50" maxlength="255" value="<?php echo pun_htmlspecialchars($pun_config['o_board_title']) ?>" />
+										<span><?php echo $lang_admin_options['Board title help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Board desc label'] ?></th>
+									<td>
+										<input type="text" name="form[board_desc]" size="50" maxlength="255" value="<?php echo pun_htmlspecialchars($pun_config['o_board_desc']) ?>" />
+										<span><?php echo $lang_admin_options['Board desc help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Base URL label'] ?></th>
+									<td>
+										<input type="text" name="form[base_url]" size="50" maxlength="100" value="<?php echo pun_htmlspecialchars($pun_config['o_base_url']) ?>" />
+										<span><?php echo $lang_admin_options['Base URL help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Timezone label'] ?></th>
+									<td>
+										<select name="form[default_timezone]">
+											<option value="-12"<?php if ($pun_config['o_default_timezone'] == -12) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC-12:00'] ?></option>
+											<option value="-11"<?php if ($pun_config['o_default_timezone'] == -11) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC-11:00'] ?></option>
+											<option value="-10"<?php if ($pun_config['o_default_timezone'] == -10) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC-10:00'] ?></option>
+											<option value="-9.5"<?php if ($pun_config['o_default_timezone'] == -9.5) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC-09:30'] ?></option>
+											<option value="-9"<?php if ($pun_config['o_default_timezone'] == -9) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC-09:00'] ?></option>
+											<option value="-8.5"<?php if ($pun_config['o_default_timezone'] == -8.5) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC-08:30'] ?></option>
+											<option value="-8"<?php if ($pun_config['o_default_timezone'] == -8) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC-08:00'] ?></option>
+											<option value="-7"<?php if ($pun_config['o_default_timezone'] == -7) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC-07:00'] ?></option>
+											<option value="-6"<?php if ($pun_config['o_default_timezone'] == -6) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC-06:00'] ?></option>
+											<option value="-5"<?php if ($pun_config['o_default_timezone'] == -5) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC-05:00'] ?></option>
+											<option value="-4"<?php if ($pun_config['o_default_timezone'] == -4) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC-04:00'] ?></option>
+											<option value="-3.5"<?php if ($pun_config['o_default_timezone'] == -3.5) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC-03:30'] ?></option>
+											<option value="-3"<?php if ($pun_config['o_default_timezone'] == -3) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC-03:00'] ?></option>
+											<option value="-2"<?php if ($pun_config['o_default_timezone'] == -2) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC-02:00'] ?></option>
+											<option value="-1"<?php if ($pun_config['o_default_timezone'] == -1) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC-01:00'] ?></option>
+											<option value="0"<?php if ($pun_config['o_default_timezone'] == 0) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC'] ?></option>
+											<option value="1"<?php if ($pun_config['o_default_timezone'] == 1) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC+01:00'] ?></option>
+											<option value="2"<?php if ($pun_config['o_default_timezone'] == 2) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC+02:00'] ?></option>
+											<option value="3"<?php if ($pun_config['o_default_timezone'] == 3) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC+03:00'] ?></option>
+											<option value="3.5"<?php if ($pun_config['o_default_timezone'] == 3.5) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC+03:30'] ?></option>
+											<option value="4"<?php if ($pun_config['o_default_timezone'] == 4) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC+04:00'] ?></option>
+											<option value="4.5"<?php if ($pun_config['o_default_timezone'] == 4.5) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC+04:30'] ?></option>
+											<option value="5"<?php if ($pun_config['o_default_timezone'] == 5) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC+05:00'] ?></option>
+											<option value="5.5"<?php if ($pun_config['o_default_timezone'] == 5.5) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC+05:30'] ?></option>
+											<option value="5.75"<?php if ($pun_config['o_default_timezone'] == 5.75) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC+05:45'] ?></option>
+											<option value="6"<?php if ($pun_config['o_default_timezone'] == 6) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC+06:00'] ?></option>
+											<option value="6.5"<?php if ($pun_config['o_default_timezone'] == 6.5) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC+06:30'] ?></option>
+											<option value="7"<?php if ($pun_config['o_default_timezone'] == 7) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC+07:00'] ?></option>
+											<option value="8"<?php if ($pun_config['o_default_timezone'] == 8) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC+08:00'] ?></option>
+											<option value="8.75"<?php if ($pun_config['o_default_timezone'] == 8.75) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC+08:45'] ?></option>
+											<option value="9"<?php if ($pun_config['o_default_timezone'] == 9) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC+09:00'] ?></option>
+											<option value="9.5"<?php if ($pun_config['o_default_timezone'] == 9.5) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC+09:30'] ?></option>
+											<option value="10"<?php if ($pun_config['o_default_timezone'] == 10) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC+10:00'] ?></option>
+											<option value="10.5"<?php if ($pun_config['o_default_timezone'] == 10.5) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC+10:30'] ?></option>
+											<option value="11"<?php if ($pun_config['o_default_timezone'] == 11) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC+11:00'] ?></option>
+											<option value="11.5"<?php if ($pun_config['o_default_timezone'] == 11.5) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC+11:30'] ?></option>
+											<option value="12"<?php if ($pun_config['o_default_timezone'] == 12) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC+12:00'] ?></option>
+											<option value="12.75"<?php if ($pun_config['o_default_timezone'] == 12.75) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC+12:45'] ?></option>
+											<option value="13"<?php if ($pun_config['o_default_timezone'] == 13) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC+13:00'] ?></option>
+											<option value="14"<?php if ($pun_config['o_default_timezone'] == 14) echo ' selected="selected"' ?>><?php echo $lang_admin_options['UTC+14:00'] ?></option>
+										</select>
+										<span><?php echo $lang_admin_options['Timezone help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['DST label'] ?></th>
+									<td>
+										<input type="radio" name="form[default_dst]" id="form_default_dst_1" value="1"<?php if ($pun_config['o_default_dst'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_default_dst_1"><strong><?php echo $lang_admin_common['Yes'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[default_dst]" id="form_default_dst_0" value="0"<?php if ($pun_config['o_default_dst'] == '0') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_default_dst_0"><strong><?php echo $lang_admin_common['No'] ?></strong></label>
+										<span><?php echo $lang_admin_options['DST help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Language label'] ?></th>
+									<td>
+										<select name="form[default_lang]">
+<?php
+
+		$languages = forum_list_langs();
+
+		foreach ($languages as $temp)
+		{
+			if ($pun_config['o_default_lang'] == $temp)
+				echo "\t\t\t\t\t\t\t\t\t\t\t".'<option value="'.$temp.'" selected="selected">'.$temp.'</option>'."\n";
+			else
+				echo "\t\t\t\t\t\t\t\t\t\t\t".'<option value="'.$temp.'">'.$temp.'</option>'."\n";
+		}
+
+?>
+										</select>
+										<span><?php echo $lang_admin_options['Language help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Default style label'] ?></th>
+									<td>
+										<select name="form[default_style]">
+<?php
+
+		$styles = forum_list_styles();
+
+		foreach ($styles as $temp)
+		{
+			if ($pun_config['o_default_style'] == $temp)
+				echo "\t\t\t\t\t\t\t\t\t\t\t".'<option value="'.$temp.'" selected="selected">'.str_replace('_', ' ', $temp).'</option>'."\n";
+			else
+				echo "\t\t\t\t\t\t\t\t\t\t\t".'<option value="'.$temp.'">'.str_replace('_', ' ', $temp).'</option>'."\n";
+		}
+
+?>
+										</select>
+										<span><?php echo $lang_admin_options['Default style help'] ?></span>
+									</td>
+								</tr>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+<?php
+
+	$diff = ($pun_user['timezone'] + $pun_user['dst']) * 3600;
+	$timestamp = time() + $diff;
+
+?>
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_options['Timeouts subhead'] ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Time format label'] ?></th>
+									<td>
+										<input type="text" name="form[time_format]" size="25" maxlength="25" value="<?php echo pun_htmlspecialchars($pun_config['o_time_format']) ?>" />
+										<span><?php printf($lang_admin_options['Time format help'], gmdate($pun_config['o_time_format'], $timestamp), '<a href="http://www.php.net/manual/en/function.date.php">'.$lang_admin_options['PHP manual'].'</a>') ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Date format label'] ?></th>
+									<td>
+										<input type="text" name="form[date_format]" size="25" maxlength="25" value="<?php echo pun_htmlspecialchars($pun_config['o_date_format']) ?>" />
+										<span><?php printf($lang_admin_options['Date format help'], gmdate($pun_config['o_date_format'], $timestamp), '<a href="http://www.php.net/manual/en/function.date.php">'.$lang_admin_options['PHP manual'].'</a>') ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Visit timeout label'] ?></th>
+									<td>
+										<input type="text" name="form[timeout_visit]" size="5" maxlength="5" value="<?php echo $pun_config['o_timeout_visit'] ?>" />
+										<span><?php echo $lang_admin_options['Visit timeout help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Online timeout label'] ?></th>
+									<td>
+										<input type="text" name="form[timeout_online]" size="5" maxlength="5" value="<?php echo $pun_config['o_timeout_online'] ?>" />
+										<span><?php echo $lang_admin_options['Online timeout help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Redirect time label'] ?></th>
+									<td>
+										<input type="text" name="form[redirect_delay]" size="3" maxlength="3" value="<?php echo $pun_config['o_redirect_delay'] ?>" />
+										<span><?php echo $lang_admin_options['Redirect time help'] ?></span>
+									</td>
+								</tr>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_options['Display subhead'] ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Version number label'] ?></th>
+									<td>
+										<input type="radio" name="form[show_version]" id="form_show_version_1" value="1"<?php if ($pun_config['o_show_version'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_show_version_1"><strong><?php echo $lang_admin_common['Yes'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[show_version]" id="form_show_version_0" value="0"<?php if ($pun_config['o_show_version'] == '0') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_show_version_0"><strong><?php echo $lang_admin_common['No'] ?></strong></label>
+										<span><?php echo $lang_admin_options['Version number help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Info in posts label'] ?></th>
+									<td>
+										<input type="radio" name="form[show_user_info]" id="form_show_user_info_1" value="1"<?php if ($pun_config['o_show_user_info'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_show_user_info_1"><strong><?php echo $lang_admin_common['Yes'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[show_user_info]" id="form_show_user_info_0" value="0"<?php if ($pun_config['o_show_user_info'] == '0') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_show_user_info_0"><strong><?php echo $lang_admin_common['No'] ?></strong></label>
+										<span><?php echo $lang_admin_options['Info in posts help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Post count label'] ?></th>
+									<td>
+										<input type="radio" name="form[show_post_count]" id="form_show_post_count_1" value="1"<?php if ($pun_config['o_show_post_count'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_show_post_count_1"><strong><?php echo $lang_admin_common['Yes'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[show_post_count]" id="form_show_post_count_0" value="0"<?php if ($pun_config['o_show_post_count'] == '0') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_show_post_count_0"><strong><?php echo $lang_admin_common['No'] ?></strong></label>
+										<span><?php echo $lang_admin_options['Post count help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Smilies label'] ?></th>
+									<td>
+										<input type="radio" name="form[smilies]" id="form_smilies_1" value="1"<?php if ($pun_config['o_smilies'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_smilies_1"><strong><?php echo $lang_admin_common['Yes'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[smilies]" id="form_smilies_0" value="0"<?php if ($pun_config['o_smilies'] == '0') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_smilies_0"><strong><?php echo $lang_admin_common['No'] ?></strong></label>
+										<span><?php echo $lang_admin_options['Smilies help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Smilies sigs label'] ?></th>
+									<td>
+										<input type="radio" name="form[smilies_sig]" id="form_smilies_sig_1" value="1"<?php if ($pun_config['o_smilies_sig'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_smilies_sig_1"><strong><?php echo $lang_admin_common['Yes'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[smilies_sig]" id="form_smilies_sig_0" value="0"<?php if ($pun_config['o_smilies_sig'] == '0') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_smilies_sig_0"><strong><?php echo $lang_admin_common['No'] ?></strong></label>
+										<span><?php echo $lang_admin_options['Smilies sigs help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Clickable links label'] ?></th>
+									<td>
+										<input type="radio" name="form[make_links]" id="form_make_links_1" value="1"<?php if ($pun_config['o_make_links'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_make_links_1"><strong><?php echo $lang_admin_common['Yes'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[make_links]" id="form_make_links_0" value="0"<?php if ($pun_config['o_make_links'] == '0') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_make_links_0"><strong><?php echo $lang_admin_common['No'] ?></strong></label>
+										<span><?php echo $lang_admin_options['Clickable links help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Topic review label'] ?></th>
+									<td>
+										<input type="text" name="form[topic_review]" size="3" maxlength="3" value="<?php echo $pun_config['o_topic_review'] ?>" />
+										<span><?php echo $lang_admin_options['Topic review help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Topics per page label'] ?></th>
+									<td>
+										<input type="text" name="form[disp_topics_default]" size="3" maxlength="3" value="<?php echo $pun_config['o_disp_topics_default'] ?>" />
+										<span><?php echo $lang_admin_options['Topics per page help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Posts per page label'] ?></th>
+									<td>
+										<input type="text" name="form[disp_posts_default]" size="3" maxlength="3" value="<?php echo $pun_config['o_disp_posts_default'] ?>" />
+										<span><?php echo $lang_admin_options['Posts per page help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Indent label'] ?></th>
+									<td>
+										<input type="text" name="form[indent_num_spaces]" size="3" maxlength="3" value="<?php echo $pun_config['o_indent_num_spaces'] ?>" />
+										<span><?php echo $lang_admin_options['Indent help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Quote depth label'] ?></th>
+									<td>
+										<input type="text" name="form[quote_depth]" size="3" maxlength="3" value="<?php echo $pun_config['o_quote_depth'] ?>" />
+										<span><?php echo $lang_admin_options['Quote depth help'] ?></span>
+									</td>
+								</tr>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_options['Features subhead'] ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Quick post label'] ?></th>
+									<td>
+										<input type="radio" name="form[quickpost]" id="form_quickpost_1" value="1"<?php if ($pun_config['o_quickpost'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_quickpost_1"><strong><?php echo $lang_admin_common['Yes'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[quickpost]" id="form_quickpost_0" value="0"<?php if ($pun_config['o_quickpost'] == '0') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_quickpost_0"><strong><?php echo $lang_admin_common['No'] ?></strong></label>
+										<span><?php echo $lang_admin_options['Quick post help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Users online label'] ?></th>
+									<td>
+										<input type="radio" name="form[users_online]" id="form_users_online_1" value="1"<?php if ($pun_config['o_users_online'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_users_online_1"><strong><?php echo $lang_admin_common['Yes'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[users_online]" id="form_users_online_0" value="0"<?php if ($pun_config['o_users_online'] == '0') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_users_online_0"><strong><?php echo $lang_admin_common['No'] ?></strong></label>
+										<span><?php echo $lang_admin_options['Users online help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><a name="censoring"></a><?php echo $lang_admin_options['Censor words label'] ?></th>
+									<td>
+										<input type="radio" name="form[censoring]" id="form_censoring_1" value="1"<?php if ($pun_config['o_censoring'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_censoring_1"><strong><?php echo $lang_admin_common['Yes'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[censoring]" id="form_censoring_0" value="0"<?php if ($pun_config['o_censoring'] == '0') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_censoring_0"><strong><?php echo $lang_admin_common['No'] ?></strong></label>
+										<span><?php printf($lang_admin_options['Censor words help'], '<a href="admin_censoring.php">'.$lang_admin_common['Censoring'].'</a>') ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><a name="signatures"></a><?php echo $lang_admin_options['Signatures label'] ?></th>
+									<td>
+										<input type="radio" name="form[signatures]" id="form_signatures_1" value="1"<?php if ($pun_config['o_signatures'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_signatures_1"><strong><?php echo $lang_admin_common['Yes'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[signatures]" id="form_signatures_0" value="0"<?php if ($pun_config['o_signatures'] == '0') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_signatures_0"><strong><?php echo $lang_admin_common['No'] ?></strong></label>
+										<span><?php echo $lang_admin_options['Signatures help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><a name="ranks"></a><?php echo $lang_admin_options['User ranks label'] ?></th>
+									<td>
+										<input type="radio" name="form[ranks]" id="form_ranks_1" value="1"<?php if ($pun_config['o_ranks'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_ranks_1"><strong><?php echo $lang_admin_common['Yes'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[ranks]" id="form_ranks_0" value="0"<?php if ($pun_config['o_ranks'] == '0') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_ranks_0"><strong><?php echo $lang_admin_common['No'] ?></strong></label>
+										<span><?php printf($lang_admin_options['User ranks help'], '<a href="admin_ranks.php">'.$lang_admin_common['Ranks'].'</a>') ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['User has posted label'] ?></th>
+									<td>
+										<input type="radio" name="form[show_dot]" id="form_show_dot_1" value="1"<?php if ($pun_config['o_show_dot'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_show_dot_1"><strong><?php echo $lang_admin_common['Yes'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[show_dot]" id="form_show_dot_0" value="0"<?php if ($pun_config['o_show_dot'] == '0') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_show_dot_0"><strong><?php echo $lang_admin_common['No'] ?></strong></label>
+										<span><?php echo $lang_admin_options['User has posted help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Topic views label'] ?></th>
+									<td>
+										<input type="radio" name="form[topic_views]" id="form_topic_views_1" value="1"<?php if ($pun_config['o_topic_views'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_topic_views_1"><strong><?php echo $lang_admin_common['Yes'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[topic_views]" id="form_topic_views_0" value="0"<?php if ($pun_config['o_topic_views'] == '0') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_topic_views_0"><strong><?php echo $lang_admin_common['No'] ?></strong></label>
+										<span><?php echo $lang_admin_options['Topic views help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Quick jump label'] ?></th>
+									<td>
+										<input type="radio" name="form[quickjump]" id="form_quickjump_1" value="1"<?php if ($pun_config['o_quickjump'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_quickjump_1"><strong><?php echo $lang_admin_common['Yes'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[quickjump]" id="form_quickjump_0" value="0"<?php if ($pun_config['o_quickjump'] == '0') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_quickjump_0"><strong><?php echo $lang_admin_common['No'] ?></strong></label>
+										<span><?php echo $lang_admin_options['Quick jump help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['GZip label'] ?></th>
+									<td>
+										<input type="radio" name="form[gzip]" id="form_gzip_1" value="1"<?php if ($pun_config['o_gzip'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_gzip_1"><strong><?php echo $lang_admin_common['Yes'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[gzip]" id="form_gzip_0" value="0"<?php if ($pun_config['o_gzip'] == '0') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_gzip_0"><strong><?php echo $lang_admin_common['No'] ?></strong></label>
+										<span><?php echo $lang_admin_options['GZip help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Search all label'] ?></th>
+									<td>
+										<input type="radio" name="form[search_all_forums]" id="form_search_all_forums_1" value="1"<?php if ($pun_config['o_search_all_forums'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_search_all_forums_1"><strong><?php echo $lang_admin_common['Yes'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[search_all_forums]" id="form_search_all_forums_0" value="0"<?php if ($pun_config['o_search_all_forums'] == '0') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_search_all_forums_0"><strong><?php echo $lang_admin_common['No'] ?></strong></label>
+										<span><?php echo $lang_admin_options['Search all help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Menu items label'] ?></th>
+									<td>
+										<textarea name="form[additional_navlinks]" rows="3" cols="55"><?php echo pun_htmlspecialchars($pun_config['o_additional_navlinks']) ?></textarea>
+										<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>
+										<input type="radio" name="form[feed_type]" id="form_feed_type_0" value="0"<?php if ($pun_config['o_feed_type'] == '0') echo ' checked="checked"' ?> />&#160;<strong><?php echo $lang_admin_options['None'] ?></strong>&#160;&#160;&#160;<input type="radio" name="form[feed_type]" id="form_feed_type_1" value="1"<?php if ($pun_config['o_feed_type'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_feed_type_0"><strong><?php echo $lang_admin_options['RSS'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[feed_type]" id="form_feed_type_2" value="2"<?php if ($pun_config['o_feed_type'] == '2') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_feed_type_1"><strong><?php echo $lang_admin_options['Atom'] ?></strong></label>
+										<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>
+				</div>
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_options['Reports subhead'] ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Reporting method label'] ?></th>
+									<td>
+										<input type="radio" name="form[report_method]" id="form_report_method_0" value="0"<?php if ($pun_config['o_report_method'] == '0') echo ' checked="checked"' ?> />&#160;<strong><?php echo $lang_admin_options['Internal'] ?></strong>&#160;&#160;&#160;<input type="radio" name="form[report_method]" id="form_report_method_1" value="1"<?php if ($pun_config['o_report_method'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_report_method_0"><strong><?php echo $lang_admin_options['By e-mail'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[report_method]" id="form_report_method_2" value="2"<?php if ($pun_config['o_report_method'] == '2') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_report_method_1"><strong><?php echo $lang_admin_options['Both'] ?></strong></label>
+										<span><?php echo $lang_admin_options['Reporting method help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Mailing list label'] ?></th>
+									<td>
+										<textarea name="form[mailing_list]" rows="5" cols="55"><?php echo pun_htmlspecialchars($pun_config['o_mailing_list']) ?></textarea>
+										<span><?php echo $lang_admin_options['Mailing list help'] ?></span>
+									</td>
+								</tr>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_options['Avatars subhead'] ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Use avatars label'] ?></th>
+									<td>
+										<input type="radio" name="form[avatars]" id="form_avatars_1" value="1"<?php if ($pun_config['o_avatars'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_avatars_1"><strong><?php echo $lang_admin_common['Yes'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[avatars]" id="form_avatars_0" value="0"<?php if ($pun_config['o_avatars'] == '0') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_avatars_0"><strong><?php echo $lang_admin_common['No'] ?></strong></label>
+										<span><?php echo $lang_admin_options['Use avatars help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Upload directory label'] ?></th>
+									<td>
+										<input type="text" name="form[avatars_dir]" size="35" maxlength="50" value="<?php echo pun_htmlspecialchars($pun_config['o_avatars_dir']) ?>" />
+										<span><?php echo $lang_admin_options['Upload directory help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Max width label'] ?></th>
+									<td>
+										<input type="text" name="form[avatars_width]" size="5" maxlength="5" value="<?php echo $pun_config['o_avatars_width'] ?>" />
+										<span><?php echo $lang_admin_options['Max width help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Max height label'] ?></th>
+									<td>
+										<input type="text" name="form[avatars_height]" size="5" maxlength="5" value="<?php echo $pun_config['o_avatars_height'] ?>" />
+										<span><?php echo $lang_admin_options['Max height help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Max size label'] ?></th>
+									<td>
+										<input type="text" name="form[avatars_size]" size="6" maxlength="6" value="<?php echo $pun_config['o_avatars_size'] ?>" />
+										<span><?php echo $lang_admin_options['Max size help'] ?></span>
+									</td>
+								</tr>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_options['E-mail subhead'] ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Admin e-mail label'] ?></th>
+									<td>
+										<input type="text" name="form[admin_email]" size="50" maxlength="80" value="<?php echo $pun_config['o_admin_email'] ?>" />
+										<span><?php echo $lang_admin_options['Admin e-mail help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Webmaster e-mail label'] ?></th>
+									<td>
+										<input type="text" name="form[webmaster_email]" size="50" maxlength="80" value="<?php echo $pun_config['o_webmaster_email'] ?>" />
+										<span><?php echo $lang_admin_options['Webmaster e-mail help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Forum subscriptions label'] ?></th>
+									<td>
+										<input type="radio" name="form[forum_subscriptions]" id="form_forum_subscriptions_1" value="1"<?php if ($pun_config['o_forum_subscriptions'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_forum_subscriptions_1"><strong><?php echo $lang_admin_common['Yes'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[forum_subscriptions]" id="form_forum_subscriptions_0" value="0"<?php if ($pun_config['o_forum_subscriptions'] == '0') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_forum_subscriptions_0"><strong><?php echo $lang_admin_common['No'] ?></strong></label>
+										<span><?php echo $lang_admin_options['Forum subscriptions help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Topic subscriptions label'] ?></th>
+									<td>
+										<input type="radio" name="form[topic_subscriptions]" id="form_topic_subscriptions_1" value="1"<?php if ($pun_config['o_topic_subscriptions'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_topic_subscriptions_1"><strong><?php echo $lang_admin_common['Yes'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[topic_subscriptions]" id="form_topic_subscriptions_0" value="0"<?php if ($pun_config['o_topic_subscriptions'] == '0') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_topic_subscriptions_0"><strong><?php echo $lang_admin_common['No'] ?></strong></label>
+										<span><?php echo $lang_admin_options['Topic subscriptions help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['SMTP address label'] ?></th>
+									<td>
+										<input type="text" name="form[smtp_host]" size="30" maxlength="100" value="<?php echo pun_htmlspecialchars($pun_config['o_smtp_host']) ?>" />
+										<span><?php echo $lang_admin_options['SMTP address help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['SMTP username label'] ?></th>
+									<td>
+										<input type="text" name="form[smtp_user]" size="25" maxlength="50" value="<?php echo pun_htmlspecialchars($pun_config['o_smtp_user']) ?>" />
+										<span><?php echo $lang_admin_options['SMTP username help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['SMTP password label'] ?></th>
+									<td>
+										<span><input type="checkbox" name="form[smtp_change_pass]" id="form_smtp_change_pass" value="1" />&#160;&#160;<label class="conl" for="form_smtp_change_pass"><?php echo $lang_admin_options['SMTP change password help'] ?></label></span>
+<?php $smtp_pass = !empty($pun_config['o_smtp_pass']) ? random_key(pun_strlen($pun_config['o_smtp_pass']), true) : ''; ?>
+										<input type="password" name="form[smtp_pass1]" size="25" maxlength="50" value="<?php echo $smtp_pass ?>" />
+										<input type="password" name="form[smtp_pass2]" size="25" maxlength="50" value="<?php echo $smtp_pass ?>" />
+										<span><?php echo $lang_admin_options['SMTP password help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['SMTP SSL label'] ?></th>
+									<td>
+										<input type="radio" name="form[smtp_ssl]" id="form_smtp_ssl_1" value="1"<?php if ($pun_config['o_smtp_ssl'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_smtp_ssl_1"><strong><?php echo $lang_admin_common['Yes'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[smtp_ssl]" id="form_smtp_ssl_0" value="0"<?php if ($pun_config['o_smtp_ssl'] == '0') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_smtp_ssl_0"><strong><?php echo $lang_admin_common['No'] ?></strong></label>
+										<span><?php echo $lang_admin_options['SMTP SSL help'] ?></span>
+									</td>
+								</tr>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_options['Registration subhead'] ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Allow new label'] ?></th>
+									<td>
+										<input type="radio" name="form[regs_allow]" id="form_regs_allow_1" value="1"<?php if ($pun_config['o_regs_allow'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_regs_allow_1"><strong><?php echo $lang_admin_common['Yes'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[regs_allow]" id="form_regs_allow_0" value="0"<?php if ($pun_config['o_regs_allow'] == '0') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_regs_allow_0"><strong><?php echo $lang_admin_common['No'] ?></strong></label>
+										<span><?php echo $lang_admin_options['Allow new help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Verify label'] ?></th>
+									<td>
+										<input type="radio" name="form[regs_verify]" id="form_regs_verify_1" value="1"<?php if ($pun_config['o_regs_verify'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_regs_verify_1"><strong><?php echo $lang_admin_common['Yes'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[regs_verify]" id="form_regs_verify_0" value="0"<?php if ($pun_config['o_regs_verify'] == '0') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_regs_verify_0"><strong><?php echo $lang_admin_common['No'] ?></strong></label>
+										<span><?php echo $lang_admin_options['Verify help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Report new label'] ?></th>
+									<td>
+										<input type="radio" name="form[regs_report]" id="form_regs_report_1" value="1"<?php if ($pun_config['o_regs_report'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_regs_report_1"><strong><?php echo $lang_admin_common['Yes'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[regs_report]" id="form_regs_report_0" value="0"<?php if ($pun_config['o_regs_report'] == '0') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_regs_report_0"><strong><?php echo $lang_admin_common['No'] ?></strong></label>
+										<span><?php echo $lang_admin_options['Report new help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Use rules label'] ?></th>
+									<td>
+										<input type="radio" name="form[rules]" id="form_rules_1" value="1"<?php if ($pun_config['o_rules'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_rules_1"><strong><?php echo $lang_admin_common['Yes'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[rules]" id="form_rules_0" value="0"<?php if ($pun_config['o_rules'] == '0') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_rules_0"><strong><?php echo $lang_admin_common['No'] ?></strong></label>
+										<span><?php echo $lang_admin_options['Use rules help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Rules label'] ?></th>
+									<td>
+										<textarea name="form[rules_message]" rows="10" cols="55"><?php echo pun_htmlspecialchars($pun_config['o_rules_message']) ?></textarea>
+										<span><?php echo $lang_admin_options['Rules help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['E-mail default label'] ?></th>
+									<td>
+										<span><?php echo $lang_admin_options['E-mail default help'] ?></span>
+										<input type="radio" name="form[default_email_setting]" id="form_default_email_setting_0" value="0"<?php if ($pun_config['o_default_email_setting'] == '0') echo ' checked="checked"' ?> />&#160;<?php echo $lang_admin_options['Display e-mail label'] ?><br />
+										<input type="radio" name="form[default_email_setting]" id="form_default_email_setting_1" value="1"<?php if ($pun_config['o_default_email_setting'] == '1') echo ' checked="checked"' ?> />&#160;<?php echo $lang_admin_options['Hide allow form label'] ?><br />
+										<input type="radio" name="form[default_email_setting]" id="form_default_email_setting_2" value="2"<?php if ($pun_config['o_default_email_setting'] == '2') echo ' checked="checked"' ?> />&#160;<?php echo $lang_admin_options['Hide both label'] ?><br />
+									</td>
+								</tr>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_options['Announcement subhead'] ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Display announcement label'] ?></th>
+									<td>
+										<input type="radio" name="form[announcement]" id="form_announcement_1" value="1"<?php if ($pun_config['o_announcement'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_announcement_1"><strong><?php echo $lang_admin_common['Yes'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[announcement]" id="form_announcement_0" value="0"<?php if ($pun_config['o_announcement'] == '0') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_announcement_0"><strong><?php echo $lang_admin_common['No'] ?></strong></label>
+										<span><?php echo $lang_admin_options['Display announcement help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Announcement message label'] ?></th>
+									<td>
+										<textarea name="form[announcement_message]" rows="5" cols="55"><?php echo pun_htmlspecialchars($pun_config['o_announcement_message']) ?></textarea>
+										<span><?php echo $lang_admin_options['Announcement message help'] ?></span>
+									</td>
+								</tr>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_options['Maintenance subhead'] ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><a name="maintenance"></a><?php echo $lang_admin_options['Maintenance mode label'] ?></th>
+									<td>
+										<input type="radio" name="form[maintenance]" id="form_maintenance_1" value="1"<?php if ($pun_config['o_maintenance'] == '1') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_maintenance_1"><strong><?php echo $lang_admin_common['Yes'] ?></strong></label>&#160;&#160;&#160;<input type="radio" name="form[maintenance]" id="form_maintenance_0" value="0"<?php if ($pun_config['o_maintenance'] == '0') echo ' checked="checked"' ?> />&#160;<label class="conl" for="form_maintenance_0"><strong><?php echo $lang_admin_common['No'] ?></strong></label>
+										<span><?php echo $lang_admin_options['Maintenance mode help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_options['Maintenance message label'] ?></th>
+									<td>
+										<textarea name="form[maintenance_message]" rows="5" cols="55"><?php echo pun_htmlspecialchars($pun_config['o_maintenance_message']) ?></textarea>
+										<span><?php echo $lang_admin_options['Maintenance message help'] ?></span>
+									</td>
+								</tr>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+				<p class="submitend"><input type="submit" name="save" value="<?php echo $lang_admin_common['Save changes'] ?>" /></p>
+			</form>
+		</div>
+	</div>
+	<div class="clearer"></div>
+</div>
+<?php
+
+require PUN_ROOT.'footer.php';
diff --git a/admin_permissions.php b/admin_permissions.php
new file mode 100644
index 0000000..a239979
--- /dev/null
+++ b/admin_permissions.php
@@ -0,0 +1,181 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// Tell header.php to use the admin template
+define('PUN_ADMIN_CONSOLE', 1);
+
+define('PUN_ROOT', dirname(__FILE__).'/');
+require PUN_ROOT.'include/common.php';
+require PUN_ROOT.'include/common_admin.php';
+
+
+if ($pun_user['g_id'] != PUN_ADMIN)
+	message($lang_common['No permission'], false, '403 Forbidden');
+
+// Load the admin_permissions.php language file
+require PUN_ROOT.'lang/'.$admin_language.'/admin_permissions.php';
+
+if (isset($_POST['form_sent']))
+{
+	confirm_referrer('admin_permissions.php');
+
+	$form = array_map('intval', $_POST['form']);
+
+	foreach ($form as $key => $input)
+	{
+		// Make sure the input is never a negative value
+		if($input < 0)
+			$input = 0;
+
+		// Only update values that have changed
+		if (array_key_exists('p_'.$key, $pun_config) && $pun_config['p_'.$key] != $input)
+			$db->query('UPDATE '.$db->prefix.'config SET conf_value='.$input.' WHERE conf_name=\'p_'.$db->escape($key).'\'') or error('Unable to update board config', __FILE__, __LINE__, $db->error());
+	}
+
+	// Regenerate the config cache
+	if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+		require PUN_ROOT.'include/cache.php';
+
+	generate_config_cache();
+
+	redirect('admin_permissions.php', $lang_admin_permissions['Perms updated redirect']);
+}
+
+$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_admin_common['Admin'], $lang_admin_common['Permissions']);
+define('PUN_ACTIVE_PAGE', 'admin');
+require PUN_ROOT.'header.php';
+
+generate_admin_menu('permissions');
+
+?>
+	<div class="blockform">
+		<h2><span><?php echo $lang_admin_permissions['Permissions head'] ?></span></h2>
+		<div class="box">
+			<form method="post" action="admin_permissions.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" />
+					<fieldset>
+						<legend><?php echo $lang_admin_permissions['Posting subhead'] ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_permissions['BBCode label'] ?></th>
+									<td>
+										<input type="radio" name="form[message_bbcode]" value="1"<?php if ($pun_config['p_message_bbcode'] == '1') echo ' checked="checked"' ?> />&#160;<strong><?php echo $lang_admin_common['Yes'] ?></strong>&#160;&#160;&#160;<input type="radio" name="form[message_bbcode]" value="0"<?php if ($pun_config['p_message_bbcode'] == '0') echo ' checked="checked"' ?> />&#160;<strong><?php echo $lang_admin_common['No'] ?></strong>
+										<span><?php echo $lang_admin_permissions['BBCode help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_permissions['Image tag label'] ?></th>
+									<td>
+										<input type="radio" name="form[message_img_tag]" value="1"<?php if ($pun_config['p_message_img_tag'] == '1') echo ' checked="checked"' ?> />&#160;<strong><?php echo $lang_admin_common['Yes'] ?></strong>&#160;&#160;&#160;<input type="radio" name="form[message_img_tag]" value="0"<?php if ($pun_config['p_message_img_tag'] == '0') echo ' checked="checked"' ?> />&#160;<strong><?php echo $lang_admin_common['No'] ?></strong>
+										<span><?php echo $lang_admin_permissions['Image tag help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_permissions['All caps message label'] ?></th>
+									<td>
+										<input type="radio" name="form[message_all_caps]" value="1"<?php if ($pun_config['p_message_all_caps'] == '1') echo ' checked="checked"' ?> />&#160;<strong><?php echo $lang_admin_common['Yes'] ?></strong>&#160;&#160;&#160;<input type="radio" name="form[message_all_caps]" value="0"<?php if ($pun_config['p_message_all_caps'] == '0') echo ' checked="checked"' ?> />&#160;<strong><?php echo $lang_admin_common['No'] ?></strong>
+										<span><?php echo $lang_admin_permissions['All caps message help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_permissions['All caps subject label'] ?></th>
+									<td>
+										<input type="radio" name="form[subject_all_caps]" value="1"<?php if ($pun_config['p_subject_all_caps'] == '1') echo ' checked="checked"' ?> />&#160;<strong><?php echo $lang_admin_common['Yes'] ?></strong>&#160;&#160;&#160;<input type="radio" name="form[subject_all_caps]" value="0"<?php if ($pun_config['p_subject_all_caps'] == '0') echo ' checked="checked"' ?> />&#160;<strong><?php echo $lang_admin_common['No'] ?></strong>
+										<span><?php echo $lang_admin_permissions['All caps subject help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_permissions['Require e-mail label'] ?></th>
+									<td>
+										<input type="radio" name="form[force_guest_email]" value="1"<?php if ($pun_config['p_force_guest_email'] == '1') echo ' checked="checked"' ?> />&#160;<strong><?php echo $lang_admin_common['Yes'] ?></strong>&#160;&#160;&#160;<input type="radio" name="form[force_guest_email]" value="0"<?php if ($pun_config['p_force_guest_email'] == '0') echo ' checked="checked"' ?> />&#160;<strong><?php echo $lang_admin_common['No'] ?></strong>
+										<span><?php echo $lang_admin_permissions['Require e-mail help'] ?></span>
+									</td>
+								</tr>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_permissions['Signatures subhead'] ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_permissions['BBCode sigs label'] ?></th>
+									<td>
+										<input type="radio" name="form[sig_bbcode]" value="1"<?php if ($pun_config['p_sig_bbcode'] == '1') echo ' checked="checked"' ?> />&#160;<strong><?php echo $lang_admin_common['Yes'] ?></strong>&#160;&#160;&#160;<input type="radio" name="form[sig_bbcode]" value="0"<?php if ($pun_config['p_sig_bbcode'] == '0') echo ' checked="checked"' ?> />&#160;<strong><?php echo $lang_admin_common['No'] ?></strong>
+										<span><?php echo $lang_admin_permissions['BBCode sigs help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_permissions['Image tag sigs label'] ?></th>
+									<td>
+										<input type="radio" name="form[sig_img_tag]" value="1"<?php if ($pun_config['p_sig_img_tag'] == '1') echo ' checked="checked"' ?> />&#160;<strong><?php echo $lang_admin_common['Yes'] ?></strong>&#160;&#160;&#160;<input type="radio" name="form[sig_img_tag]" value="0"<?php if ($pun_config['p_sig_img_tag'] == '0') echo ' checked="checked"' ?> />&#160;<strong><?php echo $lang_admin_common['No'] ?></strong>
+										<span><?php echo $lang_admin_permissions['Image tag sigs help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_permissions['All caps sigs label'] ?></th>
+									<td>
+										<input type="radio" name="form[sig_all_caps]" value="1"<?php if ($pun_config['p_sig_all_caps'] == '1') echo ' checked="checked"' ?> />&#160;<strong><?php echo $lang_admin_common['Yes'] ?></strong>&#160;&#160;&#160;<input type="radio" name="form[sig_all_caps]" value="0"<?php if ($pun_config['p_sig_all_caps'] == '0') echo ' checked="checked"' ?> />&#160;<strong><?php echo $lang_admin_common['No'] ?></strong>
+										<span><?php echo $lang_admin_permissions['All caps sigs help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_permissions['Max sig length label'] ?></th>
+									<td>
+										<input type="text" name="form[sig_length]" size="5" maxlength="5" value="<?php echo $pun_config['p_sig_length'] ?>" />
+										<span><?php echo $lang_admin_permissions['Max sig length help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_permissions['Max sig lines label'] ?></th>
+									<td>
+										<input type="text" name="form[sig_lines]" size="3" maxlength="3" value="<?php echo $pun_config['p_sig_lines'] ?>" />
+										<span><?php echo $lang_admin_permissions['Max sig lines help'] ?></span>
+									</td>
+								</tr>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_permissions['Registration subhead'] ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_permissions['Banned e-mail label'] ?></th>
+									<td>
+										<input type="radio" name="form[allow_banned_email]" value="1"<?php if ($pun_config['p_allow_banned_email'] == '1') echo ' checked="checked"' ?> />&#160;<strong><?php echo $lang_admin_common['Yes'] ?></strong>&#160;&#160;&#160;<input type="radio" name="form[allow_banned_email]" value="0"<?php if ($pun_config['p_allow_banned_email'] == '0') echo ' checked="checked"' ?> />&#160;<strong><?php echo $lang_admin_common['No'] ?></strong>
+										<span><?php echo $lang_admin_permissions['Banned e-mail help'] ?></span>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_permissions['Duplicate e-mail label'] ?></th>
+									<td>
+										<input type="radio" name="form[allow_dupe_email]" value="1"<?php if ($pun_config['p_allow_dupe_email'] == '1') echo ' checked="checked"' ?> />&#160;<strong><?php echo $lang_admin_common['Yes'] ?></strong>&#160;&#160;&#160;<input type="radio" name="form[allow_dupe_email]" value="0"<?php if ($pun_config['p_allow_dupe_email'] == '0') echo ' checked="checked"' ?> />&#160;<strong><?php echo $lang_admin_common['No'] ?></strong>
+										<span><?php echo $lang_admin_permissions['Duplicate e-mail help'] ?></span>
+									</td>
+								</tr>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+				<p class="submitend"><input type="submit" name="save" value="<?php echo $lang_admin_common['Save changes'] ?>" /></p>
+			</form>
+		</div>
+	</div>
+	<div class="clearer"></div>
+</div>
+<?php
+
+require PUN_ROOT.'footer.php';
diff --git a/admin_ranks.php b/admin_ranks.php
new file mode 100644
index 0000000..e054915
--- /dev/null
+++ b/admin_ranks.php
@@ -0,0 +1,186 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// Tell header.php to use the admin template
+define('PUN_ADMIN_CONSOLE', 1);
+
+define('PUN_ROOT', dirname(__FILE__).'/');
+require PUN_ROOT.'include/common.php';
+require PUN_ROOT.'include/common_admin.php';
+
+
+if ($pun_user['g_id'] != PUN_ADMIN)
+	message($lang_common['No permission'], false, '403 Forbidden');
+
+// Load the admin_ranks.php language file
+require PUN_ROOT.'lang/'.$admin_language.'/admin_ranks.php';
+
+// Add a rank
+if (isset($_POST['add_rank']))
+{
+	confirm_referrer('admin_ranks.php');
+
+	$rank = pun_trim($_POST['new_rank']);
+	$min_posts = pun_trim($_POST['new_min_posts']);
+
+	if ($rank == '')
+		message($lang_admin_ranks['Must enter title message']);
+
+	if ($min_posts == '' || preg_match('%[^0-9]%', $min_posts))
+		message($lang_admin_ranks['Must be integer message']);
+
+	// Make sure there isn't already a rank with the same min_posts value
+	$result = $db->query('SELECT 1 FROM '.$db->prefix.'ranks WHERE min_posts='.$min_posts) or error('Unable to fetch rank info', __FILE__, __LINE__, $db->error());
+	if ($db->num_rows($result))
+		message(sprintf($lang_admin_ranks['Dupe min posts message'], $min_posts));
+
+	$db->query('INSERT INTO '.$db->prefix.'ranks (rank, min_posts) VALUES(\''.$db->escape($rank).'\', '.$min_posts.')') or error('Unable to add rank', __FILE__, __LINE__, $db->error());
+
+	// Regenerate the ranks cache
+	if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+		require PUN_ROOT.'include/cache.php';
+
+	generate_ranks_cache();
+
+	redirect('admin_ranks.php', $lang_admin_ranks['Rank added redirect']);
+}
+
+
+// Update a rank
+else if (isset($_POST['update']))
+{
+	confirm_referrer('admin_ranks.php');
+
+	$id = intval(key($_POST['update']));
+
+	$rank = pun_trim($_POST['rank'][$id]);
+	$min_posts = pun_trim($_POST['min_posts'][$id]);
+
+	if ($rank == '')
+		message($lang_admin_ranks['Must enter title message']);
+
+	if ($min_posts == '' || preg_match('%[^0-9]%', $min_posts))
+		message($lang_admin_ranks['Must be integer message']);
+
+	// Make sure there isn't already a rank with the same min_posts value
+	$result = $db->query('SELECT 1 FROM '.$db->prefix.'ranks WHERE id!='.$id.' AND min_posts='.$min_posts) or error('Unable to fetch rank info', __FILE__, __LINE__, $db->error());
+	if ($db->num_rows($result))
+		message(sprintf($lang_admin_ranks['Dupe min posts message'], $min_posts));
+
+	$db->query('UPDATE '.$db->prefix.'ranks SET rank=\''.$db->escape($rank).'\', min_posts='.$min_posts.' WHERE id='.$id) or error('Unable to update rank', __FILE__, __LINE__, $db->error());
+
+	// Regenerate the ranks cache
+	if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+		require PUN_ROOT.'include/cache.php';
+
+	generate_ranks_cache();
+
+	redirect('admin_ranks.php', $lang_admin_ranks['Rank updated redirect']);
+}
+
+
+// Remove a rank
+else if (isset($_POST['remove']))
+{
+	confirm_referrer('admin_ranks.php');
+
+	$id = intval(key($_POST['remove']));
+
+	$db->query('DELETE FROM '.$db->prefix.'ranks WHERE id='.$id) or error('Unable to delete rank', __FILE__, __LINE__, $db->error());
+
+	// Regenerate the ranks cache
+	if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+		require PUN_ROOT.'include/cache.php';
+
+	generate_ranks_cache();
+
+	redirect('admin_ranks.php', $lang_admin_ranks['Rank removed redirect']);
+}
+
+$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_admin_common['Admin'], $lang_admin_common['Ranks']);
+$focus_element = array('ranks', 'new_rank');
+define('PUN_ACTIVE_PAGE', 'admin');
+require PUN_ROOT.'header.php';
+
+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">
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_ranks['Add rank subhead'] ?></legend>
+						<div class="infldset">
+							<p><?php echo $lang_admin_ranks['Add rank info'].' '.($pun_config['o_ranks'] == '1' ? sprintf($lang_admin_ranks['Ranks enabled'], '<a href="admin_options.php#ranks">'.$lang_admin_common['Options'].'</a>') : sprintf($lang_admin_ranks['Ranks disabled'], '<a href="admin_options.php#ranks">'.$lang_admin_common['Options'].'</a>')) ?></p>
+							<table cellspacing="0">
+							<thead>
+								<tr>
+									<th class="tcl" scope="col"><?php echo $lang_admin_ranks['Rank title label'] ?></th>
+									<th class="tc2" scope="col"><?php echo $lang_admin_ranks['Minimum posts label'] ?></th>
+									<th class="hidehead" scope="col"><?php echo $lang_admin_ranks['Actions label'] ?></th>
+								</tr>
+							</thead>
+							<tbody>
+								<tr>
+									<td class="tcl"><input type="text" name="new_rank" size="24" maxlength="50" tabindex="1" /></td>
+									<td class="tc2"><input type="text" name="new_min_posts" size="7" maxlength="7" tabindex="2" /></td>
+									<td><input type="submit" name="add_rank" value="<?php echo $lang_admin_common['Add'] ?>" tabindex="3" /></td>
+								</tr>
+							</tbody>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_ranks['Edit remove subhead'] ?></legend>
+						<div class="infldset">
+<?php
+
+$result = $db->query('SELECT id, rank, min_posts FROM '.$db->prefix.'ranks ORDER BY min_posts') or error('Unable to fetch rank list', __FILE__, __LINE__, $db->error());
+if ($db->num_rows($result))
+{
+
+?>
+							<table cellspacing="0">
+							<thead>
+								<tr>
+									<th class="tcl" scope="col"><?php echo $lang_admin_ranks['Rank title label'] ?></th>
+									<th class="tc2" scope="col"><?php echo $lang_admin_ranks['Minimum posts label'] ?></th>
+									<th class="hidehead" scope="col"><?php echo $lang_admin_ranks['Actions label'] ?></th>
+								</tr>
+							</thead>
+							<tbody>
+<?php
+
+	while ($cur_rank = $db->fetch_assoc($result))
+		echo "\t\t\t\t\t\t\t\t".'<tr><td class="tcl"><input type="text" name="rank['.$cur_rank['id'].']" value="'.pun_htmlspecialchars($cur_rank['rank']).'" size="24" maxlength="50" /></td><td class="tc2"><input type="text" name="min_posts['.$cur_rank['id'].']" value="'.$cur_rank['min_posts'].'" size="7" maxlength="7" /></td><td><input type="submit" name="update['.$cur_rank['id'].']" value="'.$lang_admin_common['Update'].'" />&#160;<input type="submit" name="remove['.$cur_rank['id'].']" value="'.$lang_admin_common['Remove'].'" /></td></tr>'."\n";
+
+?>
+							</tbody>
+							</table>
+<?php
+
+}
+else
+	echo "\t\t\t\t\t\t\t".'<p>'.$lang_admin_ranks['No ranks in list'].'</p>'."\n";
+
+?>
+						</div>
+					</fieldset>
+				</div>
+			</form>
+		</div>
+	</div>
+	<div class="clearer"></div>
+</div>
+<?php
+
+require PUN_ROOT.'footer.php';
diff --git a/admin_reports.php b/admin_reports.php
new file mode 100644
index 0000000..f2ce80d
--- /dev/null
+++ b/admin_reports.php
@@ -0,0 +1,184 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// Tell header.php to use the admin template
+define('PUN_ADMIN_CONSOLE', 1);
+
+define('PUN_ROOT', dirname(__FILE__).'/');
+require PUN_ROOT.'include/common.php';
+require PUN_ROOT.'include/common_admin.php';
+
+
+if (!$pun_user['is_admmod'])
+	message($lang_common['No permission'], false, '403 Forbidden');
+
+// Load the admin_reports.php language file
+require PUN_ROOT.'lang/'.$admin_language.'/admin_reports.php';
+
+// Zap a report
+if (isset($_POST['zap_id']))
+{
+	confirm_referrer('admin_reports.php');
+
+	$zap_id = intval(key($_POST['zap_id']));
+
+	$result = $db->query('SELECT zapped FROM '.$db->prefix.'reports WHERE id='.$zap_id) or error('Unable to fetch report info', __FILE__, __LINE__, $db->error());
+	$zapped = $db->result($result);
+
+	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)
+	{
+		$zapped_threshold = $db->result($result);
+		$db->query('DELETE FROM '.$db->prefix.'reports WHERE zapped <= '.$zapped_threshold) or error('Unable to delete old read reports', __FILE__, __LINE__, $db->error());
+	}
+
+	redirect('admin_reports.php', $lang_admin_reports['Report zapped redirect']);
+}
+
+
+$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_admin_common['Admin'], $lang_admin_common['Reports']);
+define('PUN_ACTIVE_PAGE', 'admin');
+require PUN_ROOT.'header.php';
+
+generate_admin_menu('reports');
+
+?>
+	<div class="blockform">
+		<h2><span><?php echo $lang_admin_reports['New reports head'] ?></span></h2>
+		<div class="box">
+			<form method="post" action="admin_reports.php?action=zap">
+<?php
+
+$result = $db->query('SELECT r.id, r.topic_id, r.forum_id, r.reported_by, r.created, r.message, p.id AS pid, t.subject, f.forum_name, u.username AS reporter FROM '.$db->prefix.'reports AS r LEFT JOIN '.$db->prefix.'posts AS p ON r.post_id=p.id LEFT JOIN '.$db->prefix.'topics AS t ON r.topic_id=t.id LEFT JOIN '.$db->prefix.'forums AS f ON r.forum_id=f.id LEFT JOIN '.$db->prefix.'users AS u ON r.reported_by=u.id WHERE r.zapped IS NULL ORDER BY created DESC') or error('Unable to fetch report list', __FILE__, __LINE__, $db->error());
+
+if ($db->num_rows($result))
+{
+	while ($cur_report = $db->fetch_assoc($result))
+	{
+		$reporter = ($cur_report['reporter'] != '') ? '<a href="profile.php?id='.$cur_report['reported_by'].'">'.pun_htmlspecialchars($cur_report['reporter']).'</a>' : $lang_admin_reports['Deleted user'];
+		$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>»&#160;<a href="viewtopic.php?id='.$cur_report['topic_id'].'">'.pun_htmlspecialchars($cur_report['subject']).'</a></span>' : '<span>»&#160;'.$lang_admin_reports['Deleted'].'</span>';
+		$post = str_replace("\n", '<br />', pun_htmlspecialchars($cur_report['message']));
+		$post_id = ($cur_report['pid'] != '') ? '<span>»&#160;<a href="viewtopic.php?pid='.$cur_report['pid'].'#p'.$cur_report['pid'].'">'.sprintf($lang_admin_reports['Post ID'], $cur_report['pid']).'</a></span>' : '<span>»&#160;'.$lang_admin_reports['Deleted'].'</span>';
+		$report_location = array($forum, $topic, $post_id);
+
+?>
+				<div class="inform">
+					<fieldset>
+						<legend><?php printf($lang_admin_reports['Report subhead'], format_time($cur_report['created'])) ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php printf($lang_admin_reports['Reported by'], $reporter) ?></th>
+									<td class="location"><?php echo implode(' ', $report_location) ?></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_reports['Reason'] ?><div><input type="submit" name="zap_id[<?php echo $cur_report['id'] ?>]" value="<?php echo $lang_admin_reports['Zap'] ?>" /></div></th>
+									<td><?php echo $post ?></td>
+								</tr>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+<?php
+
+	}
+}
+else
+{
+
+?>
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_common['None'] ?></legend>
+						<div class="infldset">
+							<p><?php echo $lang_admin_reports['No new reports'] ?></p>
+						</div>
+					</fieldset>
+				</div>
+<?php
+
+}
+
+?>
+			</form>
+		</div>
+	</div>
+
+	<div class="blockform block2">
+		<h2><span><?php echo $lang_admin_reports['Last 10 head'] ?></span></h2>
+		<div class="box">
+			<div class="fakeform">
+<?php
+
+$result = $db->query('SELECT r.id, r.topic_id, r.forum_id, r.reported_by, r.message, r.zapped, r.zapped_by AS zapped_by_id, p.id AS pid, t.subject, f.forum_name, u.username AS reporter, u2.username AS zapped_by FROM '.$db->prefix.'reports AS r LEFT JOIN '.$db->prefix.'posts AS p ON r.post_id=p.id LEFT JOIN '.$db->prefix.'topics AS t ON r.topic_id=t.id LEFT JOIN '.$db->prefix.'forums AS f ON r.forum_id=f.id LEFT JOIN '.$db->prefix.'users AS u ON r.reported_by=u.id LEFT JOIN '.$db->prefix.'users AS u2 ON r.zapped_by=u2.id WHERE r.zapped IS NOT NULL ORDER BY zapped DESC LIMIT 10') or error('Unable to fetch report list', __FILE__, __LINE__, $db->error());
+
+if ($db->num_rows($result))
+{
+	while ($cur_report = $db->fetch_assoc($result))
+	{
+		$reporter = ($cur_report['reporter'] != '') ? '<a href="profile.php?id='.$cur_report['reported_by'].'">'.pun_htmlspecialchars($cur_report['reporter']).'</a>' : $lang_admin_reports['Deleted user'];
+		$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>»&#160;<a href="viewtopic.php?id='.$cur_report['topic_id'].'">'.pun_htmlspecialchars($cur_report['subject']).'</a></span>' : '<span>»&#160;'.$lang_admin_reports['Deleted'].'</span>';
+		$post = str_replace("\n", '<br />', pun_htmlspecialchars($cur_report['message']));
+		$post_id = ($cur_report['pid'] != '') ? '<span>»&#160;<a href="viewtopic.php?pid='.$cur_report['pid'].'#p'.$cur_report['pid'].'">'.sprintf($lang_admin_reports['Post ID'], $cur_report['pid']).'</a></span>' : '<span>»&#160;'.$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);
+
+?>
+				<div class="inform">
+					<fieldset>
+						<legend><?php printf($lang_admin_reports['Zapped subhead'], format_time($cur_report['zapped']), $zapped_by) ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php printf($lang_admin_reports['Reported by'], $reporter) ?></th>
+									<td class="location"><?php echo implode(' ', $report_location) ?></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_reports['Reason'] ?></th>
+									<td><?php echo $post ?></td>
+								</tr>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+<?php
+
+	}
+}
+else
+{
+
+?>
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_common['None'] ?></legend>
+						<div class="infldset">
+							<p><?php echo $lang_admin_reports['No zapped reports'] ?></p>
+						</div>
+					</fieldset>
+				</div>
+<?php
+
+}
+
+?>
+			</div>
+		</div>
+	</div>
+	<div class="clearer"></div>
+</div>
+<?php
+
+require PUN_ROOT.'footer.php';
diff --git a/admin_users.php b/admin_users.php
new file mode 100644
index 0000000..d41c1fb
--- /dev/null
+++ b/admin_users.php
@@ -0,0 +1,1085 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// Tell header.php to use the admin template
+define('PUN_ADMIN_CONSOLE', 1);
+
+define('PUN_ROOT', dirname(__FILE__).'/');
+require PUN_ROOT.'include/common.php';
+require PUN_ROOT.'include/common_admin.php';
+
+
+if (!$pun_user['is_admmod'])
+	message($lang_common['No permission'], false, '403 Forbidden');
+
+// Load the admin_users.php language file
+require PUN_ROOT.'lang/'.$admin_language.'/admin_users.php';
+
+// Show IP statistics for a certain user ID
+if (isset($_GET['ip_stats']))
+{
+	$ip_stats = intval($_GET['ip_stats']);
+	if ($ip_stats < 1)
+		message($lang_common['Bad request']);
+
+	// Fetch ip count
+	$result = $db->query('SELECT poster_ip, MAX(posted) AS last_used FROM '.$db->prefix.'posts WHERE poster_id='.$ip_stats.' GROUP BY poster_ip') or error('Unable to fetch post info', __FILE__, __LINE__, $db->error());
+	$num_ips = $db->num_rows($result);
+
+	// Determine the ip offset (based on $_GET['p'])
+	$num_pages = ceil($num_ips / 50);
+
+	$p = (!isset($_GET['p']) || $_GET['p'] <= 1 || $_GET['p'] > $num_pages) ? 1 : intval($_GET['p']);
+	$start_from = 50 * ($p - 1);
+
+	// Generate paging links
+	$paging_links = '<span class="pages-label">'.$lang_common['Pages'].' </span>'.paginate($num_pages, $p, 'admin_users.php?ip_stats='.$ip_stats );
+
+	$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_admin_common['Admin'], $lang_admin_common['Users'], $lang_admin_users['Results head']);
+	define('PUN_ACTIVE_PAGE', 'admin');
+	require PUN_ROOT.'header.php';
+
+?>
+<div class="linkst">
+	<div class="inbox crumbsplus">
+		<ul class="crumbs">
+			<li><a href="admin_index.php"><?php echo $lang_admin_common['Admin'].' '.$lang_admin_common['Index'] ?></a></li>
+			<li><span>»&#160;</span><a href="admin_users.php"><?php echo $lang_admin_common['Users'] ?></a></li>
+			<li><span>»&#160;</span><strong><?php echo $lang_admin_users['Results head'] ?></strong></li>
+		</ul>
+		<div class="pagepost">
+			<p class="pagelink"><?php echo $paging_links ?></p>
+		</div>
+		<div class="clearer"></div>
+	</div>
+</div>
+
+<div id="users1" class="blocktable">
+	<h2><span><?php echo $lang_admin_users['Results head'] ?></span></h2>
+	<div class="box">
+		<div class="inbox">
+			<table cellspacing="0">
+			<thead>
+				<tr>
+					<th class="tcl" scope="col"><?php echo $lang_admin_users['Results IP address head'] ?></th>
+					<th class="tc2" scope="col"><?php echo $lang_admin_users['Results last used head'] ?></th>
+					<th class="tc3" scope="col"><?php echo $lang_admin_users['Results times found head'] ?></th>
+					<th class="tcr" scope="col"><?php echo $lang_admin_users['Results action head'] ?></th>
+				</tr>
+			</thead>
+			<tbody>
+<?php
+
+	$result = $db->query('SELECT poster_ip, MAX(posted) AS last_used, COUNT(id) AS used_times FROM '.$db->prefix.'posts WHERE poster_id='.$ip_stats.' GROUP BY poster_ip ORDER BY last_used DESC LIMIT '.$start_from.', 50') or error('Unable to fetch post info', __FILE__, __LINE__, $db->error());
+	if ($db->num_rows($result))
+	{
+		while ($cur_ip = $db->fetch_assoc($result))
+		{
+
+?>
+				<tr>
+					<td class="tcl"><a href="moderate.php?get_host=<?php echo pun_htmlspecialchars($cur_ip['poster_ip']) ?>"><?php echo pun_htmlspecialchars($cur_ip['poster_ip']) ?>"><?php echo $cur_ip['poster_ip'] ?></a></td>
+					<td class="tc2"><?php echo format_time($cur_ip['last_used']) ?></td>
+					<td class="tc3"><?php echo $cur_ip['used_times'] ?></td>
+					<td class="tcr"><a href="admin_users.php?show_users=<?php echo pun_htmlspecialchars($cur_ip['poster_ip']) ?>"><?php echo $lang_admin_users['Results find more link'] ?></a></td>
+				</tr>
+<?php
+
+		}
+	}
+	else
+		echo "\t\t\t\t".'<tr><td class="tcl" colspan="4">'.$lang_admin_users['Results no posts found'].'</td></tr>'."\n";
+
+?>
+			</tbody>
+			</table>
+		</div>
+	</div>
+</div>
+
+<div class="linksb">
+	<div class="inbox crumbsplus">
+		<div class="pagepost">
+			<p class="pagelink"><?php echo $paging_links ?></p>
+		</div>
+		<ul class="crumbs">
+			<li><a href="admin_index.php"><?php echo $lang_admin_common['Admin'].' '.$lang_admin_common['Index'] ?></a></li>
+			<li><span>»&#160;</span><a href="admin_users.php"><?php echo $lang_admin_common['Users'] ?></a></li>
+			<li><span>»&#160;</span><strong><?php echo $lang_admin_users['Results head'] ?></strong></li>
+		</ul>
+		<div class="clearer"></div>
+	</div>
+</div>
+<?php
+
+	require PUN_ROOT.'footer.php';
+}
+
+
+if (isset($_GET['show_users']))
+{
+	$ip = pun_trim($_GET['show_users']);
+
+	if (!@preg_match('%^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$%', $ip) && !@preg_match('%^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$%', $ip))
+		message($lang_admin_users['Bad IP message']);
+
+	// Fetch user count
+	$result = $db->query('SELECT DISTINCT poster_id, poster FROM '.$db->prefix.'posts WHERE poster_ip=\''.$db->escape($ip).'\'') or error('Unable to fetch post info', __FILE__, __LINE__, $db->error());
+	$num_users = $db->num_rows($result);
+
+	// Determine the user offset (based on $_GET['p'])
+	$num_pages = ceil($num_users / 50);
+
+	$p = (!isset($_GET['p']) || $_GET['p'] <= 1 || $_GET['p'] > $num_pages) ? 1 : intval($_GET['p']);
+	$start_from = 50 * ($p - 1);
+
+	// Generate paging links
+	$paging_links = '<span class="pages-label">'.$lang_common['Pages'].' </span>'.paginate($num_pages, $p, 'admin_users.php?show_users='.$ip);
+
+	$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_admin_common['Admin'], $lang_admin_common['Users'], $lang_admin_users['Results head']);
+	define('PUN_ACTIVE_PAGE', 'admin');
+	require PUN_ROOT.'header.php';
+
+?>
+<div class="linkst">
+	<div class="inbox crumbsplus">
+		<ul class="crumbs">
+			<li><a href="admin_index.php"><?php echo $lang_admin_common['Admin'].' '.$lang_admin_common['Index'] ?></a></li>
+			<li><span>»&#160;</span><a href="admin_users.php"><?php echo $lang_admin_common['Users'] ?></a></li>
+			<li><span>»&#160;</span><strong><?php echo $lang_admin_users['Results head'] ?></strong></li>
+		</ul>
+		<div class="pagepost">
+			<p class="pagelink"><?php echo $paging_links ?></p>
+		</div>
+		<div class="clearer"></div>
+	</div>
+</div>
+
+<div id="users2" class="blocktable">
+	<h2><span><?php echo $lang_admin_users['Results head'] ?></span></h2>
+	<div class="box">
+		<div class="inbox">
+			<table cellspacing="0">
+			<thead>
+				<tr>
+					<th class="tcl" scope="col"><?php echo $lang_admin_users['Results username head'] ?></th>
+					<th class="tc2" scope="col"><?php echo $lang_admin_users['Results e-mail head'] ?></th>
+					<th class="tc3" scope="col"><?php echo $lang_admin_users['Results title head'] ?></th>
+					<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>
+				</tr>
+			</thead>
+			<tbody>
+<?php
+
+	$result = $db->query('SELECT DISTINCT poster_id, poster FROM '.$db->prefix.'posts WHERE poster_ip=\''.$db->escape($ip).'\' ORDER BY poster DESC') or error('Unable to fetch post info', __FILE__, __LINE__, $db->error());
+	$num_posts = $db->num_rows($result);
+
+	if ($num_posts)
+	{
+		// Loop through users and print out some info
+		for ($i = 0; $i < $num_posts; ++$i)
+		{
+			list($poster_id, $poster) = $db->fetch_row($result);
+
+			$result2 = $db->query('SELECT u.id, u.username, u.email, u.title, u.num_posts, u.admin_note, g.g_id, g.g_user_title FROM '.$db->prefix.'users AS u INNER JOIN '.$db->prefix.'groups AS g ON g.g_id=u.group_id WHERE u.id>1 AND u.id='.$poster_id) or error('Unable to fetch user info', __FILE__, __LINE__, $db->error());
+
+			if (($user_data = $db->fetch_assoc($result2)))
+			{
+				$user_title = get_title($user_data);
+
+				$actions = '<a href="admin_users.php?ip_stats='.$user_data['id'].'">'.$lang_admin_users['Results view IP link'].'</a> | <a href="search.php?action=show_user_posts&amp;user_id='.$user_data['id'].'">'.$lang_admin_users['Results show posts link'].'</a>';
+
+?>
+				<tr>
+					<td class="tcl"><?php echo '<a href="profile.php?id='.$user_data['id'].'">'.pun_htmlspecialchars($user_data['username']).'</a>' ?></td>
+					<td class="tc2"><a href="mailto:<?php echo $user_data['email'] ?>"><?php echo $user_data['email'] ?></a></td>
+					<td class="tc3"><?php echo $user_title ?></td>
+					<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']) : '&#160;' ?></td>
+					<td class="tcr"><?php echo $actions ?></td>
+				</tr>
+<?php
+
+			}
+			else
+			{
+
+?>
+				<tr>
+					<td class="tcl"><?php echo pun_htmlspecialchars($poster) ?></td>
+					<td class="tc2">&#160;</td>
+					<td class="tc3"><?php echo $lang_admin_users['Results guest'] ?></td>
+					<td class="tc4">&#160;</td>
+					<td class="tc5">&#160;</td>
+					<td class="tcr">&#160;</td>
+				</tr>
+<?php
+
+			}
+		}
+	}
+	else
+		echo "\t\t\t\t".'<tr><td class="tcl" colspan="6">'.$lang_admin_users['Results no IP found'].'</td></tr>'."\n";
+
+?>
+			</tbody>
+			</table>
+		</div>
+	</div>
+</div>
+
+<div class="linksb">
+	<div class="inbox crumbsplus">
+		<div class="pagepost">
+			<p class="pagelink"><?php echo $paging_links ?></p>
+		</div>
+		<ul class="crumbs">
+			<li><a href="admin_index.php"><?php echo $lang_admin_common['Admin'].' '.$lang_admin_common['Index'] ?></a></li>
+			<li><span>»&#160;</span><a href="admin_users.php"><?php echo $lang_admin_common['Users'] ?></a></li>
+			<li><span>»&#160;</span><strong><?php echo $lang_admin_users['Results head'] ?></strong></li>
+		</ul>
+		<div class="clearer"></div>
+	</div>
+</div>
+<?php
+	require PUN_ROOT.'footer.php';
+}
+
+
+// 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'], false, '403 Forbidden');
+
+	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'], false, '403 Forbidden');
+
+	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);
+
+		// Regenerate the users info cache
+		if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+			require PUN_ROOT.'include/cache.php';
+
+		generate_users_info_cache();
+
+		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'], false, '403 Forbidden');
+
+	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" />&#160;<strong><?php echo $lang_admin_common['Yes'] ?></strong>&#160;&#160;&#160;<input type="radio" name="ban_the_ip" tabindex="4" value="0" checked="checked" />&#160;<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();
+
+	// trim() all elements in $form
+	$form = array_map('pun_trim', $form);
+	$conditions = $query_str = array();
+
+	$posts_greater = isset($_GET['posts_greater']) ? pun_trim($_GET['posts_greater']) : '';
+	$posts_less = isset($_GET['posts_less']) ? pun_trim($_GET['posts_less']) : '';
+	$last_post_after = isset($_GET['last_post_after']) ? pun_trim($_GET['last_post_after']) : '';
+	$last_post_before = isset($_GET['last_post_before']) ? pun_trim($_GET['last_post_before']) : '';
+	$last_visit_after = isset($_GET['last_visit_after']) ? pun_trim($_GET['last_visit_after']) : '';
+	$last_visit_before = isset($_GET['last_visit_before']) ? pun_trim($_GET['last_visit_before']) : '';
+	$registered_after = isset($_GET['registered_after']) ? pun_trim($_GET['registered_after']) : '';
+	$registered_before = isset($_GET['registered_before']) ? pun_trim($_GET['registered_before']) : '';
+	$order_by = isset($_GET['order_by']) && in_array($_GET['order_by'], array('username', 'email', 'num_posts', 'last_post', 'last_visit', 'registered')) ? $_GET['order_by'] : 'username';
+	$direction = isset($_GET['direction']) && $_GET['direction'] == 'DESC' ? 'DESC' : 'ASC';
+	$user_group = isset($_GET['user_group']) ? intval($_GET['user_group']) : -1;
+
+	$query_str[] = 'order_by='.$order_by;
+	$query_str[] = 'direction='.$direction;
+	$query_str[] = 'user_group='.$user_group;
+
+	if (preg_match('%[^0-9]%', $posts_greater.$posts_less))
+		message($lang_admin_users['Non numeric message']);
+
+	// Try to convert date/time to timestamps
+	if ($last_post_after != '')
+	{
+		$query_str[] = 'last_post_after='.$last_post_after;
+
+		$last_post_after = strtotime($last_post_after);
+		if ($last_post_after === false || $last_post_after == -1)
+			message($lang_admin_users['Invalid date time message']);
+
+		$conditions[] = 'u.last_post>'.$last_post_after;
+	}
+	if ($last_post_before != '')
+	{
+		$query_str[] = 'last_post_before='.$last_post_before;
+
+		$last_post_before = strtotime($last_post_before);
+		if ($last_post_before === false || $last_post_before == -1)
+			message($lang_admin_users['Invalid date time message']);
+
+		$conditions[] = 'u.last_post<'.$last_post_before;
+	}
+	if ($last_visit_after != '')
+	{
+		$query_str[] = 'last_visit_after='.$last_visit_after;
+
+		$last_visit_after = strtotime($last_visit_after);
+		if ($last_visit_after === false || $last_visit_after == -1)
+			message($lang_admin_users['Invalid date time message']);
+
+		$conditions[] = 'u.last_visit>'.$last_visit_after;
+	}
+	if ($last_visit_before != '')
+	{
+		$query_str[] = 'last_visit_before='.$last_visit_before;
+
+		$last_visit_before = strtotime($last_visit_before);
+		if ($last_visit_before === false || $last_visit_before == -1)
+			message($lang_admin_users['Invalid date time message']);
+
+		$conditions[] = 'u.last_visit<'.$last_visit_before;
+	}
+	if ($registered_after != '')
+	{
+		$query_str[] = 'registered_after='.$registered_after;
+
+		$registered_after = strtotime($registered_after);
+		if ($registered_after === false || $registered_after == -1)
+			message($lang_admin_users['Invalid date time message']);
+
+		$conditions[] = 'u.registered>'.$registered_after;
+	}
+	if ($registered_before != '')
+	{
+		$query_str[] = 'registered_before='.$registered_before;
+
+		$registered_before = strtotime($registered_before);
+		if ($registered_before === false || $registered_before == -1)
+			message($lang_admin_users['Invalid date time message']);
+
+		$conditions[] = 'u.registered<'.$registered_before;
+	}
+
+	$like_command = ($db_type == 'pgsql') ? 'ILIKE' : 'LIKE';
+	foreach ($form as $key => $input)
+	{
+		if ($input != '' && in_array($key, array('username', 'email', 'title', 'realname', 'url', 'jabber', 'icq', 'msn', 'aim', 'yahoo', 'location', 'signature', 'admin_note')))
+		{
+			$conditions[] = 'u.'.$db->escape($key).' '.$like_command.' \''.$db->escape(str_replace('*', '%', $input)).'\'';
+			$query_str[] = 'form%5B'.$key.'%5D='.urlencode($input);
+		}
+	}
+
+	if ($posts_greater != '')
+	{
+		$query_str[] = 'posts_greater='.$posts_greater;
+		$conditions[] = 'u.num_posts>'.$posts_greater;
+	}
+	if ($posts_less != '')
+	{
+		$query_str[] = 'posts_less='.$posts_less;
+		$conditions[] = 'u.num_posts<'.$posts_less;
+	}
+
+	if ($user_group > -1)
+		$conditions[] = 'u.group_id='.$user_group;
+
+	// Fetch user count
+	$result = $db->query('SELECT COUNT(id) FROM '.$db->prefix.'users AS u LEFT JOIN '.$db->prefix.'groups AS g ON g.g_id=u.group_id WHERE u.id>1'.(!empty($conditions) ? ' AND '.implode(' AND ', $conditions) : '')) or error('Unable to fetch user info', __FILE__, __LINE__, $db->error());
+	$num_users = $db->result($result);
+
+	// Determine the user offset (based on $_GET['p'])
+	$num_pages = ceil($num_users / 50);
+
+	$p = (!isset($_GET['p']) || $_GET['p'] <= 1 || $_GET['p'] > $num_pages) ? 1 : intval($_GET['p']);
+	$start_from = 50 * ($p - 1);
+
+	// Generate paging links
+	$paging_links = '<span class="pages-label">'.$lang_common['Pages'].' </span>'.paginate($num_pages, $p, 'admin_users.php?find_user=&amp;'.implode('&amp;', $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';
+
+?>
+<div class="linkst">
+	<div class="inbox crumbsplus">
+		<ul class="crumbs">
+			<li><a href="admin_index.php"><?php echo $lang_admin_common['Admin'].' '.$lang_admin_common['Index'] ?></a></li>
+			<li><span>»&#160;</span><a href="admin_users.php"><?php echo $lang_admin_common['Users'] ?></a></li>
+			<li><span>»&#160;</span><strong><?php echo $lang_admin_users['Results head'] ?></strong></li>
+		</ul>
+		<div class="pagepost">
+			<p class="pagelink"><?php echo $paging_links ?></p>
+		</div>
+		<div class="clearer"></div>
+	</div>
+</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">
+		<div class="inbox">
+			<table cellspacing="0">
+			<thead>
+				<tr>
+					<th class="tcl" scope="col"><?php echo $lang_admin_users['Results username head'] ?></th>
+					<th class="tc2" scope="col"><?php echo $lang_admin_users['Results e-mail head'] ?></th>
+					<th class="tc3" scope="col"><?php echo $lang_admin_users['Results title head'] ?></th>
+					<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>
+<?php
+
+	$result = $db->query('SELECT u.id, u.username, u.email, u.title, u.num_posts, u.admin_note, g.g_id, g.g_user_title FROM '.$db->prefix.'users AS u LEFT JOIN '.$db->prefix.'groups AS g ON g.g_id=u.group_id WHERE u.id>1'.(!empty($conditions) ? ' AND '.implode(' AND ', $conditions) : '').' ORDER BY '.$db->escape($order_by).' '.$db->escape($direction).' LIMIT '.$start_from.', 50') or error('Unable to fetch user info', __FILE__, __LINE__, $db->error());
+	if ($db->num_rows($result))
+	{
+		while ($user_data = $db->fetch_assoc($result))
+		{
+			$user_title = get_title($user_data);
+
+			// This script is a special case in that we want to display "Not verified" for non-verified users
+			if (($user_data['g_id'] == '' || $user_data['g_id'] == PUN_UNVERIFIED) && $user_title != $lang_common['Banned'])
+				$user_title = '<span class="warntext">'.$lang_admin_users['Not verified'].'</span>';
+
+			$actions = '<a href="admin_users.php?ip_stats='.$user_data['id'].'">'.$lang_admin_users['Results view IP link'].'</a> | <a href="search.php?action=show_user_posts&amp;user_id='.$user_data['id'].'">'.$lang_admin_users['Results show posts link'].'</a>';
+
+?>
+				<tr>
+					<td class="tcl"><?php echo '<a href="profile.php?id='.$user_data['id'].'">'.pun_htmlspecialchars($user_data['username']).'</a>' ?></td>
+					<td class="tc2"><a href="mailto:<?php echo $user_data['email'] ?>"><?php echo $user_data['email'] ?></a></td>
+					<td class="tc3"><?php echo $user_title ?></td>
+					<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']) : '&#160;' ?></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
+
+		}
+	}
+	else
+		echo "\t\t\t\t".'<tr><td class="tcl" colspan="6">'.$lang_admin_users['No match'].'</td></tr>'."\n";
+
+?>
+			</tbody>
+			</table>
+		</div>
+	</div>
+</div>
+
+<div class="linksb">
+	<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>
+			<li><span>»&#160;</span><a href="admin_users.php"><?php echo $lang_admin_common['Users'] ?></a></li>
+			<li><span>»&#160;</span><strong><?php echo $lang_admin_users['Results head'] ?></strong></li>
+		</ul>
+		<div class="clearer"></div>
+	</div>
+</div>
+</form>
+<?php
+
+	require PUN_ROOT.'footer.php';
+}
+
+
+else
+{
+	$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_admin_common['Admin'], $lang_admin_common['Users']);
+	$focus_element = array('find_user', 'form[username]');
+	define('PUN_ACTIVE_PAGE', 'admin');
+	require PUN_ROOT.'header.php';
+
+	generate_admin_menu('users');
+
+?>
+	<div class="blockform">
+		<h2><span><?php echo $lang_admin_users['User search head'] ?></span></h2>
+		<div class="box">
+			<form id="find_user" method="get" action="admin_users.php">
+				<p class="submittop"><input type="submit" name="find_user" value="<?php echo $lang_admin_users['Submit search'] ?>" tabindex="1" /></p>
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_users['User search subhead'] ?></legend>
+						<div class="infldset">
+							<p><?php echo $lang_admin_users['User search info'] ?></p>
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_users['Username label'] ?></th>
+									<td><input type="text" name="form[username]" size="25" maxlength="25" tabindex="2" /></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_users['E-mail address label'] ?></th>
+									<td><input type="text" name="form[email]" size="30" maxlength="80" tabindex="3" /></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_users['Title label'] ?></th>
+									<td><input type="text" name="form[title]" size="30" maxlength="50" tabindex="4" /></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_users['Real name label'] ?></th>
+									<td><input type="text" name="form[realname]" size="30" maxlength="40" tabindex="5" /></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_users['Website label'] ?></th>
+									<td><input type="text" name="form[url]" size="35" maxlength="100" tabindex="6" /></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_users['Jabber label'] ?></th>
+									<td><input type="text" name="form[jabber]" size="30" maxlength="75" tabindex="7" /></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_users['ICQ label'] ?></th>
+									<td><input type="text" name="form[icq]" size="12" maxlength="12" tabindex="8" /></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_users['MSN label'] ?></th>
+									<td><input type="text" name="form[msn]" size="30" maxlength="50" tabindex="9" /></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_users['AOL label'] ?></th>
+									<td><input type="text" name="form[aim]" size="20" maxlength="20" tabindex="10" /></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_users['Yahoo label'] ?></th>
+									<td><input type="text" name="form[yahoo]" size="20" maxlength="20" tabindex="11" /></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_users['Location label'] ?></th>
+									<td><input type="text" name="form[location]" size="30" maxlength="30" tabindex="12" /></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_users['Signature label'] ?></th>
+									<td><input type="text" name="form[signature]" size="35" maxlength="512" tabindex="13" /></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_users['Admin note label'] ?></th>
+									<td><input type="text" name="form[admin_note]" size="30" maxlength="30" tabindex="14" /></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_users['Posts more than label'] ?></th>
+									<td><input type="text" name="posts_greater" size="5" maxlength="8" tabindex="15" /></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_users['Posts less than label'] ?></th>
+									<td><input type="text" name="posts_less" size="5" maxlength="8" tabindex="16" /></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_users['Last post after label'] ?></th>
+									<td><input type="text" name="last_post_after" size="24" maxlength="19" tabindex="17" />
+									<span><?php echo $lang_admin_users['Date help'] ?></span></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_users['Last post before label'] ?></th>
+									<td><input type="text" name="last_post_before" size="24" maxlength="19" tabindex="18" />
+									<span><?php echo $lang_admin_users['Date help'] ?></span></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_users['Last visit after label'] ?></th>
+									<td><input type="text" name="last_visit_after" size="24" maxlength="19" tabindex="17" />
+									<span><?php echo $lang_admin_users['Date help'] ?></span></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_users['Last visit before label'] ?></th>
+									<td><input type="text" name="last_visit_before" size="24" maxlength="19" tabindex="18" />
+									<span><?php echo $lang_admin_users['Date help'] ?></span></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_users['Registered after label'] ?></th>
+									<td><input type="text" name="registered_after" size="24" maxlength="19" tabindex="19" />
+									<span><?php echo $lang_admin_users['Date help'] ?></span></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_users['Registered before label'] ?></th>
+									<td><input type="text" name="registered_before" size="24" maxlength="19" tabindex="20" />
+									<span><?php echo $lang_admin_users['Date help'] ?></span></td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_users['Order by label'] ?></th>
+									<td>
+										<select name="order_by" tabindex="21">
+											<option value="username" selected="selected"><?php echo $lang_admin_users['Order by username'] ?></option>
+											<option value="email"><?php echo $lang_admin_users['Order by e-mail'] ?></option>
+											<option value="num_posts"><?php echo $lang_admin_users['Order by posts'] ?></option>
+											<option value="last_post"><?php echo $lang_admin_users['Order by last post'] ?></option>
+											<option value="last_visit"><?php echo $lang_admin_users['Order by last visit'] ?></option>
+											<option value="registered"><?php echo $lang_admin_users['Order by registered'] ?></option>
+										</select>&#160;&#160;&#160;<select name="direction" tabindex="22">
+											<option value="ASC" selected="selected"><?php echo $lang_admin_users['Ascending'] ?></option>
+											<option value="DESC"><?php echo $lang_admin_users['Descending'] ?></option>
+										</select>
+									</td>
+								</tr>
+								<tr>
+									<th scope="row"><?php echo $lang_admin_users['User group label'] ?></th>
+									<td>
+										<select name="user_group" tabindex="23">
+											<option value="-1" selected="selected"><?php echo $lang_admin_users['All groups'] ?></option>
+											<option value="0"><?php echo $lang_admin_users['Unverified users'] ?></option>
+<?php
+
+	$result = $db->query('SELECT g_id, g_title FROM '.$db->prefix.'groups WHERE g_id!='.PUN_GUEST.' ORDER BY g_title') or error('Unable to fetch user group list', __FILE__, __LINE__, $db->error());
+
+	while ($cur_group = $db->fetch_assoc($result))
+		echo "\t\t\t\t\t\t\t\t\t\t\t".'<option value="'.$cur_group['g_id'].'">'.pun_htmlspecialchars($cur_group['g_title']).'</option>'."\n";
+
+?>
+										</select>
+									</td>
+								</tr>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+				<p class="submitend"><input type="submit" name="find_user" value="<?php echo $lang_admin_users['Submit search'] ?>" tabindex="25" /></p>
+			</form>
+		</div>
+
+		<h2 class="block2"><span><?php echo $lang_admin_users['IP search head'] ?></span></h2>
+		<div class="box">
+			<form method="get" action="admin_users.php">
+				<div class="inform">
+					<fieldset>
+						<legend><?php echo $lang_admin_users['IP search subhead'] ?></legend>
+						<div class="infldset">
+							<table class="aligntop" cellspacing="0">
+								<tr>
+									<th scope="row"><?php echo $lang_admin_users['IP address label'] ?><div><input type="submit" value="<?php echo $lang_admin_users['Find IP address'] ?>" tabindex="26" /></div></th>
+									<td><input type="text" name="show_users" size="18" maxlength="15" tabindex="24" />
+									<span><?php echo $lang_admin_users['IP address help'] ?></span></td>
+								</tr>
+							</table>
+						</div>
+					</fieldset>
+				</div>
+			</form>
+		</div>
+	</div>
+	<div class="clearer"></div>
+</div>
+<?php
+
+	require PUN_ROOT.'footer.php';
+}
diff --git a/cache/.htaccess b/cache/.htaccess
new file mode 100644
index 0000000..e67301e
--- /dev/null
+++ b/cache/.htaccess
@@ -0,0 +1,4 @@
+<Limit GET POST PUT>
+Order Allow,Deny
+Deny from All
+</Limit>
diff --git a/cache/index.html b/cache/index.html
new file mode 100644
index 0000000..89337b2
--- /dev/null
+++ b/cache/index.html
@@ -0,0 +1 @@
+<html><head><title>.</title></head><body>.</body></html>
diff --git a/common.js b/common.js
new file mode 100644
index 0000000..47a7155
--- /dev/null
+++ b/common.js
@@ -0,0 +1,38 @@
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+function select_checkboxes(curFormId, link, new_string)
+{
+	var curForm = document.getElementById(curFormId);
+	var inputlist = curForm.getElementsByTagName("input");
+	for (i = 0; i < inputlist.length; i++)
+	{
+		if (inputlist[i].getAttribute("type") == 'checkbox' && inputlist[i].disabled == false)
+			inputlist[i].checked = true;
+	}
+	
+	link.setAttribute('onclick', 'return unselect_checkboxes(\'' + curFormId + '\', this, \'' + link.innerHTML + '\')');
+	link.innerHTML = new_string;
+
+	return false;
+}
+
+function unselect_checkboxes(curFormId, link, new_string)
+{
+	var curForm = document.getElementById(curFormId);
+	var inputlist = curForm.getElementsByTagName("input");
+	for (i = 0; i < inputlist.length; i++)
+	{
+		if (inputlist[i].getAttribute("type") == 'checkbox' && inputlist[i].disabled == false)
+			inputlist[i].checked = false;
+	}
+	
+	link.setAttribute('onclick', 'return select_checkboxes(\'' + curFormId + '\', this, \'' + link.innerHTML + '\')');
+	link.innerHTML = new_string;
+
+	return false;
+}
diff --git a/db_update.php b/db_update.php
new file mode 100644
index 0000000..c62cde9
--- /dev/null
+++ b/db_update.php
@@ -0,0 +1,1908 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// The FluxBB version this script updates to
+define('UPDATE_TO', '1.4.12');
+
+define('UPDATE_TO_DB_REVISION', 15);
+define('UPDATE_TO_SI_REVISION', 2);
+define('UPDATE_TO_PARSER_REVISION', 2);
+
+define('MIN_PHP_VERSION', '4.4.0');
+define('MIN_MYSQL_VERSION', '4.1.2');
+define('MIN_PGSQL_VERSION', '7.0.0');
+define('PUN_SEARCH_MIN_WORD', 3);
+define('PUN_SEARCH_MAX_WORD', 20);
+
+// The MySQL connection character set that was used for FluxBB 1.2 - in 99% of cases this should be detected automatically,
+// but can be overridden using the below constant if required.
+//define('FORUM_DEFAULT_CHARSET', 'latin1');
+
+
+// The number of items to process per page view (lower this if the update script times out during UTF-8 conversion)
+define('PER_PAGE', 300);
+
+// Don't set to UTF-8 until after we've found out what the default character set is
+define('FORUM_NO_SET_NAMES', 1);
+
+// Make sure we are running at least MIN_PHP_VERSION
+if (!function_exists('version_compare') || version_compare(PHP_VERSION, MIN_PHP_VERSION, '<'))
+	exit('You are running PHP version '.PHP_VERSION.'. FluxBB '.UPDATE_TO.' requires at least PHP '.MIN_PHP_VERSION.' to run properly. You must upgrade your PHP installation before you can continue.');
+
+define('PUN_ROOT', dirname(__FILE__).'/');
+
+// Attempt to load the configuration file config.php
+if (file_exists(PUN_ROOT.'config.php'))
+	include PUN_ROOT.'config.php';
+
+// If we have the 1.3-legacy constant defined, define the proper 1.4 constant so we don't get an incorrect "need to install" message
+if (defined('FORUM'))
+	define('PUN', FORUM);
+
+// If PUN isn't defined, config.php is missing or corrupt
+if (!defined('PUN'))
+{
+	header('Location: install.php');
+	exit;
+}
+
+// Enable debug mode
+if (!defined('PUN_DEBUG'))
+	define('PUN_DEBUG', 1);
+
+// Load the functions script
+require PUN_ROOT.'include/functions.php';
+
+// Load UTF-8 functions
+require PUN_ROOT.'include/utf8/utf8.php';
+
+// Strip out "bad" UTF-8 characters
+forum_remove_bad_characters();
+
+// Reverse the effect of register_globals
+forum_unregister_globals();
+
+// Turn on full PHP error reporting
+error_reporting(E_ALL);
+
+// Force POSIX locale (to prevent functions such as strtolower() from messing up UTF-8 strings)
+setlocale(LC_CTYPE, 'C');
+
+// Turn off magic_quotes_runtime
+if (get_magic_quotes_runtime())
+	set_magic_quotes_runtime(0);
+
+// Strip slashes from GET/POST/COOKIE (if magic_quotes_gpc is enabled)
+if (get_magic_quotes_gpc())
+{
+	function stripslashes_array($array)
+	{
+		return is_array($array) ? array_map('stripslashes_array', $array) : stripslashes($array);
+	}
+
+	$_GET = stripslashes_array($_GET);
+	$_POST = stripslashes_array($_POST);
+	$_COOKIE = stripslashes_array($_COOKIE);
+	$_REQUEST = stripslashes_array($_REQUEST);
+}
+
+// If a cookie name is not specified in config.php, we use the default (forum_cookie)
+if (empty($cookie_name))
+	$cookie_name = 'pun_cookie';
+
+// If the cache directory is not specified, we use the default setting
+if (!defined('FORUM_CACHE_DIR'))
+	define('FORUM_CACHE_DIR', PUN_ROOT.'cache/');
+
+// Turn off PHP time limit
+@set_time_limit(0);
+
+// Define a few commonly used constants
+define('PUN_UNVERIFIED', 0);
+define('PUN_ADMIN', 1);
+define('PUN_MOD', 2);
+define('PUN_GUEST', 3);
+define('PUN_MEMBER', 4);
+
+// Load DB abstraction layer and try to connect
+require PUN_ROOT.'include/dblayer/common_db.php';
+
+// Check what the default character set is - since 1.2 didn't specify any we will use whatever the default was (usually latin1)
+$old_connection_charset = defined('FORUM_DEFAULT_CHARSET') ? FORUM_DEFAULT_CHARSET : $db->get_names();
+
+// Set the connection to UTF-8 now
+$db->set_names('utf8');
+
+// Get the forum config
+$result = $db->query('SELECT * FROM '.$db->prefix.'config') or error('Unable to fetch config.', __FILE__, __LINE__, $db->error());
+while ($cur_config_item = $db->fetch_row($result))
+	$pun_config[$cur_config_item[0]] = $cur_config_item[1];
+
+// Load language file
+$default_lang = $pun_config['o_default_lang'];
+
+if (!file_exists(PUN_ROOT.'lang/'.$default_lang.'/update.php'))
+	$default_lang = 'English';
+
+require PUN_ROOT.'lang/'.$default_lang.'/common.php';
+require PUN_ROOT.'lang/'.$default_lang.'/update.php';
+
+// Check current version
+$cur_version = $pun_config['o_cur_version'];
+
+if (version_compare($cur_version, '1.2', '<'))
+	error(sprintf($lang_update['Version mismatch error'], $db_name));
+
+// Do some DB type specific checks
+$mysql = false;
+switch ($db_type)
+{
+	case 'mysql':
+	case 'mysqli':
+	case 'mysql_innodb':
+	case 'mysqli_innodb':
+		$mysql_info = $db->get_version();
+		if (version_compare($mysql_info['version'], MIN_MYSQL_VERSION, '<'))
+			error(sprintf($lang_update['You are running error'], 'MySQL', $mysql_info['version'], UPDATE_TO, MIN_MYSQL_VERSION));
+
+		$mysql = true;
+		break;
+
+	case 'pgsql':
+		$pgsql_info = $db->get_version();
+		if (version_compare($pgsql_info['version'], MIN_PGSQL_VERSION, '<'))
+			error(sprintf($lang_update['You are running error'], 'PostgreSQL', $pgsql_info['version'], UPDATE_TO, MIN_PGSQL_VERSION));
+
+		break;
+}
+
+// Check the database, search index and parser revision and the current version
+if (isset($pun_config['o_database_revision']) && $pun_config['o_database_revision'] >= UPDATE_TO_DB_REVISION &&
+		isset($pun_config['o_searchindex_revision']) && $pun_config['o_searchindex_revision'] >= UPDATE_TO_SI_REVISION &&
+		isset($pun_config['o_parser_revision']) && $pun_config['o_parser_revision'] >= UPDATE_TO_PARSER_REVISION &&
+		version_compare($pun_config['o_cur_version'], UPDATE_TO, '>='))
+	error($lang_update['No update error']);
+
+$default_style = $pun_config['o_default_style'];
+if (!file_exists(PUN_ROOT.'style/'.$default_style.'.css'))
+	$default_style = 'Air';
+
+// Start a session, used to queue up errors if duplicate users occur when converting from FluxBB v1.2.
+session_start();
+
+//
+// Determines whether $str is UTF-8 encoded or not
+//
+function seems_utf8($str)
+{
+	$str_len = strlen($str);
+	for ($i = 0; $i < $str_len; ++$i)
+	{
+		if (ord($str[$i]) < 0x80) continue; # 0bbbbbbb
+		else if ((ord($str[$i]) & 0xE0) == 0xC0) $n=1; # 110bbbbb
+		else if ((ord($str[$i]) & 0xF0) == 0xE0) $n=2; # 1110bbbb
+		else if ((ord($str[$i]) & 0xF8) == 0xF0) $n=3; # 11110bbb
+		else if ((ord($str[$i]) & 0xFC) == 0xF8) $n=4; # 111110bb
+		else if ((ord($str[$i]) & 0xFE) == 0xFC) $n=5; # 1111110b
+		else return false; # Does not match any model
+
+		for ($j = 0; $j < $n; ++$j) # n bytes matching 10bbbbbb follow ?
+		{
+			if ((++$i == strlen($str)) || ((ord($str[$i]) & 0xC0) != 0x80))
+				return false;
+		}
+	}
+
+	return true;
+}
+
+
+//
+// Translates the number from a HTML numeric entity into an UTF-8 character
+//
+function dcr2utf8($src)
+{
+	$dest = '';
+	if ($src < 0)
+		return false;
+	else if ($src <= 0x007f)
+		$dest .= chr($src);
+	else if ($src <= 0x07ff)
+	{
+		$dest .= chr(0xc0 | ($src >> 6));
+		$dest .= chr(0x80 | ($src & 0x003f));
+	}
+	else if ($src == 0xFEFF)
+	{
+		// nop -- zap the BOM
+	}
+	else if ($src >= 0xD800 && $src <= 0xDFFF)
+	{
+		// found a surrogate
+		return false;
+	}
+	else if ($src <= 0xffff)
+	{
+		$dest .= chr(0xe0 | ($src >> 12));
+		$dest .= chr(0x80 | (($src >> 6) & 0x003f));
+		$dest .= chr(0x80 | ($src & 0x003f));
+	}
+	else if ($src <= 0x10ffff)
+	{
+		$dest .= chr(0xf0 | ($src >> 18));
+		$dest .= chr(0x80 | (($src >> 12) & 0x3f));
+		$dest .= chr(0x80 | (($src >> 6) & 0x3f));
+		$dest .= chr(0x80 | ($src & 0x3f));
+	}
+	else
+	{
+		// out of range
+		return false;
+	}
+
+	return $dest;
+}
+
+
+//
+// Attempts to convert $str from $old_charset to UTF-8. Also converts HTML entities (including numeric entities) to UTF-8 characters
+//
+function convert_to_utf8(&$str, $old_charset)
+{
+	if (is_null($str) || $str == '')
+		return false;
+
+	$save = $str;
+
+	// Replace literal entities (for non-UTF-8 compliant html_entity_encode)
+	if (version_compare(PHP_VERSION, '5.0.0', '<') && $old_charset == 'ISO-8859-1' || $old_charset == 'ISO-8859-15')
+		$str = html_entity_decode($str, ENT_QUOTES, $old_charset);
+
+	if ($old_charset != 'UTF-8' && !seems_utf8($str))
+	{
+		if (function_exists('iconv'))
+			$str = iconv($old_charset == 'ISO-8859-1' ? 'WINDOWS-1252' : 'ISO-8859-1', 'UTF-8', $str);
+		else if (function_exists('mb_convert_encoding'))
+			$str = mb_convert_encoding($str, 'UTF-8', $old_charset == 'ISO-8859-1' ? 'WINDOWS-1252' : 'ISO-8859-1');
+		else if ($old_charset == 'ISO-8859-1')
+			$str = utf8_encode($str);
+	}
+
+	// Replace literal entities (for UTF-8 compliant html_entity_encode)
+	if (version_compare(PHP_VERSION, '5.0.0', '>='))
+		$str = html_entity_decode($str, ENT_QUOTES, 'UTF-8');
+
+	// Replace numeric entities
+	$str = preg_replace_callback('%&#([0-9]+);%', 'utf8_callback_1', $str);
+	$str = preg_replace_callback('%&#x([a-f0-9]+);%i', 'utf8_callback_2', $str);
+
+	// Remove "bad" characters
+	$str = remove_bad_characters($str);
+
+	return ($save != $str);
+}
+
+
+function utf8_callback_1($matches)
+{
+	return dcr2utf8($matches[1]);
+}
+
+
+function utf8_callback_2($matches)
+{
+	return dcr2utf8(hexdec($matches[1]));
+}
+
+
+//
+// Alter a table to be utf8. MySQL only
+// Function based on update_convert_table_utf8() from the Drupal project (http://drupal.org/)
+//
+function alter_table_utf8($table)
+{
+	global $mysql, $db;
+	static $types;
+
+	if (!$mysql)
+		return;
+
+	if (!isset($types))
+	{
+		$types = array(
+			'char'			=> 'binary',
+			'varchar'		=> 'varbinary',
+			'tinytext'		=> 'tinyblob',
+			'mediumtext'	=> 'mediumblob',
+			'text'			=> 'blob',
+			'longtext'		=> 'longblob'
+		);
+	}
+
+	// Set table default charset to utf8
+	$db->query('ALTER TABLE '.$table.' CHARACTER SET utf8') or error('Unable to set table character set', __FILE__, __LINE__, $db->error());
+
+	// Find out which columns need converting and build SQL statements
+	$result = $db->query('SHOW FULL COLUMNS FROM '.$table) or error('Unable to fetch column information', __FILE__, __LINE__, $db->error());
+	while ($cur_column = $db->fetch_assoc($result))
+	{
+		if (is_null($cur_column['Collation']))
+			continue;
+
+		list($type) = explode('(', $cur_column['Type']);
+		if (isset($types[$type]) && strpos($cur_column['Collation'], 'utf8') === false)
+		{
+			$allow_null = ($cur_column['Null'] == 'YES');
+			$collate = (substr($cur_column['Collation'], -3) == 'bin') ? 'utf8_bin' : 'utf8_general_ci';
+
+			$db->alter_field($table, $cur_column['Field'], preg_replace('%'.$type.'%i', $types[$type], $cur_column['Type']), $allow_null, $cur_column['Default'], null, true) or error('Unable to alter field to binary', __FILE__, __LINE__, $db->error());
+			$db->alter_field($table, $cur_column['Field'], $cur_column['Type'].' CHARACTER SET utf8 COLLATE '.$collate, $allow_null, $cur_column['Default'], null, true) or error('Unable to alter field to utf8', __FILE__, __LINE__, $db->error());
+		}
+	}
+}
+
+//
+// Safely converts text type columns into utf8
+// If finished returns true, otherwise returns $end_at
+//
+function convert_table_utf8($table, $callback, $old_charset, $key = null, $start_at = null, $error_callback = null)
+{
+	global $mysql, $db, $old_connection_charset;
+
+	$finished = true;
+	$end_at = 0;
+	if ($mysql)
+	{
+		// Only set up the tables if we are doing this in 1 go, or its the first go
+		if (is_null($start_at) || $start_at == 0)
+		{
+			// Drop any temp table that exists, in-case it's left over from a failed update
+			$db->drop_table($table.'_utf8', true) or error('Unable to drop left over temp table', __FILE__, __LINE__, $db->error());
+
+			// Copy the table
+			$db->query('CREATE TABLE '.$table.'_utf8 LIKE '.$table) or error('Unable to create new table', __FILE__, __LINE__, $db->error());
+
+			// Set table default charset to utf8
+			alter_table_utf8($table.'_utf8');
+		}
+
+		// Change to the old character set so MySQL doesn't attempt to perform conversion on the data from the old table
+		$db->set_names($old_connection_charset);
+
+		// Move & Convert everything
+		$result = $db->query('SELECT * FROM '.$table.(is_null($start_at) ? '' : ' WHERE '.$key.'>'.$start_at).' ORDER BY '.$key.' ASC'.(is_null($start_at) ? '' : ' LIMIT '.PER_PAGE), false) or error('Unable to select from old table', __FILE__, __LINE__, $db->error());
+
+		// Change back to utf8 mode so we can insert it into the new table
+		$db->set_names('utf8');
+
+		while ($cur_item = $db->fetch_assoc($result))
+		{
+			$cur_item = call_user_func($callback, $cur_item, $old_charset);
+
+			$temp = array();
+			foreach ($cur_item as $idx => $value)
+				$temp[$idx] = is_null($value) ? 'NULL' : '\''.$db->escape($value).'\'';
+
+			$db->query('INSERT INTO '.$table.'_utf8('.implode(',', array_keys($temp)).') VALUES ('.implode(',', array_values($temp)).')') or (is_null($error_callback) ? error('Unable to insert data to new table', __FILE__, __LINE__, $db->error()) : call_user_func($error_callback, $cur_item));
+
+			$end_at = $cur_item[$key];
+		}
+
+		// If we aren't doing this all in 1 go and $end_at has a value (i.e. we have processed at least 1 row), figure out if we have more to do or not
+		if (!is_null($start_at) && $end_at > 0)
+		{
+			$result = $db->query('SELECT 1 FROM '.$table.' WHERE '.$key.'>'.$end_at.' ORDER BY '.$key.' ASC LIMIT 1') or error('Unable to check for next row', __FILE__, __LINE__, $db->error());
+			$finished = $db->num_rows($result) == 0;
+		}
+
+		// Only swap the tables if we are doing this in 1 go, or its the last go
+		if ($finished)
+		{
+			// Delete old table
+			$db->drop_table($table, true) or error('Unable to drop old table', __FILE__, __LINE__, $db->error());
+
+			// Rename table
+			$db->query('ALTER TABLE '.$table.'_utf8 RENAME '.$table) or error('Unable to rename new table', __FILE__, __LINE__, $db->error());
+
+			return true;
+		}
+
+		return $end_at;
+	}
+	else
+	{
+		// Convert everything
+		$result = $db->query('SELECT * FROM '.$table.(is_null($start_at) ? '' : ' WHERE '.$key.'>'.$start_at).' ORDER BY '.$key.' ASC'.(is_null($start_at ) ? '' : ' LIMIT '.PER_PAGE)) or error('Unable to select from table', __FILE__, __LINE__, $db->error());
+		while ($cur_item = $db->fetch_assoc($result))
+		{
+			$cur_item = call_user_func($callback, $cur_item, $old_charset);
+
+			$temp = array();
+			foreach ($cur_item as $idx => $value)
+				$temp[] = $idx.'='.(is_null($value) ? 'NULL' : '\''.$db->escape($value).'\'');
+
+			if (!empty($temp))
+				$db->query('UPDATE '.$table.' SET '.implode(', ', $temp).' WHERE '.$key.'=\''.$db->escape($cur_item[$key]).'\'') or error('Unable to update data', __FILE__, __LINE__, $db->error());
+
+			$end_at = $cur_item[$key];
+		}
+
+		if (!is_null($start_at) && $end_at > 0)
+		{
+			$result = $db->query('SELECT 1 FROM '.$table.' WHERE '.$key.'>'.$end_at.' ORDER BY '.$key.' ASC LIMIT 1') or error('Unable to check for next row', __FILE__, __LINE__, $db->error());
+			if ($db->num_rows($result) == 0)
+				return true;
+
+			return $end_at;
+		}
+
+		return true;
+	}
+}
+
+
+header('Content-type: text/html; charset=utf-8');
+
+// Empty all output buffers and stop buffering
+while (@ob_end_clean());
+
+
+$stage = isset($_REQUEST['stage']) ? $_REQUEST['stage'] : '';
+$old_charset = isset($_REQUEST['req_old_charset']) ? str_replace('ISO8859', 'ISO-8859', strtoupper($_REQUEST['req_old_charset'])) : 'ISO-8859-1';
+$start_at = isset($_REQUEST['start_at']) ? intval($_REQUEST['start_at']) : 0;
+$query_str = '';
+
+// Show form
+if (empty($stage))
+{
+	if (file_exists(FORUM_CACHE_DIR.'db_update.lock'))
+	{
+		// Deal with newlines, tabs and multiple spaces
+		$pattern = array("\t", '  ', '  ');
+		$replace = array('&#160; &#160; ', '&#160; ', ' &#160;');
+		$message = str_replace($pattern, $replace, $pun_config['o_maintenance_message']);
+
+?>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php echo $lang_common['lang_identifier'] ?>" lang="<?php echo $lang_common['lang_identifier'] ?>" dir="<?php echo $lang_common['lang_direction'] ?>">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<title><?php echo $lang_update['Maintenance'] ?></title>
+<link rel="stylesheet" type="text/css" href="style/<?php echo $default_style ?>.css" />
+</head>
+<body>
+
+<div id="punmaint" class="pun">
+<div class="top-box"><div><!-- Top Corners --></div></div>
+<div class="punwrap">
+
+<div id="brdmain">
+<div class="block">
+	<h2><?php echo $lang_update['Maintenance'] ?></h2>
+	<div class="box">
+		<div class="inbox">
+			<p><?php echo $message ?></p>
+		</div>
+	</div>
+</div>
+</div>
+
+</div>
+<div class="end-box"><div><!-- Bottom Corners --></div></div>
+</div>
+
+</body>
+</html>
+<?php
+
+	}
+	else
+	{
+
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php echo $lang_common['lang_identifier'] ?>" lang="<?php echo $lang_common['lang_identifier'] ?>" dir="<?php echo $lang_common['lang_direction'] ?>">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<title><?php echo $lang_update['Update'] ?></title>
+<link rel="stylesheet" type="text/css" href="style/<?php echo $default_style ?>.css" />
+</head>
+<body onLoad="document.getElementById('install').req_db_pass.focus();document.getElementById('install').start.disabled=false;">
+
+<div id="pundb_update" class="pun">
+<div class="top-box"><div><!-- Top Corners --></div></div>
+<div class="punwrap">
+
+<div id="brdheader" class="block">
+	<div class="box">
+		<div id="brdtitle" class="inbox">
+			<h1><span><?php echo $lang_update['Update'] ?></span></h1>
+			<div id="brddesc"><p><?php echo $lang_update['Update message'] ?></p><p><strong><?php echo $lang_update['Note']; ?></strong> <?php echo $lang_update['Members message']; ?></p></div>
+		</div>
+	</div>
+</div>
+
+<div id="brdmain">
+<div class="blockform">
+	<h2><span><?php echo $lang_update['Update'] ?></span></h2>
+	<div class="box">
+		<form id="install" method="post" action="db_update.php">
+			<input type="hidden" name="stage" value="start" />
+			<div class="inform">
+				<fieldset>
+				<legend><?php echo $lang_update['Administrator only'] ?></legend>
+					<div class="infldset">
+						<p><?php echo $lang_update['Database password info'] ?></p>
+						<p><strong><?php echo $lang_update['Note']; ?></strong> <?php echo $lang_update['Database password note'] ?></p>
+						<label class="required"><strong><?php echo $lang_update['Database password'] ?> <span><?php echo $lang_update['Required'] ?></span></strong><br /><input type="password" id="req_db_pass" name="req_db_pass" /><br /></label>
+						<p><?php echo $lang_update['Maintenance message info'] ?></p>
+						<div class="txtarea">
+							<label class="required"><strong><?php echo $lang_update['Maintenance message'] ?> <span><?php echo $lang_update['Required'] ?></span></strong><br />
+							<textarea name="req_maintenance_message" rows="4" cols="65"><?php echo pun_htmlspecialchars($pun_config['o_maintenance_message']) ?></textarea><br /></label>
+						</div>
+					</div>
+				</fieldset>
+			</div>
+			<div class="inform">
+				<div class="forminfo">
+					<p><?php echo $lang_update['Intro 1'] ?></p>
+					<p><?php echo $lang_update['Intro 2'] ?></p>
+<?php
+
+	if (strpos($cur_version, '1.2') === 0)
+	{
+		if (!function_exists('iconv') && !function_exists('mb_convert_encoding'))
+		{
+
+?>
+					<p><?php echo $lang_update['No charset conversion'] ?></p>
+<?php
+
+		}
+
+?>
+				</div>
+			</div>
+			<div class="inform">
+				<div class="forminfo">
+					<p><?php echo $lang_update['Enable conversion'] ?></p>
+					<p><?php echo $lang_update['Current character set'] ?></p>
+				</div>
+				<fieldset>
+					<legend><?php echo $lang_update['Charset conversion'] ?></legend>
+					<div class="infldset">
+						<div class="rbox">
+							<label><input type="checkbox" name="convert_charset" value="1" checked="checked" /><?php echo $lang_update['Enable conversion label'] ?><br /></label>
+						</div>
+						<label>
+							<strong><?php echo $lang_update['Current character set label'] ?></strong><br /><?php echo $lang_update['Current character set info'] ?><br />
+							<input type="text" name="req_old_charset" size="12" maxlength="20" value="<?php echo $old_charset ?>" /><br />
+						</label>
+					</div>
+				</fieldset>
+<?php
+
+	}
+	else
+		echo "\t\t\t\t".'</div>'."\n";
+
+?>
+			</div>
+			<p class="buttons"><input type="submit" name="start" value="<?php echo $lang_update['Start update'] ?>" /></p>
+		</form>
+	</div>
+</div>
+</div>
+
+</div>
+<div class="end-box"><div><!-- Bottom Corners --></div></div>
+</div>
+
+</body>
+</html>
+<?php
+
+	}
+	$db->end_transaction();
+	$db->close();
+	exit;
+
+}
+
+// Read the lock file
+$lock = file_exists(FORUM_CACHE_DIR.'db_update.lock') ? trim(file_get_contents(FORUM_CACHE_DIR.'db_update.lock')) : false;
+$lock_error = false;
+
+// Generate or fetch the UID - this confirms we have a valid admin
+if (isset($_POST['req_db_pass']))
+{
+	$req_db_pass = strtolower(pun_trim($_POST['req_db_pass']));
+
+	switch ($db_type)
+	{
+		// For SQLite we compare against the database file name, since the password is left blank
+		case 'sqlite':
+			if ($req_db_pass != strtolower($db_name))
+				error(sprintf($lang_update['Invalid file error'], 'config.php'));
+
+			break;
+		// For everything else, check the password matches
+		default:
+			if ($req_db_pass != strtolower($db_password))
+				error(sprintf($lang_update['Invalid password error'], 'config.php'));
+
+			break;
+	}
+
+	// Generate a unique id to identify this session, only if this is a valid session
+	$uid = pun_hash($req_db_pass.'|'.uniqid(rand(), true));
+	if ($lock) // We already have a lock file
+		$lock_error = true;
+	else // Create the lock file
+	{
+		$fh = @fopen(FORUM_CACHE_DIR.'db_update.lock', 'wb');
+		if (!$fh)
+			error(sprintf($lang_update['Unable to lock error'], 'cache'));
+
+		fwrite($fh, $uid);
+		fclose($fh);
+
+		// Update maintenance message
+		if ($_POST['req_maintenance_message'] != '')
+			$maintenance_message = pun_trim(pun_linebreaks($_POST['req_maintenance_message']));
+		else
+		{
+			// Load the admin_options.php language file
+			require PUN_ROOT.'lang/'.$default_lang.'/admin_options.php';
+
+			$maintenance_message = $lang_admin_options['Default maintenance message'];
+		}
+
+		$db->query('UPDATE '.$db->prefix.'config SET conf_value=\''.$db->escape($maintenance_message).'\' WHERE conf_name=\'o_maintenance_message\'') or error('Unable to update board config', __FILE__, __LINE__, $db->error());
+
+		// Regenerate the config cache
+		if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+			require PUN_ROOT.'include/cache.php';
+
+		generate_config_cache();
+	}
+}
+else if (isset($_GET['uid']))
+{
+	$uid = pun_trim($_GET['uid']);
+	if (!$lock || $lock != $uid) // The lock doesn't exist or doesn't match the given UID
+		$lock_error = true;
+}
+else
+	error($lang_update['No password error']);
+
+// If there is an error with the lock file
+if ($lock_error)
+	error(sprintf($lang_update['Script runs error'], FORUM_CACHE_DIR.'db_update.lock'));
+
+switch ($stage)
+{
+	// Start by updating the database structure
+	case 'start':
+		$query_str = '?stage=preparse_posts';
+
+		// If we don't need to update the database, skip this stage
+		if (isset($pun_config['o_database_revision']) && $pun_config['o_database_revision'] >= UPDATE_TO_DB_REVISION)
+			break;
+
+		// Make all email fields VARCHAR(80)
+		$db->alter_field('bans', 'email', 'VARCHAR(80)', true) or error('Unable to alter email field', __FILE__, __LINE__, $db->error());
+		$db->alter_field('posts', 'poster_email', 'VARCHAR(80)', true) or error('Unable to alter poster_email field', __FILE__, __LINE__, $db->error());
+		$db->alter_field('users', 'email', 'VARCHAR(80)', false, '') or error('Unable to alter email field', __FILE__, __LINE__, $db->error());
+		$db->alter_field('users', 'jabber', 'VARCHAR(80)', true) or error('Unable to alter jabber field', __FILE__, __LINE__, $db->error());
+		$db->alter_field('users', 'msn', 'VARCHAR(80)', true) or error('Unable to alter msn field', __FILE__, __LINE__, $db->error());
+		$db->alter_field('users', 'activate_string', 'VARCHAR(80)', true) or error('Unable to alter activate_string field', __FILE__, __LINE__, $db->error());
+
+		// Make all IP fields VARCHAR(39) to support IPv6
+		$db->alter_field('posts', 'poster_ip', 'VARCHAR(39)', true) or error('Unable to alter poster_ip field', __FILE__, __LINE__, $db->error());
+		$db->alter_field('users', 'registration_ip', 'VARCHAR(39)', false, '0.0.0.0') or error('Unable to alter registration_ip field', __FILE__, __LINE__, $db->error());
+
+		// Make the message field MEDIUMTEXT to allow proper conversion of 65535 character posts to UTF-8
+		$db->alter_field('posts', 'message', 'MEDIUMTEXT', true) or error('Unable to alter message field', __FILE__, __LINE__, $db->error());
+
+		// Add the DST option to the users table
+		$db->add_field('users', 'dst', 'TINYINT(1)', false, 0, 'timezone') or error('Unable to add dst field', __FILE__, __LINE__, $db->error());
+
+		// Add the last_post field to the online table
+		$db->add_field('online', 'last_post', 'INT(10) UNSIGNED', true, null, null) or error('Unable to add last_post field', __FILE__, __LINE__, $db->error());
+
+		// Add the last_search field to the online table
+		$db->add_field('online', 'last_search', 'INT(10) UNSIGNED', true, null, null) or error('Unable to add last_search field', __FILE__, __LINE__, $db->error());
+
+		// Add the last_search column to the users table
+		$db->add_field('users', 'last_search', 'INT(10) UNSIGNED', true, null, 'last_post') or error('Unable to add last_search field', __FILE__, __LINE__, $db->error());
+
+		// Drop use_avatar column from users table
+		$db->drop_field('users', 'use_avatar') or error('Unable to drop use_avatar field', __FILE__, __LINE__, $db->error());
+
+		// Drop save_pass column from users table
+		$db->drop_field('users', 'save_pass') or error('Unable to drop save_pass field', __FILE__, __LINE__, $db->error());
+
+		// Drop g_edit_subjects_interval column from groups table
+		$db->drop_field('groups', 'g_edit_subjects_interval');
+
+		// Add database revision number
+		if (!array_key_exists('o_database_revision', $pun_config))
+			$db->query('INSERT INTO '.$db->prefix.'config (conf_name, conf_value) VALUES (\'o_database_revision\', \'0\')') or error('Unable to insert config value \'o_database_revision\'', __FILE__, __LINE__, $db->error());
+
+		// Add search index revision number
+		if (!array_key_exists('o_searchindex_revision', $pun_config))
+			$db->query('INSERT INTO '.$db->prefix.'config (conf_name, conf_value) VALUES (\'o_searchindex_revision\', \'0\')') or error('Unable to insert config value \'o_searchindex_revision\'', __FILE__, __LINE__, $db->error());
+
+		// Add parser revision number
+		if (!array_key_exists('o_parser_revision', $pun_config))
+			$db->query('INSERT INTO '.$db->prefix.'config (conf_name, conf_value) VALUES (\'o_parser_revision\', \'0\')') or error('Unable to insert config value \'o_parser_revision\'', __FILE__, __LINE__, $db->error());
+
+		// Add default email setting option
+		if (!array_key_exists('o_default_email_setting', $pun_config))
+			$db->query('INSERT INTO '.$db->prefix.'config (conf_name, conf_value) VALUES (\'o_default_email_setting\', \'1\')') or error('Unable to insert config value \'o_default_email_setting\'', __FILE__, __LINE__, $db->error());
+
+		// Make sure we have o_additional_navlinks (was added in 1.2.1)
+		if (!array_key_exists('o_additional_navlinks', $pun_config))
+			$db->query('INSERT INTO '.$db->prefix.'config (conf_name, conf_value) VALUES (\'o_additional_navlinks\', \'\')') or error('Unable to insert config value \'o_additional_navlinks\'', __FILE__, __LINE__, $db->error());
+
+		// Insert new config option o_topic_views
+		if (!array_key_exists('o_topic_views', $pun_config))
+			$db->query('INSERT INTO '.$db->prefix.'config (conf_name, conf_value) VALUES (\'o_topic_views\', \'1\')') or error('Unable to insert config value \'o_topic_views\'', __FILE__, __LINE__, $db->error());
+
+		// Insert new config option o_signatures
+		if (!array_key_exists('o_signatures', $pun_config))
+			$db->query('INSERT INTO '.$db->prefix.'config (conf_name, conf_value) VALUES (\'o_signatures\', \'1\')') or error('Unable to insert config value \'o_signatures\'', __FILE__, __LINE__, $db->error());
+
+		// Insert new config option o_smtp_ssl
+		if (!array_key_exists('o_smtp_ssl', $pun_config))
+			$db->query('INSERT INTO '.$db->prefix.'config (conf_name, conf_value) VALUES (\'o_smtp_ssl\', \'0\')') or error('Unable to insert config value \'o_smtp_ssl\'', __FILE__, __LINE__, $db->error());
+
+		// Insert new config option o_default_dst
+		if (!array_key_exists('o_default_dst', $pun_config))
+			$db->query('INSERT INTO '.$db->prefix.'config (conf_name, conf_value) VALUES (\'o_default_dst\', \'0\')') or error('Unable to insert config value \'o_default_dst\'', __FILE__, __LINE__, $db->error());
+
+		// Insert new config option o_quote_depth
+		if (!array_key_exists('o_quote_depth', $pun_config))
+			$db->query('INSERT INTO '.$db->prefix.'config (conf_name, conf_value) VALUES (\'o_quote_depth\', \'3\')') or error('Unable to insert config value \'o_quote_depth\'', __FILE__, __LINE__, $db->error());
+
+		// Insert new config option o_feed_type
+		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))
+		{
+			// If it isn't in $pun_config['o_base_url'] it should be in $base_url, but just in-case it isn't we can make a guess at it
+			if (!isset($base_url))
+			{
+				// Make an educated guess regarding base_url
+				$base_url  = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://';	// protocol
+				$base_url .= preg_replace('%:(80|443)$%', '', $_SERVER['HTTP_HOST']);							// host[:port]
+				$base_url .= str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME']));							// path
+			}
+
+			if (substr($base_url, -1) == '/')
+				$base_url = substr($base_url, 0, -1);
+
+			$db->query('INSERT INTO '.$db->prefix.'config (conf_name, conf_value) VALUES (\'o_base_url\', \''.$db->escape($base_url).'\')') or error('Unable to insert config value \'o_quote_depth\'', __FILE__, __LINE__, $db->error());
+		}
+
+		if (strpos($cur_version, '1.2') === 0)
+		{
+			// Groups are almost the same as 1.2:
+			// unverified:	32000 -> 0
+
+			$db->query('UPDATE '.$db->prefix.'users SET group_id = 0 WHERE group_id = 32000') or error('Unable to update unverified users', __FILE__, __LINE__, $db->error());
+		}
+		else if (strpos($cur_version, '1.3') === 0)
+		{
+			// Groups have changed quite a lot from 1.3:
+			// unverified:	0 -> 0
+			// admin:		1 -> 1
+			// mod:			? -> 2
+			// guest:		2 -> 3
+			// member:		? -> 4
+
+			$result = $db->query('SELECT MAX(g_id) + 1 FROM '.$db->prefix.'groups') or error('Unable to select temp group ID', __FILE__, __LINE__, $db->error());
+			$temp_id = $db->result($result);
+
+			$result = $db->query('SELECT g_id FROM '.$db->prefix.'groups WHERE g_moderator = 1 AND g_id > 1 LIMIT 1') or error('Unable to select moderator group', __FILE__, __LINE__, $db->error());
+			if ($db->num_rows($result))
+				$mod_gid = $db->result($result);
+			else
+			{
+				$db->query('INSERT INTO '.$db->prefix.'groups (g_title, g_user_title, g_moderator, g_mod_edit_users, g_mod_rename_users, g_mod_change_passwords, g_mod_ban_users, g_read_board, g_view_users, g_post_replies, g_post_topics, g_edit_posts, g_delete_posts, g_delete_topics, g_set_title, g_search, g_search_users, g_send_email, g_post_flood, g_search_flood, g_email_flood, g_report_flood) VALUES('."'Moderators', 'Moderator', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0)") or error('Unable to add group', __FILE__, __LINE__, $db->error());
+				$mod_gid = $db->insert_id();
+			}
+
+			$member_gid = $pun_config['o_default_user_group'];
+
+			// move the mod group to a temp place
+			$db->query('UPDATE '.$db->prefix.'groups SET g_id = '.$temp_id.' WHERE g_id = '.$mod_gid) or error('Unable to update group ID', __FILE__, __LINE__, $db->error());
+			$db->query('UPDATE '.$db->prefix.'users SET group_id = '.$temp_id.' WHERE group_id = '.$mod_gid) or error('Unable to update users group ID', __FILE__, __LINE__, $db->error());
+			$db->query('UPDATE '.$db->prefix.'forum_perms SET group_id = '.$temp_id.' WHERE group_id = '.$mod_gid) or error('Unable to forum_perms group ID', __FILE__, __LINE__, $db->error());
+			if ($member_gid == $mod_gid) $member_gid = $temp_id;
+
+			// move whoever is in 3 to a spare slot
+			$db->query('UPDATE '.$db->prefix.'groups SET g_id = '.$mod_gid.' WHERE g_id = 3') or error('Unable to update group ID', __FILE__, __LINE__, $db->error());
+			$db->query('UPDATE '.$db->prefix.'users SET group_id = '.$mod_gid.' WHERE group_id = 3') or error('Unable to update users group ID', __FILE__, __LINE__, $db->error());
+			$db->query('UPDATE '.$db->prefix.'forum_perms SET group_id = '.$mod_gid.' WHERE group_id = 3') or error('Unable to forum_perms group ID', __FILE__, __LINE__, $db->error());
+			if ($member_gid == 3) $member_gid = $mod_gid;
+
+			// move guest to 3
+			$db->query('UPDATE '.$db->prefix.'groups SET g_id = 3 WHERE g_id = 2') or error('Unable to update group ID', __FILE__, __LINE__, $db->error());
+			$db->query('UPDATE '.$db->prefix.'users SET group_id = 3 WHERE group_id = 2') or error('Unable to update users group ID', __FILE__, __LINE__, $db->error());
+			$db->query('UPDATE '.$db->prefix.'forum_perms SET group_id = 3 WHERE group_id = 2') or error('Unable to forum_perms group ID', __FILE__, __LINE__, $db->error());
+			if ($member_gid == 2) $member_gid = 3;
+
+			// move mod group in temp place to 2
+			$db->query('UPDATE '.$db->prefix.'groups SET g_id = 2 WHERE g_id = '.$temp_id) or error('Unable to update group ID', __FILE__, __LINE__, $db->error());
+			$db->query('UPDATE '.$db->prefix.'users SET group_id = 2 WHERE group_id = '.$temp_id) or error('Unable to update users group ID', __FILE__, __LINE__, $db->error());
+			$db->query('UPDATE '.$db->prefix.'forum_perms SET group_id = 2 WHERE group_id = '.$temp_id) or error('Unable to forum_perms group ID', __FILE__, __LINE__, $db->error());
+			if ($member_gid == $temp_id) $member_gid = 2;
+
+			// Only move stuff around if it isn't already in the right place
+			if ($member_gid != $mod_gid || $member_gid != 4)
+			{
+				// move members to temp place
+				$db->query('UPDATE '.$db->prefix.'groups SET g_id = '.$temp_id.' WHERE g_id = '.$member_gid) or error('Unable to update group ID', __FILE__, __LINE__, $db->error());
+				$db->query('UPDATE '.$db->prefix.'users SET group_id = '.$temp_id.' WHERE group_id = '.$member_gid) or error('Unable to update users group ID', __FILE__, __LINE__, $db->error());
+				$db->query('UPDATE '.$db->prefix.'forum_perms SET group_id = '.$temp_id.' WHERE group_id = '.$member_gid) or error('Unable to forum_perms group ID', __FILE__, __LINE__, $db->error());
+
+				// move whoever is in 4 to members place
+				$db->query('UPDATE '.$db->prefix.'groups SET g_id = '.$member_gid.' WHERE g_id = 4') or error('Unable to update group ID', __FILE__, __LINE__, $db->error());
+				$db->query('UPDATE '.$db->prefix.'users SET group_id = '.$member_gid.' WHERE group_id = 4') or error('Unable to update users group ID', __FILE__, __LINE__, $db->error());
+				$db->query('UPDATE '.$db->prefix.'forum_perms SET group_id = '.$member_gid.' WHERE group_id = 4') or error('Unable to forum_perms group ID', __FILE__, __LINE__, $db->error());
+
+				// move members in temp place to 4
+				$db->query('UPDATE '.$db->prefix.'groups SET g_id = 4 WHERE g_id = '.$temp_id) or error('Unable to update group ID', __FILE__, __LINE__, $db->error());
+				$db->query('UPDATE '.$db->prefix.'users SET group_id = 4 WHERE group_id = '.$temp_id) or error('Unable to update users group ID', __FILE__, __LINE__, $db->error());
+				$db->query('UPDATE '.$db->prefix.'forum_perms SET group_id = 4 WHERE group_id = '.$temp_id) or error('Unable to forum_perms group ID', __FILE__, __LINE__, $db->error());
+			}
+
+			$db->query('UPDATE '.$db->prefix.'config SET conf_value=\''.$member_gid.'\' WHERE conf_name=\'o_default_user_group\'') or error('Unable to update default user group ID', __FILE__, __LINE__, $db->error());
+		}
+
+		// Server time zone is now simply the default time zone
+		if (!array_key_exists('o_default_timezone', $pun_config))
+			$db->query('UPDATE '.$db->prefix.'config SET conf_name = \'o_default_timezone\' WHERE conf_name = \'o_server_timezone\'') or error('Unable to update time zone config', __FILE__, __LINE__, $db->error());
+
+		// Increase visit timeout to 30 minutes (only if it hasn't been changed from the default)
+		if (!array_key_exists('o_database_revision', $pun_config) && $pun_config['o_timeout_visit'] == '600')
+			$db->query('UPDATE '.$db->prefix.'config SET conf_value = \'1800\' WHERE conf_name = \'o_timeout_visit\'') or error('Unable to update visit timeout config', __FILE__, __LINE__, $db->error());
+
+		// Remove obsolete g_post_polls permission from groups table
+		$db->drop_field('groups', 'g_post_polls');
+
+		// Make room for multiple moderator groups
+		if (!$db->field_exists('groups', 'g_moderator'))
+		{
+			// Add g_moderator column to groups table
+			$db->add_field('groups', 'g_moderator', 'TINYINT(1)', false, 0, 'g_user_title') or error('Unable to add g_moderator field', __FILE__, __LINE__, $db->error());
+
+			// Give the moderator group moderator privileges
+			$db->query('UPDATE '.$db->prefix.'groups SET g_moderator = 1 WHERE g_id = 2') or error('Unable to update moderator powers', __FILE__, __LINE__, $db->error());
+		}
+
+		// Replace obsolete p_mod_edit_users config setting with new per-group permission
+		if (array_key_exists('p_mod_edit_users', $pun_config))
+		{
+			$db->query('DELETE FROM '.$db->prefix.'config WHERE conf_name = \'p_mod_edit_users\'') or error('Unable to update moderator powers', __FILE__, __LINE__, $db->error());
+
+			$db->add_field('groups', 'g_mod_edit_users', 'TINYINT(1)', false, 0, 'g_moderator') or error('Unable to add g_mod_edit_users field', __FILE__, __LINE__, $db->error());
+
+			$db->query('UPDATE '.$db->prefix.'groups SET g_mod_edit_users = '.$pun_config['p_mod_edit_users'].' WHERE g_moderator = 1') or error('Unable to update moderator powers', __FILE__, __LINE__, $db->error());
+		}
+
+		// Replace obsolete p_mod_rename_users config setting with new per-group permission
+		if (array_key_exists('p_mod_rename_users', $pun_config))
+		{
+			$db->query('DELETE FROM '.$db->prefix.'config WHERE conf_name = \'p_mod_rename_users\'') or error('Unable to update moderator powers', __FILE__, __LINE__, $db->error());
+
+			$db->add_field('groups', 'g_mod_rename_users', 'TINYINT(1)', false, 0, 'g_mod_edit_users') or error('Unable to add g_mod_rename_users field', __FILE__, __LINE__, $db->error());
+
+			$db->query('UPDATE '.$db->prefix.'groups SET g_mod_rename_users = '.$pun_config['p_mod_rename_users'].' WHERE g_moderator = 1') or error('Unable to update moderator powers', __FILE__, __LINE__, $db->error());
+		}
+
+		// Replace obsolete p_mod_change_passwords config setting with new per-group permission
+		if (array_key_exists('p_mod_change_passwords', $pun_config))
+		{
+			$db->query('DELETE FROM '.$db->prefix.'config WHERE conf_name = \'p_mod_change_passwords\'') or error('Unable to update moderator powers', __FILE__, __LINE__, $db->error());
+
+			$db->add_field('groups', 'g_mod_change_passwords', 'TINYINT(1)', false, 0, 'g_mod_rename_users') or error('Unable to add g_mod_change_passwords field', __FILE__, __LINE__, $db->error());
+
+			$db->query('UPDATE '.$db->prefix.'groups SET g_mod_change_passwords = '.$pun_config['p_mod_change_passwords'].' WHERE g_moderator = 1') or error('Unable to update moderator powers', __FILE__, __LINE__, $db->error());
+		}
+
+		// Replace obsolete p_mod_ban_users config setting with new per-group permission
+		if (array_key_exists('p_mod_ban_users', $pun_config))
+		{
+			$db->query('DELETE FROM '.$db->prefix.'config WHERE conf_name = \'p_mod_ban_users\'') or error('Unable to update moderator powers', __FILE__, __LINE__, $db->error());
+
+			$db->add_field('groups', 'g_mod_ban_users', 'TINYINT(1)', false, 0, 'g_mod_change_passwords') or error('Unable to add g_mod_ban_users field', __FILE__, __LINE__, $db->error());
+
+			$db->query('UPDATE '.$db->prefix.'groups SET g_mod_ban_users = '.$pun_config['p_mod_ban_users'].' WHERE g_moderator = 1') or error('Unable to update moderator powers', __FILE__, __LINE__, $db->error());
+		}
+
+		// We need to add a unique index to avoid users having multiple rows in the online table
+		if (!$db->index_exists('online', 'user_id_ident_idx'))
+		{
+			$db->truncate_table('online') or error('Unable to clear online table', __FILE__, __LINE__, $db->error());
+
+			if ($mysql)
+				$db->add_index('online', 'user_id_ident_idx', array('user_id', 'ident(25)'), true) or error('Unable to add user_id_ident_idx index', __FILE__, __LINE__, $db->error());
+			else
+				$db->add_index('online', 'user_id_ident_idx', array('user_id', 'ident'), true) or error('Unable to add user_id_ident_idx index', __FILE__, __LINE__, $db->error());
+		}
+
+		// Remove the redundant user_id_idx on the online table
+		$db->drop_index('online', 'user_id_idx') or error('Unable to drop user_id_idx index', __FILE__, __LINE__, $db->error());
+
+		// Add an index to ident on the online table
+		if ($mysql)
+			$db->add_index('online', 'ident_idx', array('ident(25)')) or error('Unable to add ident_idx index', __FILE__, __LINE__, $db->error());
+		else
+			$db->add_index('online', 'ident_idx', array('ident')) or error('Unable to add ident_idx index', __FILE__, __LINE__, $db->error());
+
+		// Add an index to logged in the online table
+		$db->add_index('online', 'logged_idx', array('logged')) or error('Unable to add logged_idx index', __FILE__, __LINE__, $db->error());
+
+		// Add an index to last_post in the topics table
+		$db->add_index('topics', 'last_post_idx', array('last_post')) or error('Unable to add last_post_idx index', __FILE__, __LINE__, $db->error());
+
+		// Add an index to username on the bans table
+		if ($mysql)
+			$db->add_index('bans', 'username_idx', array('username(25)')) or error('Unable to add username_idx index', __FILE__, __LINE__, $db->error());
+		else
+			$db->add_index('bans', 'username_idx', array('username')) or error('Unable to add username_idx index', __FILE__, __LINE__, $db->error());
+
+		// Change the username_idx on users to a unique index of max size 25
+		$db->drop_index('users', 'username_idx') or error('Unable to drop old username_idx index', __FILE__, __LINE__, $db->error());
+		$field = $mysql ? 'username(25)' : 'username';
+
+		// Attempt to add a unique index. If the user doesn't use a transactional database this can fail due to multiple matching usernames in the
+		// users table. This is bad, but just giving up if it happens is even worse! If it fails just add a regular non-unique index.
+		if (!$db->add_index('users', 'username_idx', array($field), true))
+			$db->add_index('users', 'username_idx', array($field)) or error('Unable to add username_idx field', __FILE__, __LINE__, $db->error());
+
+		// Add g_view_users field to groups table
+		$db->add_field('groups', 'g_view_users', 'TINYINT(1)', false, 1, 'g_read_board') or error('Unable to add g_view_users field', __FILE__, __LINE__, $db->error());
+
+		// Add the last_email_sent column to the users table and the g_send_email and
+		// g_email_flood columns to the groups table
+		$db->add_field('users', 'last_email_sent', 'INT(10) UNSIGNED', true, null, 'last_search') or error('Unable to add last_email_sent field', __FILE__, __LINE__, $db->error());
+		$db->add_field('groups', 'g_send_email', 'TINYINT(1)', false, 1, 'g_search_users') or error('Unable to add g_send_email field', __FILE__, __LINE__, $db->error());
+		$db->add_field('groups', 'g_email_flood', 'SMALLINT(6)', false, 60, 'g_search_flood') or error('Unable to add g_email_flood field', __FILE__, __LINE__, $db->error());
+
+		// Add the last_report_sent column to the users table and the g_report_flood
+		// column to the groups table
+		$db->add_field('users', 'last_report_sent', 'INT(10) UNSIGNED', true, null, 'last_email_sent') or error('Unable to add last_report_sent field', __FILE__, __LINE__, $db->error());
+		$db->add_field('groups', 'g_report_flood', 'SMALLINT(6)', false, 60, 'g_email_flood') or error('Unable to add g_report_flood field', __FILE__, __LINE__, $db->error());
+
+		// Set non-default g_send_email, g_flood_email and g_flood_report values properly
+		$db->query('UPDATE '.$db->prefix.'groups SET g_send_email = 0 WHERE g_id = 3') or error('Unable to update group email permissions', __FILE__, __LINE__, $db->error());
+		$db->query('UPDATE '.$db->prefix.'groups SET g_email_flood = 0, g_report_flood = 0 WHERE g_id IN (1,2,3)') or error('Unable to update group email permissions', __FILE__, __LINE__, $db->error());
+
+		// Add the auto notify/subscription option to the users table
+		$db->add_field('users', 'auto_notify', 'TINYINT(1)', false, 0, 'notify_with_post') or error('Unable to add auto_notify field', __FILE__, __LINE__, $db->error());
+
+		// Add the first_post_id column to the topics table
+		if (!$db->field_exists('topics', 'first_post_id'))
+		{
+			$db->add_field('topics', 'first_post_id', 'INT(10) UNSIGNED', false, 0, 'posted') or error('Unable to add first_post_id field', __FILE__, __LINE__, $db->error());
+			$db->add_index('topics', 'first_post_id_idx', array('first_post_id')) or error('Unable to add first_post_id_idx index', __FILE__, __LINE__, $db->error());
+
+			// Now that we've added the column and indexed it, we need to give it correct data
+			$result = $db->query('SELECT MIN(id) AS first_post, topic_id FROM '.$db->prefix.'posts GROUP BY topic_id') or error('Unable to fetch first_post_id', __FILE__, __LINE__, $db->error());
+
+			while ($cur_post = $db->fetch_assoc($result))
+				$db->query('UPDATE '.$db->prefix.'topics SET first_post_id = '.$cur_post['first_post'].' WHERE id = '.$cur_post['topic_id']) or error('Unable to update first_post_id', __FILE__, __LINE__, $db->error());
+		}
+
+		// Move any users with the old unverified status to their new group
+		$db->query('UPDATE '.$db->prefix.'users SET group_id=0 WHERE group_id=32000') or error('Unable to move unverified users', __FILE__, __LINE__, $db->error());
+
+		// Add the ban_creator column to the bans table
+		$db->add_field('bans', 'ban_creator', 'INT(10) UNSIGNED', false, 0) or error('Unable to add ban_creator field', __FILE__, __LINE__, $db->error());
+
+		// Add the time/date format settings to the user table
+		$db->add_field('users', 'time_format', 'TINYINT(1)', false, 0, 'dst') or error('Unable to add time_format field', __FILE__, __LINE__, $db->error());
+		$db->add_field('users', 'date_format', 'TINYINT(1)', false, 0, 'dst') or error('Unable to add date_format field', __FILE__, __LINE__, $db->error());
+
+		// Change the search_data field to mediumtext
+		$db->alter_field('search_cache', 'search_data', 'MEDIUMTEXT', true) or error('Unable to alter search_data field', __FILE__, __LINE__, $db->error());
+
+		// Incase we had the fulltext search extension installed (1.3-legacy), remove it
+		$db->drop_index('topics', 'subject_idx') or error('Unable to drop subject_idx index', __FILE__, __LINE__, $db->error());
+		$db->drop_index('posts', 'message_idx') or error('Unable to drop message_idx index', __FILE__, __LINE__, $db->error());
+		// Incase we had the fulltext search mod installed (1.2), remove it
+		$db->drop_index('topics', 'subject_fulltext_search') or error('Unable to drop subject_fulltext_search index', __FILE__, __LINE__, $db->error());
+		$db->drop_index('posts', 'message_fulltext_search') or error('Unable to drop message_fulltext_search index', __FILE__, __LINE__, $db->error());
+
+		// If the search_cache table has been dropped by the fulltext search extension, recreate it
+		if (!$db->table_exists('search_cache'))
+		{
+			$schema = array(
+				'FIELDS'		=> array(
+					'id'			=> array(
+						'datatype'		=> 'INT(10) UNSIGNED',
+						'allow_null'	=> false,
+						'default'		=> '0'
+					),
+					'ident'			=> array(
+						'datatype'		=> 'VARCHAR(200)',
+						'allow_null'	=> false,
+						'default'		=> '\'\''
+					),
+					'search_data'	=> array(
+						'datatype'		=> 'MEDIUMTEXT',
+						'allow_null'	=> true
+					)
+				),
+				'PRIMARY KEY'	=> array('id'),
+				'INDEXES'		=> array(
+					'ident_idx'	=> array('ident')
+				)
+			);
+
+			if ($db_type == 'mysql' || $db_type == 'mysqli' || $db_type == 'mysql_innodb' || $db_type == 'mysqli_innodb')
+				$schema['INDEXES']['ident_idx'] = array('ident(8)');
+
+			$db->create_table('search_cache', $schema);
+		}
+
+		// If the search_matches table has been dropped by the fulltext search extension, recreate it
+		if (!$db->table_exists('search_matches'))
+		{
+			$schema = array(
+				'FIELDS'		=> array(
+					'post_id'		=> array(
+						'datatype'		=> 'INT(10) UNSIGNED',
+						'allow_null'	=> false,
+						'default'		=> '0'
+					),
+					'word_id'		=> array(
+						'datatype'		=> 'INT(10) UNSIGNED',
+						'allow_null'	=> false,
+						'default'		=> '0'
+					),
+					'subject_match'	=> array(
+						'datatype'		=> 'TINYINT(1)',
+						'allow_null'	=> false,
+						'default'		=> '0'
+					)
+				),
+				'INDEXES'		=> array(
+					'word_id_idx'	=> array('word_id'),
+					'post_id_idx'	=> array('post_id')
+				)
+			);
+
+			$db->create_table('search_matches', $schema);
+		}
+
+		// If the search_words table has been dropped by the fulltext search extension, recreate it
+		if (!$db->table_exists('search_words'))
+		{
+			$schema = array(
+				'FIELDS'		=> array(
+					'id'			=> array(
+						'datatype'		=> 'SERIAL',
+						'allow_null'	=> false
+					),
+					'word'			=> array(
+						'datatype'		=> 'VARCHAR(20)',
+						'allow_null'	=> false,
+						'default'		=> '\'\'',
+						'collation'		=> 'bin'
+					)
+				),
+				'PRIMARY KEY'	=> array('word'),
+				'INDEXES'		=> array(
+					'id_idx'	=> array('id')
+				)
+			);
+
+			if ($db_type == 'sqlite')
+			{
+				$schema['PRIMARY KEY'] = array('id');
+				$schema['UNIQUE KEYS'] = array('word_idx'	=> array('word'));
+			}
+
+			$db->create_table('search_words', $schema);
+		}
+
+		// Rename the subscription table
+		$db->rename_table('subscriptions', 'topic_subscriptions');
+
+		// if we don't have the forum_subscriptions table, create it
+		if (!$db->table_exists('forum_subscriptions'))
+		{
+			$schema = array(
+				'FIELDS'		=> array(
+					'user_id'		=> array(
+						'datatype'		=> 'INT(10) UNSIGNED',
+						'allow_null'	=> false,
+						'default'		=> '0'
+					),
+					'forum_id'		=> array(
+						'datatype'		=> 'INT(10) UNSIGNED',
+						'allow_null'	=> false,
+						'default'		=> '0'
+					)
+				),
+				'PRIMARY KEY'	=> array('user_id', 'forum_id')
+			);
+
+			$db->create_table('forum_subscriptions', $schema) or error('Unable to create forum subscriptions table', __FILE__, __LINE__, $db->error());
+		}
+
+		// Insert new config option o_forum_subscriptions
+		if (!array_key_exists('o_forum_subscriptions', $pun_config))
+			$db->query('INSERT INTO '.$db->prefix.'config (conf_name, conf_value) VALUES (\'o_forum_subscriptions\', \'1\')') or error('Unable to insert config value \'o_forum_subscriptions\'', __FILE__, __LINE__, $db->error());
+
+		// Rename config option o_subscriptions to o_topic_subscriptions
+		if (!array_key_exists('o_topic_subscriptions', $pun_config))
+			$db->query('UPDATE '.$db->prefix.'config SET conf_name=\'o_topic_subscriptions\' WHERE conf_name=\'o_subscriptions\'') or error('Unable to rename config value \'o_subscriptions\'', __FILE__, __LINE__, $db->error());
+
+		// Change the default style if the old doesn't exist anymore
+		if ($pun_config['o_default_style'] != $default_style)
+			$db->query('UPDATE '.$db->prefix.'config SET conf_value = \''.$db->escape($default_style).'\' WHERE conf_name = \'o_default_style\'') or error('Unable to update default style config', __FILE__, __LINE__, $db->error());
+
+		// For MySQL(i) without InnoDB, change the engine of the online table (for performance reasons)
+		if ($db_type == 'mysql' || $db_type == 'mysqli')
+			$db->query('ALTER TABLE '.$db->prefix.'online ENGINE = MyISAM') or error('Unable to change engine type of online table to MyISAM', __FILE__, __LINE__, $db->error());
+
+		// Should we do charset conversion or not?
+		if (strpos($cur_version, '1.2') === 0 && isset($_POST['convert_charset']))
+			$query_str = '?stage=conv_bans&req_old_charset='.$old_charset;
+
+		break;
+
+
+	// Convert bans
+	case 'conv_bans':
+		$query_str = '?stage=conv_categories&req_old_charset='.$old_charset;
+
+		function _conv_bans($cur_item, $old_charset)
+		{
+			global $lang_update;
+
+			echo sprintf($lang_update['Converting item'], $lang_update['ban'], $cur_item['id']).'<br />'."\n";
+
+			convert_to_utf8($cur_item['username'], $old_charset);
+			convert_to_utf8($cur_item['message'], $old_charset);
+
+			return $cur_item;
+		}
+
+		$end_at = convert_table_utf8($db->prefix.'bans', '_conv_bans', $old_charset, 'id', $start_at);
+
+		if ($end_at !== true)
+			$query_str = '?stage=conv_bans&req_old_charset='.$old_charset.'&start_at='.$end_at;
+
+		break;
+
+
+	// Convert categories
+	case 'conv_categories':
+		$query_str = '?stage=conv_censors&req_old_charset='.$old_charset;
+
+		echo sprintf($lang_update['Converting'], $lang_update['categories']).'<br />'."\n";
+
+		function _conv_categories($cur_item, $old_charset)
+		{
+			convert_to_utf8($cur_item['cat_name'], $old_charset);
+
+			return $cur_item;
+		}
+
+		convert_table_utf8($db->prefix.'categories', '_conv_categories', $old_charset, 'id');
+
+		break;
+
+
+	// Convert censor words
+	case 'conv_censors':
+		$query_str = '?stage=conv_config&req_old_charset='.$old_charset;
+
+		echo sprintf($lang_update['Converting'], $lang_update['censor words']).'<br />'."\n";
+
+		function _conv_censoring($cur_item, $old_charset)
+		{
+			convert_to_utf8($cur_item['search_for'], $old_charset);
+			convert_to_utf8($cur_item['replace_with'], $old_charset);
+
+			return $cur_item;
+		}
+
+		convert_table_utf8($db->prefix.'censoring', '_conv_censoring', $old_charset, 'id');
+
+		break;
+
+
+	// Convert config
+	case 'conv_config':
+		$query_str = '?stage=conv_forums&req_old_charset='.$old_charset;
+
+		echo sprintf($lang_update['Converting'], $lang_update['configuration']).'<br />'."\n";
+
+		function _conv_config($cur_item, $old_charset)
+		{
+			convert_to_utf8($cur_item['conf_value'], $old_charset);
+
+			return $cur_item;
+		}
+
+		convert_table_utf8($db->prefix.'config', '_conv_config', $old_charset, 'conf_name');
+
+		break;
+
+
+	// Convert forums
+	case 'conv_forums':
+		$query_str = '?stage=conv_perms&req_old_charset='.$old_charset;
+
+		echo sprintf($lang_update['Converting'], $lang_update['forums']).'<br />'."\n";
+
+		function _conv_forums($cur_item, $old_charset)
+		{
+			$moderators = ($cur_item['moderators'] != '') ? unserialize($cur_item['moderators']) : array();
+			$moderators_utf8 = array();
+			foreach ($moderators as $mod_username => $mod_user_id)
+			{
+				convert_to_utf8($mod_username, $old_charset);
+				$moderators_utf8[$mod_username] = $mod_user_id;
+			}
+
+			convert_to_utf8($cur_item['forum_name'], $old_charset);
+			convert_to_utf8($cur_item['forum_desc'], $old_charset);
+
+			if (!empty($moderators_utf8))
+				$cur_item['moderators'] = serialize($moderators_utf8);
+
+			return $cur_item;
+		}
+
+		convert_table_utf8($db->prefix.'forums', '_conv_forums', $old_charset, 'id');
+
+		break;
+
+
+	// Convert forum permissions
+	case 'conv_perms':
+		$query_str = '?stage=conv_groups&req_old_charset='.$old_charset;
+
+		alter_table_utf8($db->prefix.'forum_perms');
+
+		break;
+
+
+	// Convert groups
+	case 'conv_groups':
+		$query_str = '?stage=conv_online&req_old_charset='.$old_charset;
+
+		echo sprintf($lang_update['Converting'], $lang_update['groups']).'<br />'."\n";
+
+		function _conv_groups($cur_item, $old_charset)
+		{
+			convert_to_utf8($cur_item['g_title'], $old_charset);
+			convert_to_utf8($cur_item['g_user_title'], $old_charset);
+
+			return $cur_item;
+		}
+
+		convert_table_utf8($db->prefix.'groups', '_conv_groups', $old_charset, 'g_id');
+
+		break;
+
+
+	// Convert online
+	case 'conv_online':
+		$query_str = '?stage=conv_posts&req_old_charset='.$old_charset;
+
+		// Truncate the table
+		$db->truncate_table('online') or error('Unable to empty online table', __FILE__, __LINE__, $db->error());
+
+		alter_table_utf8($db->prefix.'online');
+
+		break;
+
+
+	// Convert posts
+	case 'conv_posts':
+		$query_str = '?stage=conv_ranks&req_old_charset='.$old_charset;
+
+		function _conv_posts($cur_item, $old_charset)
+		{
+			global $lang_update;
+
+			echo sprintf($lang_update['Converting item'], $lang_update['post'], $cur_item['id']).'<br />'."\n";
+
+			convert_to_utf8($cur_item['poster'], $old_charset);
+			convert_to_utf8($cur_item['message'], $old_charset);
+			convert_to_utf8($cur_item['edited_by'], $old_charset);
+
+			return $cur_item;
+		}
+
+		$end_at = convert_table_utf8($db->prefix.'posts', '_conv_posts', $old_charset, 'id', $start_at);
+
+		if ($end_at !== true)
+			$query_str = '?stage=conv_posts&req_old_charset='.$old_charset.'&start_at='.$end_at;
+
+		break;
+
+
+	// Convert ranks
+	case 'conv_ranks':
+		$query_str = '?stage=conv_reports&req_old_charset='.$old_charset;
+
+		echo sprintf($lang_update['Converting'], $lang_update['ranks']).'<br />'."\n";
+
+		function _conv_ranks($cur_item, $old_charset)
+		{
+			convert_to_utf8($cur_item['rank'], $old_charset);
+
+			return $cur_item;
+		}
+
+		convert_table_utf8($db->prefix.'ranks', '_conv_ranks', $old_charset, 'id');
+
+		break;
+
+
+	// Convert reports
+	case 'conv_reports':
+		$query_str = '?stage=conv_search_cache&req_old_charset='.$old_charset;
+
+		function _conv_reports($cur_item, $old_charset)
+		{
+			global $lang_update;
+
+			echo sprintf($lang_update['Converting item'], $lang_update['report'], $cur_item['id']).'<br />'."\n";
+
+			convert_to_utf8($cur_item['message'], $old_charset);
+
+			return $cur_item;
+		}
+
+		$end_at = convert_table_utf8($db->prefix.'reports', '_conv_reports', $old_charset, 'id', $start_at);
+
+		if ($end_at !== true)
+			$query_str = '?stage=conv_reports&req_old_charset='.$old_charset.'&start_at='.$end_at;
+
+		break;
+
+
+	// Convert search cache
+	case 'conv_search_cache':
+		$query_str = '?stage=conv_search_matches&req_old_charset='.$old_charset;
+
+		// Truncate the table
+		$db->truncate_table('search_cache') or error('Unable to empty search cache table', __FILE__, __LINE__, $db->error());
+
+		alter_table_utf8($db->prefix.'search_cache');
+
+		break;
+
+
+	// Convert search matches
+	case 'conv_search_matches':
+		$query_str = '?stage=conv_search_words&req_old_charset='.$old_charset;
+
+		// Truncate the table
+		$db->truncate_table('search_matches') or error('Unable to empty search index match table', __FILE__, __LINE__, $db->error());
+
+		alter_table_utf8($db->prefix.'search_matches');
+
+		break;
+
+
+	// Convert search words
+	case 'conv_search_words':
+		$query_str = '?stage=conv_subscriptions&req_old_charset='.$old_charset;
+
+		// Truncate the table
+		$db->truncate_table('search_words') or error('Unable to empty search index words table', __FILE__, __LINE__, $db->error());
+
+		// Reset the sequence for the search words (not needed for SQLite)
+		switch ($db_type)
+		{
+			case 'mysql':
+			case 'mysqli':
+			case 'mysql_innodb':
+			case 'mysqli_innodb':
+				$db->query('ALTER TABLE '.$db->prefix.'search_words auto_increment=1') or error('Unable to update table auto_increment', __FILE__, __LINE__, $db->error());
+				break;
+
+			case 'pgsql';
+				$db->query('SELECT setval(\''.$db->prefix.'search_words_id_seq\', 1, false)') or error('Unable to update sequence', __FILE__, __LINE__, $db->error());
+				break;
+		}
+
+		alter_table_utf8($db->prefix.'search_words');
+
+		break;
+
+
+	// Convert subscriptions
+	case 'conv_subscriptions':
+		$query_str = '?stage=conv_topics&req_old_charset='.$old_charset;
+
+		// By this stage we should have already renamed the subscription table
+		alter_table_utf8($db->prefix.'topic_subscriptions');
+		alter_table_utf8($db->prefix.'forum_subscriptions'); // This should actually already be utf8, but for consistency...
+
+		break;
+
+
+	// Convert topics
+	case 'conv_topics':
+		$query_str = '?stage=conv_users&req_old_charset='.$old_charset;
+
+		function _conv_topics($cur_item, $old_charset)
+		{
+			global $lang_update;
+
+			echo sprintf($lang_update['Converting item'], $lang_update['topic'], $cur_item['id']).'<br />'."\n";
+
+			convert_to_utf8($cur_item['poster'], $old_charset);
+			convert_to_utf8($cur_item['subject'], $old_charset);
+			convert_to_utf8($cur_item['last_poster'], $old_charset);
+
+			return $cur_item;
+		}
+
+		$end_at = convert_table_utf8($db->prefix.'topics', '_conv_topics', $old_charset, 'id', $start_at);
+
+		if ($end_at !== true)
+			$query_str = '?stage=conv_topics&req_old_charset='.$old_charset.'&start_at='.$end_at;
+
+		break;
+
+
+	// Convert users
+	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;
+
+			echo sprintf($lang_update['Converting item'], $lang_update['user'], $cur_item['id']).'<br />'."\n";
+
+			convert_to_utf8($cur_item['username'], $old_charset);
+			convert_to_utf8($cur_item['title'], $old_charset);
+			convert_to_utf8($cur_item['realname'], $old_charset);
+			convert_to_utf8($cur_item['location'], $old_charset);
+			convert_to_utf8($cur_item['signature'], $old_charset);
+			convert_to_utf8($cur_item['admin_note'], $old_charset);
+
+			return $cur_item;
+		}
+
+		function _error_users($cur_user)
+		{
+			$_SESSION['dupe_users'][$cur_user['id']] = $cur_user;
+		}
+
+		$end_at = convert_table_utf8($db->prefix.'users', '_conv_users', $old_charset, 'id', $start_at, '_error_users');
+
+		if ($end_at !== true)
+			$query_str = '?stage=conv_users&req_old_charset='.$old_charset.'&start_at='.$end_at;
+		else if (!empty($_SESSION['dupe_users']))
+			$query_str = '?stage=conv_users_dupe';
+
+		break;
+
+
+	// Handle any duplicate users which occured due to conversion
+	case 'conv_users_dupe':
+		$query_str = '?stage=preparse_posts';
+
+		if (!$mysql || empty($_SESSION['dupe_users']))
+			break;
+
+		if (isset($_POST['form_sent']))
+		{
+			$errors = array();
+
+			require PUN_ROOT.'include/email.php';
+
+			foreach ($_SESSION['dupe_users'] as $id => $cur_user)
+			{
+				$errors[$id] = array();
+
+				$username = pun_trim($_POST['dupe_users'][$id]);
+
+				if (pun_strlen($username) < 2)
+					$errors[$id][] = $lang_update['Username too short error'];
+				else if (pun_strlen($username) > 25) // This usually doesn't happen since the form element only accepts 25 characters
+					$errors[$id][] = $lang_update['Username too long error'];
+				else if (!strcasecmp($username, 'Guest'))
+					$errors[$id][] = $lang_update['Username Guest reserved error'];
+				else if (preg_match('%[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}%', $username) || preg_match('%((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))%', $username))
+					$errors[$id][] = $lang_update['Username IP format error'];
+				else if ((strpos($username, '[') !== false || strpos($username, ']') !== false) && strpos($username, '\'') !== false && strpos($username, '"') !== false)
+					$errors[$id][] = $lang_update['Username bad characters error'];
+				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(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))
+				{
+					$busy = $db->result($result);
+					$errors[$id][] = sprintf($lang_update['Username duplicate error'], pun_htmlspecialchars($busy));
+				}
+
+				if (empty($errors[$id]))
+				{
+					$old_username = $cur_user['username'];
+					$_SESSION['dupe_users'][$id]['username'] = $cur_user['username'] = $username;
+
+					$temp = array();
+					foreach ($cur_user as $idx => $value)
+						$temp[$idx] = is_null($value) ? 'NULL' : '\''.$db->escape($value).'\'';
+
+					// Insert the renamed user
+					$db->query('INSERT INTO '.$db->prefix.'users('.implode(',', array_keys($temp)).') VALUES ('.implode(',', array_values($temp)).')') or error('Unable to insert data to new table', __FILE__, __LINE__, $db->error());
+
+					// Renaming a user also affects a bunch of other stuff, lets fix that too...
+					$db->query('UPDATE '.$db->prefix.'posts SET poster=\''.$db->escape($username).'\' WHERE poster_id='.$id) or error('Unable to update posts', __FILE__, __LINE__, $db->error());
+
+					// TODO: The following must compare using collation utf8_bin otherwise we will accidently update posts/topics/etc belonging to both of the duplicate users, not just the one we renamed!
+					$db->query('UPDATE '.$db->prefix.'posts SET edited_by=\''.$db->escape($username).'\' WHERE edited_by=\''.$db->escape($old_username).'\' COLLATE utf8_bin') or error('Unable to update posts', __FILE__, __LINE__, $db->error());
+					$db->query('UPDATE '.$db->prefix.'topics SET poster=\''.$db->escape($username).'\' WHERE poster=\''.$db->escape($old_username).'\' COLLATE utf8_bin') or error('Unable to update topics', __FILE__, __LINE__, $db->error());
+					$db->query('UPDATE '.$db->prefix.'topics SET last_poster=\''.$db->escape($username).'\' WHERE last_poster=\''.$db->escape($old_username).'\' COLLATE utf8_bin') or error('Unable to update topics', __FILE__, __LINE__, $db->error());
+					$db->query('UPDATE '.$db->prefix.'forums SET last_poster=\''.$db->escape($username).'\' WHERE last_poster=\''.$db->escape($old_username).'\' COLLATE utf8_bin') or error('Unable to update forums', __FILE__, __LINE__, $db->error());
+					$db->query('UPDATE '.$db->prefix.'online SET ident=\''.$db->escape($username).'\' WHERE ident=\''.$db->escape($old_username).'\' COLLATE utf8_bin') or error('Unable to update online list', __FILE__, __LINE__, $db->error());
+
+					// If the user is a moderator or an administrator we have to update the moderator lists
+					$result = $db->query('SELECT g_moderator FROM '.$db->prefix.'groups WHERE g_id='.$cur_user['group_id']) or error('Unable to fetch group', __FILE__, __LINE__, $db->error());
+					$group_mod = $db->result($result);
+
+					if ($cur_user['group_id'] == PUN_ADMIN || $group_mod == '1')
+					{
+						$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();
+
+							if (in_array($id, $cur_moderators))
+							{
+								unset($cur_moderators[$old_username]);
+								$cur_moderators[$username] = $id;
+								uksort($cur_moderators, 'utf8_strcasecmp');
+
+								$db->query('UPDATE '.$db->prefix.'forums SET moderators=\''.$db->escape(serialize($cur_moderators)).'\' WHERE id='.$cur_forum['id']) or error('Unable to update forum', __FILE__, __LINE__, $db->error());
+							}
+						}
+					}
+
+					// Email the user alerting them of the change
+					if (file_exists(PUN_ROOT.'lang/'.$cur_user['language'].'/mail_templates/rename.tpl'))
+						$mail_tpl = trim(file_get_contents(PUN_ROOT.'lang/'.$cur_user['language'].'/mail_templates/rename.tpl'));
+					else if (file_exists(PUN_ROOT.'lang/'.$pun_config['o_default_lang'].'/mail_templates/rename.tpl'))
+						$mail_tpl = trim(file_get_contents(PUN_ROOT.'lang/'.$pun_config['o_default_lang'].'/mail_templates/rename.tpl'));
+					else
+						$mail_tpl = trim(file_get_contents(PUN_ROOT.'lang/English/mail_templates/rename.tpl'));
+
+					// The first row contains the subject
+					$first_crlf = strpos($mail_tpl, "\n");
+					$mail_subject = trim(substr($mail_tpl, 8, $first_crlf-8));
+					$mail_message = trim(substr($mail_tpl, $first_crlf));
+
+					$mail_subject = str_replace('<board_title>', $pun_config['o_board_title'], $mail_subject);
+					$mail_message = str_replace('<base_url>', get_base_url().'/', $mail_message);
+					$mail_message = str_replace('<old_username>', $old_username, $mail_message);
+					$mail_message = str_replace('<new_username>', $username, $mail_message);
+					$mail_message = str_replace('<board_mailer>', $pun_config['o_board_title'], $mail_message);
+
+					pun_mail($cur_user['email'], $mail_subject, $mail_message);
+
+					unset($_SESSION['dupe_users'][$id]);
+				}
+			}
+		}
+
+		if (!empty($_SESSION['dupe_users']))
+		{
+			$query_str = '';
+
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php echo $lang_common['lang_identifier'] ?>" lang="<?php echo $lang_common['lang_identifier'] ?>" dir="<?php echo $lang_common['lang_direction'] ?>">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<title><?php echo $lang_update['Update'] ?></title>
+<link rel="stylesheet" type="text/css" href="style/<?php echo $default_style ?>.css" />
+</head>
+<body>
+
+<div id="pundb_update" class="pun">
+<div class="top-box"><div><!-- Top Corners --></div></div>
+<div class="punwrap">
+
+<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&amp;uid=<?php echo $uid ?>">
+			<input type="hidden" name="form_sent" value="1" />
+			<div class="inform">
+				<div class="forminfo">
+						<p style="font-size: 1.1em"><?php echo $lang_update['Error info 1'] ?></p>
+						<p style="font-size: 1.1em"><?php echo $lang_update['Error info 2'] ?></p>
+				</div>
+			</div>
+<?php
+
+			foreach ($_SESSION['dupe_users'] as $id => $cur_user)
+			{
+
+?>
+			<div class="inform">
+				<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>
+					</div>
+				</fieldset>
+<?php if (!empty($errors[$id])): ?>				<div class="forminfo error-info">
+					<h3><?php echo $lang_update['Correct errors'] ?></h3>
+					<ul class="error-list">
+<?php
+
+foreach ($errors[$id] as $cur_error)
+	echo "\t\t\t\t\t\t".'<li><strong>'.$cur_error.'</strong></li>'."\n";
+?>
+					</ul>
+				</div>
+<?php endif; ?>			</div>
+<?php
+
+			}
+
+?>
+			<p class="buttons"><input type="submit" name="rename" value="<?php echo $lang_update['Rename users'] ?>" /></p>
+		</form>
+	</div>
+</div>
+
+</div>
+<div class="end-box"><div><!-- Bottom Corners --></div></div>
+</div>
+
+</body>
+</html>
+<?php
+
+		}
+
+		break;
+
+
+	// Preparse posts
+	case 'preparse_posts':
+		$query_str = '?stage=preparse_sigs';
+
+		// If we don't need to parse the posts, skip this stage
+		if (isset($pun_config['o_parser_revision']) && $pun_config['o_parser_revision'] >= UPDATE_TO_PARSER_REVISION)
+			break;
+
+		require PUN_ROOT.'include/parser.php';
+
+		// Fetch posts to process this cycle
+		$result = $db->query('SELECT id, message FROM '.$db->prefix.'posts WHERE id > '.$start_at.' ORDER BY id ASC LIMIT '.PER_PAGE) or error('Unable to fetch posts', __FILE__, __LINE__, $db->error());
+
+		$temp = array();
+		$end_at = 0;
+		while ($cur_item = $db->fetch_assoc($result))
+		{
+			echo sprintf($lang_update['Preparsing item'], $lang_update['post'], $cur_item['id']).'<br />'."\n";
+			$db->query('UPDATE '.$db->prefix.'posts SET message = \''.$db->escape(preparse_bbcode($cur_item['message'], $temp)).'\' WHERE id = '.$cur_item['id']) or error('Unable to update post', __FILE__, __LINE__, $db->error());
+
+			$end_at = $cur_item['id'];
+		}
+
+		// Check if there is more work to do
+		if ($end_at > 0)
+		{
+			$result = $db->query('SELECT 1 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 = '?stage=preparse_posts&start_at='.$end_at;
+		}
+
+		break;
+
+
+	// Preparse signatures
+	case 'preparse_sigs':
+		$query_str = '?stage=rebuild_idx';
+
+		// If we don't need to parse the sigs, skip this stage
+		if (isset($pun_config['o_parser_revision']) && $pun_config['o_parser_revision'] >= UPDATE_TO_PARSER_REVISION)
+			break;
+
+		require PUN_ROOT.'include/parser.php';
+
+		// Fetch users to process this cycle
+		$result = $db->query('SELECT id, signature FROM '.$db->prefix.'users WHERE id > '.$start_at.' ORDER BY id ASC LIMIT '.PER_PAGE) or error('Unable to fetch users', __FILE__, __LINE__, $db->error());
+
+		$temp = array();
+		$end_at = 0;
+		while ($cur_item = $db->fetch_assoc($result))
+		{
+			echo sprintf($lang_update['Preparsing item'], $lang_update['signature'], $cur_item['id']).'<br />'."\n";
+			$db->query('UPDATE '.$db->prefix.'users SET signature = \''.$db->escape(preparse_bbcode($cur_item['signature'], $temp, true)).'\' WHERE id = '.$cur_item['id']) or error('Unable to update user', __FILE__, __LINE__, $db->error());
+
+			$end_at = $cur_item['id'];
+		}
+
+		// Check if there is more work to do
+		if ($end_at > 0)
+		{
+			$result = $db->query('SELECT 1 FROM '.$db->prefix.'users 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 = '?stage=preparse_sigs&start_at='.$end_at;
+		}
+
+		break;
+
+
+	// Rebuild the search index
+	case 'rebuild_idx':
+		$query_str = '?stage=finish';
+
+		// If we don't need to update the search index, skip this stage
+		if (isset($pun_config['o_searchindex_revision']) && $pun_config['o_searchindex_revision'] >= UPDATE_TO_SI_REVISION)
+			break;
+
+		if ($start_at == 0)
+		{
+			// Truncate the tables just in-case we didn't already (if we are coming directly here without converting the tables)
+			$db->truncate_table('search_cache') or error('Unable to empty search cache table', __FILE__, __LINE__, $db->error());
+			$db->truncate_table('search_matches') or error('Unable to empty search index match table', __FILE__, __LINE__, $db->error());
+			$db->truncate_table('search_words') or error('Unable to empty search index words table', __FILE__, __LINE__, $db->error());
+
+			// Reset the sequence for the search words (not needed for SQLite)
+			switch ($db_type)
+			{
+				case 'mysql':
+				case 'mysqli':
+				case 'mysql_innodb':
+				case 'mysqli_innodb':
+					$db->query('ALTER TABLE '.$db->prefix.'search_words auto_increment=1') or error('Unable to update table auto_increment', __FILE__, __LINE__, $db->error());
+					break;
+
+				case 'pgsql';
+					$db->query('SELECT setval(\''.$db->prefix.'search_words_id_seq\', 1, false)') or error('Unable to update sequence', __FILE__, __LINE__, $db->error());
+					break;
+			}
+		}
+
+		require PUN_ROOT.'include/search_idx.php';
+
+		// Fetch posts to process this cycle
+		$result = $db->query('SELECT p.id, p.message, t.subject, t.first_post_id FROM '.$db->prefix.'posts AS p INNER JOIN '.$db->prefix.'topics AS t ON t.id=p.topic_id WHERE p.id > '.$start_at.' ORDER BY p.id ASC LIMIT '.PER_PAGE) or error('Unable to fetch posts', __FILE__, __LINE__, $db->error());
+
+		$end_at = 0;
+		while ($cur_item = $db->fetch_assoc($result))
+		{
+			echo sprintf($lang_update['Rebuilding index item'], $lang_update['post'], $cur_item['id']).'<br />'."\n";
+
+			if ($cur_item['id'] == $cur_item['first_post_id'])
+				update_search_index('post', $cur_item['id'], $cur_item['message'], $cur_item['subject']);
+			else
+				update_search_index('post', $cur_item['id'], $cur_item['message']);
+
+			$end_at = $cur_item['id'];
+		}
+
+		// Check if there is more work to do
+		if ($end_at > 0)
+		{
+			$result = $db->query('SELECT 1 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 = '?stage=rebuild_idx&start_at='.$end_at;
+		}
+
+		break;
+
+
+	// Show results page
+	case 'finish':
+		// We update the version number
+		$db->query('UPDATE '.$db->prefix.'config SET conf_value = \''.UPDATE_TO.'\' WHERE conf_name = \'o_cur_version\'') or error('Unable to update version', __FILE__, __LINE__, $db->error());
+
+		// And the database revision number
+		$db->query('UPDATE '.$db->prefix.'config SET conf_value = \''.UPDATE_TO_DB_REVISION.'\' WHERE conf_name = \'o_database_revision\'') or error('Unable to update database revision number', __FILE__, __LINE__, $db->error());
+
+		// And the search index revision number
+		$db->query('UPDATE '.$db->prefix.'config SET conf_value = \''.UPDATE_TO_SI_REVISION.'\' WHERE conf_name = \'o_searchindex_revision\'') or error('Unable to update search index revision number', __FILE__, __LINE__, $db->error());
+
+		// And the parser revision number
+		$db->query('UPDATE '.$db->prefix.'config SET conf_value = \''.UPDATE_TO_PARSER_REVISION.'\' WHERE conf_name = \'o_parser_revision\'') or error('Unable to update parser revision number', __FILE__, __LINE__, $db->error());
+
+		// Check the default language still exists!
+		if (!file_exists(PUN_ROOT.'lang/'.$pun_config['o_default_lang'].'/common.php'))
+			$db->query('UPDATE '.$db->prefix.'config SET conf_value = \'English\' WHERE conf_name = \'o_default_lang\'') or error('Unable to update default language', __FILE__, __LINE__, $db->error());
+
+		// Check the default style still exists!
+		if (!file_exists(PUN_ROOT.'style/'.$pun_config['o_default_style'].'.css'))
+			$db->query('UPDATE '.$db->prefix.'config SET conf_value = \'Air\' WHERE conf_name = \'o_default_style\'') or error('Unable to update default style', __FILE__, __LINE__, $db->error());
+
+		// This feels like a good time to synchronize the forums
+		$result = $db->query('SELECT id FROM '.$db->prefix.'forums') or error('Unable to fetch forum IDs', __FILE__, __LINE__, $db->error());
+
+		while ($row = $db->fetch_row($result))
+			update_forum($row[0]);
+
+		// Empty the PHP cache
+		forum_clear_cache();
+
+		// Delete the update lock file
+		@unlink(FORUM_CACHE_DIR.'db_update.lock');
+
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php echo $lang_common['lang_identifier'] ?>" lang="<?php echo $lang_common['lang_identifier'] ?>" dir="<?php echo $lang_common['lang_direction'] ?>">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<title><?php echo $lang_update['Update'] ?></title>
+<link rel="stylesheet" type="text/css" href="style/<?php echo $default_style ?>.css" />
+</head>
+<body>
+
+<div id="pundb_update" class="pun">
+<div class="top-box"><div><!-- Top Corners --></div></div>
+<div class="punwrap">
+
+<div class="blockform">
+	<h2><span><?php echo $lang_update['Update'] ?></span></h2>
+	<div class="box">
+		<div class="fakeform">
+			<div class="inform">
+				<div class="forminfo">
+					<p style="font-size: 1.1em"><?php printf($lang_update['Successfully updated'], sprintf('<a href="index.php">%s</a>', $lang_update['go to index'])) ?></p>
+				</div>
+			</div>
+		</div>
+	</div>
+</div>
+
+</div>
+<div class="end-box"><div><!-- Bottom Corners --></div></div>
+</div>
+
+</body>
+</html>
+<?php
+
+		break;
+}
+
+$db->end_transaction();
+$db->close();
+
+if ($query_str != '')
+	exit('<script type="text/javascript">window.location="db_update.php'.$query_str.'&uid='.$uid.'"</script><noscript><meta http-equiv="refresh" content="0;url=db_update.php'.$query_str.'&uid='.$uid.'" /></noscript>');
diff --git a/delete.php b/delete.php
new file mode 100644
index 0000000..58e2aa7
--- /dev/null
+++ b/delete.php
@@ -0,0 +1,137 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+define('PUN_ROOT', dirname(__FILE__).'/');
+require PUN_ROOT.'include/common.php';
+
+
+if ($pun_user['g_read_board'] == '0')
+	message($lang_common['No view'], false, '403 Forbidden');
+
+
+$id = isset($_GET['id']) ? intval($_GET['id']) : 0;
+if ($id < 1)
+	message($lang_common['Bad request'], false, '404 Not Found');
+
+// Fetch some info about the post, the topic and the forum
+$result = $db->query('SELECT f.id AS fid, f.forum_name, f.moderators, f.redirect_url, fp.post_replies, fp.post_topics, t.id AS tid, t.subject, t.first_post_id, t.closed, p.posted, p.poster, p.poster_id, p.message, p.hide_smilies 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 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 p.id='.$id) or error('Unable to fetch post info', __FILE__, __LINE__, $db->error());
+if (!$db->num_rows($result))
+	message($lang_common['Bad request'], false, '404 Not Found');
+
+$cur_post = $db->fetch_assoc($result);
+
+if ($pun_config['o_censoring'] == '1')
+	$cur_post['subject'] = censor_words($cur_post['subject']);
+
+// Sort out who the moderators are and if we are currently a moderator (or an admin)
+$mods_array = ($cur_post['moderators'] != '') ? unserialize($cur_post['moderators']) : array();
+$is_admmod = ($pun_user['g_id'] == PUN_ADMIN || ($pun_user['g_moderator'] == '1' && array_key_exists($pun_user['username'], $mods_array))) ? true : false;
+
+$is_topic_post = ($id == $cur_post['first_post_id']) ? true : false;
+
+// Do we have permission to edit this post?
+if (($pun_user['g_delete_posts'] == '0' ||
+	($pun_user['g_delete_topics'] == '0' && $is_topic_post) ||
+	$cur_post['poster_id'] != $pun_user['id'] ||
+	$cur_post['closed'] == '1') &&
+	!$is_admmod)
+	message($lang_common['No permission'], false, '403 Forbidden');
+
+// Load the delete.php language file
+require PUN_ROOT.'lang/'.$pun_user['language'].'/delete.php';
+
+
+if (isset($_POST['delete']))
+{
+	// Make sure they got here from the site
+	confirm_referrer('delete.php');
+
+	require PUN_ROOT.'include/search_idx.php';
+
+	if ($is_topic_post)
+	{
+		// Delete the topic and all of it's posts
+		delete_topic($cur_post['tid']);
+		update_forum($cur_post['fid']);
+
+		redirect('viewforum.php?id='.$cur_post['fid'], $lang_delete['Topic del redirect']);
+	}
+	else
+	{
+		// Delete just this one post
+		delete_post($id, $cur_post['tid']);
+		update_forum($cur_post['fid']);
+
+		// Redirect towards the previous post
+		$result = $db->query('SELECT id FROM '.$db->prefix.'posts WHERE topic_id='.$cur_post['tid'].' AND id < '.$id.' ORDER BY id DESC LIMIT 1') or error('Unable to fetch post info', __FILE__, __LINE__, $db->error());
+		$post_id = $db->result($result);
+
+		redirect('viewtopic.php?pid='.$post_id.'#p'.$post_id, $lang_delete['Post del redirect']);
+	}
+}
+
+
+$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_delete['Delete post']);
+define ('PUN_ACTIVE_PAGE', 'index');
+require PUN_ROOT.'header.php';
+
+require PUN_ROOT.'include/parser.php';
+$cur_post['message'] = parse_message($cur_post['message'], $cur_post['hide_smilies']);
+
+?>
+<div class="linkst">
+	<div class="inbox">
+		<ul class="crumbs">
+			<li><a href="index.php"><?php echo $lang_common['Index'] ?></a></li>
+			<li><span>»&#160;</span><a href="viewforum.php?id=<?php echo $cur_post['fid'] ?>"><?php echo pun_htmlspecialchars($cur_post['forum_name']) ?></a></li>
+			<li><span>»&#160;</span><a href="viewtopic.php?pid=<?php echo $id ?>#p<?php echo $id ?>"><?php echo pun_htmlspecialchars($cur_post['subject']) ?></a></li>
+			<li><span>»&#160;</span><strong><?php echo $lang_delete['Delete post'] ?></strong></li>
+		</ul>
+	</div>
+</div>
+
+<div class="blockform">
+	<h2><span><?php echo $lang_delete['Delete post'] ?></span></h2>
+	<div class="box">
+		<form method="post" action="delete.php?id=<?php echo $id ?>">
+			<div class="inform">
+				<div class="forminfo">
+					<h3><span><?php printf($is_topic_post ? $lang_delete['Topic by'] : $lang_delete['Reply by'], '<strong>'.pun_htmlspecialchars($cur_post['poster']).'</strong>', format_time($cur_post['posted'])) ?></span></h3>
+					<p><?php echo ($is_topic_post) ? '<strong>'.$lang_delete['Topic warning'].'</strong>' : '<strong>'.$lang_delete['Warning'].'</strong>' ?><br /><?php echo $lang_delete['Delete info'] ?></p>
+				</div>
+			</div>
+			<p class="buttons"><input type="submit" name="delete" value="<?php echo $lang_delete['Delete'] ?>" /> <a href="javascript:history.go(-1)"><?php echo $lang_common['Go back'] ?></a></p>
+		</form>
+	</div>
+</div>
+
+<div id="postreview">
+	<div class="blockpost">
+		<div class="box">
+			<div class="inbox">
+				<div class="postbody">
+					<div class="postleft">
+						<dl>
+							<dt><strong><?php echo pun_htmlspecialchars($cur_post['poster']) ?></strong></dt>
+							<dd><span><?php echo format_time($cur_post['posted']) ?></span></dd>
+						</dl>
+					</div>
+					<div class="postright">
+						<div class="postmsg">
+							<?php echo $cur_post['message']."\n" ?>
+						</div>
+					</div>
+				</div>
+				<div class="clearer"></div>
+			</div>
+		</div>
+	</div>
+</div>
+<?php
+
+require PUN_ROOT.'footer.php';
diff --git a/edit.php b/edit.php
new file mode 100644
index 0000000..7a1d674
--- /dev/null
+++ b/edit.php
@@ -0,0 +1,287 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+define('PUN_ROOT', dirname(__FILE__).'/');
+require PUN_ROOT.'include/common.php';
+
+
+if ($pun_user['g_read_board'] == '0')
+	message($lang_common['No view'], false, '403 Forbidden');
+
+
+$id = isset($_GET['id']) ? intval($_GET['id']) : 0;
+if ($id < 1)
+	message($lang_common['Bad request'], false, '404 Not Found');
+
+// Fetch some info about the post, the topic and the forum
+$result = $db->query('SELECT f.id AS fid, f.forum_name, f.moderators, f.redirect_url, fp.post_replies, fp.post_topics, t.id AS tid, t.subject, t.posted, t.first_post_id, t.sticky, t.closed, p.poster, p.poster_id, p.message, p.hide_smilies 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 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 p.id='.$id) or error('Unable to fetch post info', __FILE__, __LINE__, $db->error());
+if (!$db->num_rows($result))
+	message($lang_common['Bad request'], false, '404 Not Found');
+
+$cur_post = $db->fetch_assoc($result);
+
+// Sort out who the moderators are and if we are currently a moderator (or an admin)
+$mods_array = ($cur_post['moderators'] != '') ? unserialize($cur_post['moderators']) : array();
+$is_admmod = ($pun_user['g_id'] == PUN_ADMIN || ($pun_user['g_moderator'] == '1' && array_key_exists($pun_user['username'], $mods_array))) ? true : false;
+
+$can_edit_subject = $id == $cur_post['first_post_id'];
+
+if ($pun_config['o_censoring'] == '1')
+{
+	$cur_post['subject'] = censor_words($cur_post['subject']);
+	$cur_post['message'] = censor_words($cur_post['message']);
+}
+
+// Do we have permission to edit this post?
+if (($pun_user['g_edit_posts'] == '0' ||
+	$cur_post['poster_id'] != $pun_user['id'] ||
+	$cur_post['closed'] == '1') &&
+	!$is_admmod)
+	message($lang_common['No permission'], false, '403 Forbidden');
+
+// Load the post.php/edit.php language file
+require PUN_ROOT.'lang/'.$pun_user['language'].'/post.php';
+
+// Start with a clean slate
+$errors = array();
+
+
+if (isset($_POST['form_sent']))
+{
+	// Make sure they got here from the site
+	confirm_referrer('edit.php');
+
+	// If it's a topic it must contain a subject
+	if ($can_edit_subject)
+	{
+		$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'])
+			$errors[] = $lang_post['All caps subject'];
+	}
+
+	// Clean up message from POST
+	$message = pun_linebreaks(pun_trim($_POST['req_message']));
+
+	// Here we use strlen() not pun_strlen() as we want to limit the post to PUN_MAX_POSTSIZE bytes, not characters
+	if (strlen($message) > PUN_MAX_POSTSIZE)
+		$errors[] = sprintf($lang_post['Too long message'], forum_number_format(PUN_MAX_POSTSIZE));
+	else if ($pun_config['p_message_all_caps'] == '0' && is_all_uppercase($message) && !$pun_user['is_admmod'])
+		$errors[] = $lang_post['All caps message'];
+
+	// Validate BBCode syntax
+	if ($pun_config['p_message_bbcode'] == '1')
+	{
+		require PUN_ROOT.'include/parser.php';
+		$message = preparse_bbcode($message, $errors);
+	}
+
+	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';
+	if (!$is_admmod)
+		$stick_topic = $cur_post['sticky'];
+	
+	// Replace four-byte characters (MySQL cannot handle them)
+	$message = strip_bad_multibyte_chars($message);
+
+	// Did everything go according to plan?
+	if (empty($errors) && !isset($_POST['preview']))
+	{
+		$edited_sql = (!isset($_POST['silent']) || !$is_admmod) ? ', edited='.time().', edited_by=\''.$db->escape($pun_user['username']).'\'' : '';
+
+		require PUN_ROOT.'include/search_idx.php';
+
+		if ($can_edit_subject)
+		{
+			// Update the topic and any redirect topics
+			$db->query('UPDATE '.$db->prefix.'topics SET subject=\''.$db->escape($subject).'\', sticky='.$stick_topic.' WHERE id='.$cur_post['tid'].' OR moved_to='.$cur_post['tid']) or error('Unable to update topic', __FILE__, __LINE__, $db->error());
+
+			// We changed the subject, so we need to take that into account when we update the search words
+			update_search_index('edit', $id, $message, $subject);
+		}
+		else
+			update_search_index('edit', $id, $message);
+
+		// Update the post
+		$db->query('UPDATE '.$db->prefix.'posts SET message=\''.$db->escape($message).'\', hide_smilies='.$hide_smilies.$edited_sql.' WHERE id='.$id) or error('Unable to update post', __FILE__, __LINE__, $db->error());
+
+		redirect('viewtopic.php?pid='.$id.'#p'.$id, $lang_post['Edit redirect']);
+	}
+}
+
+
+
+$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_post['Edit post']);
+$required_fields = array('req_subject' => $lang_common['Subject'], 'req_message' => $lang_common['Message']);
+$focus_element = array('edit', 'req_message');
+define('PUN_ACTIVE_PAGE', 'index');
+require PUN_ROOT.'header.php';
+
+$cur_index = 1;
+
+?>
+<div class="linkst">
+	<div class="inbox">
+		<ul class="crumbs">
+			<li><a href="index.php"><?php echo $lang_common['Index'] ?></a></li>
+			<li><span>»&#160;</span><a href="viewforum.php?id=<?php echo $cur_post['fid'] ?>"><?php echo pun_htmlspecialchars($cur_post['forum_name']) ?></a></li>
+			<li><span>»&#160;</span><a href="viewtopic.php?id=<?php echo $cur_post['tid'] ?>"><?php echo pun_htmlspecialchars($cur_post['subject']) ?></a></li>
+			<li><span>»&#160;</span><strong><?php echo $lang_post['Edit post'] ?></strong></li>
+		</ul>
+	</div>
+</div>
+
+<?php
+
+// If there are errors, we display them
+if (!empty($errors))
+{
+
+?>
+<div id="posterror" class="block">
+	<h2><span><?php echo $lang_post['Post errors'] ?></span></h2>
+	<div class="box">
+		<div class="inbox error-info">
+			<p><?php echo $lang_post['Post errors info'] ?></p>
+			<ul class="error-list">
+<?php
+
+	foreach ($errors as $cur_error)
+		echo "\t\t\t\t".'<li><strong>'.$cur_error.'</strong></li>'."\n";
+?>
+			</ul>
+		</div>
+	</div>
+</div>
+
+<?php
+
+}
+else if (isset($_POST['preview']))
+{
+	require_once PUN_ROOT.'include/parser.php';
+	$preview_message = parse_message($message, $hide_smilies);
+
+?>
+<div id="postpreview" class="blockpost">
+	<h2><span><?php echo $lang_post['Post preview'] ?></span></h2>
+	<div class="box">
+		<div class="inbox">
+			<div class="postbody">
+				<div class="postright">
+					<div class="postmsg">
+						<?php echo $preview_message."\n" ?>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</div>
+
+<?php
+
+}
+
+?>
+<div id="editform" class="blockform">
+	<h2><span><?php echo $lang_post['Edit post'] ?></span></h2>
+	<div class="box">
+		<form id="edit" method="post" action="edit.php?id=<?php echo $id ?>&amp;action=edit" onsubmit="return process_form(this)">
+			<div class="inform">
+				<fieldset>
+					<legend><?php echo $lang_post['Edit post legend'] ?></legend>
+					<input type="hidden" name="form_sent" value="1" />
+					<div class="infldset txtarea">
+<?php if ($can_edit_subject): ?>						<label class="required"><strong><?php echo $lang_common['Subject'] ?> <span><?php echo $lang_common['Required'] ?></span></strong><br />
+						<input class="longinput" type="text" name="req_subject" size="80" maxlength="70" tabindex="<?php echo $cur_index++ ?>" value="<?php echo pun_htmlspecialchars(isset($_POST['req_subject']) ? $_POST['req_subject'] : $cur_post['subject']) ?>" /><br /></label>
+<?php endif; ?>						<label class="required"><strong><?php echo $lang_common['Message'] ?> <span><?php echo $lang_common['Required'] ?></span></strong><br />
+						<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_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>
+<?php
+
+$checkboxes = array();
+if ($can_edit_subject && $is_admmod)
+{
+	if (isset($_POST['stick_topic']) || $cur_post['sticky'] == '1')
+		$checkboxes[] = '<label><input type="checkbox" name="stick_topic" value="1" checked="checked" tabindex="'.($cur_index++).'" />'.$lang_common['Stick topic'].'<br /></label>';
+	else
+		$checkboxes[] = '<label><input type="checkbox" name="stick_topic" value="1" tabindex="'.($cur_index++).'" />'.$lang_common['Stick topic'].'<br /></label>';
+}
+
+if ($pun_config['o_smilies'] == '1')
+{
+	if (isset($_POST['hide_smilies']) || $cur_post['hide_smilies'] == '1')
+		$checkboxes[] = '<label><input type="checkbox" name="hide_smilies" value="1" checked="checked" tabindex="'.($cur_index++).'" />'.$lang_post['Hide smilies'].'<br /></label>';
+	else
+		$checkboxes[] = '<label><input type="checkbox" name="hide_smilies" value="1" tabindex="'.($cur_index++).'" />'.$lang_post['Hide smilies'].'<br /></label>';
+}
+
+if ($is_admmod)
+{
+	if ((isset($_POST['form_sent']) && isset($_POST['silent'])) || !isset($_POST['form_sent']))
+		$checkboxes[] = '<label><input type="checkbox" name="silent" value="1" tabindex="'.($cur_index++).'" checked="checked" />'.$lang_post['Silent edit'].'<br /></label>';
+	else
+		$checkboxes[] = '<label><input type="checkbox" name="silent" value="1" tabindex="'.($cur_index++).'" />'.$lang_post['Silent edit'].'<br /></label>';
+}
+
+if (!empty($checkboxes))
+{
+
+?>
+			</div>
+			<div class="inform">
+				<fieldset>
+					<legend><?php echo $lang_common['Options'] ?></legend>
+					<div class="infldset">
+						<div class="rbox">
+							<?php echo implode("\n\t\t\t\t\t\t\t", $checkboxes)."\n" ?>
+						</div>
+					</div>
+				</fieldset>
+<?php
+
+	}
+
+?>
+			</div>
+			<p class="buttons"><input type="submit" name="submit" value="<?php echo $lang_common['Submit'] ?>" tabindex="<?php echo $cur_index++ ?>" accesskey="s" /> <input type="submit" name="preview" value="<?php echo $lang_post['Preview'] ?>" tabindex="<?php echo $cur_index++ ?>" accesskey="p" /> <a href="javascript:history.go(-1)"><?php echo $lang_common['Go back'] ?></a></p>
+		</form>
+	</div>
+</div>
+<?php
+
+require PUN_ROOT.'footer.php';
diff --git a/extern.php b/extern.php
new file mode 100644
index 0000000..eb4bc94
--- /dev/null
+++ b/extern.php
@@ -0,0 +1,552 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+/*-----------------------------------------------------------------------------
+
+  INSTRUCTIONS
+
+  This script is used to include information about your board from
+  pages outside the forums and to syndicate news about recent
+  discussions via RSS/Atom/XML. The script can display a list of
+  recent discussions, a list of active users or a collection of
+  general board statistics. The script can be called directly via
+  an URL, from a PHP include command or through the use of Server
+  Side Includes (SSI).
+
+  The scripts behaviour is controlled via variables supplied in the
+  URL to the script. The different variables are: action (what to
+  do), show (how many items to display), fid (the ID or IDs of
+  the forum(s) to poll for topics), nfid (the ID or IDs of forums
+  that should be excluded), tid (the ID of the topic from which to
+  display posts) and type (output as HTML or RSS). The only
+  mandatory variable is action. Possible/default values are:
+
+	action: feed - show most recent topics/posts (HTML or RSS)
+			online - show users online (HTML)
+			online_full - as above, but includes a full list (HTML)
+			stats - show board statistics (HTML)
+
+	type:   rss - output as RSS 2.0
+			atom - output as Atom 1.0
+			xml - output as XML
+			html - output as HTML (<li>'s)
+
+	fid:    One or more forum IDs (comma-separated). If ignored,
+			topics from all readable forums will be pulled.
+
+	nfid:   One or more forum IDs (comma-separated) that are to be
+			excluded. E.g. the ID of a a test forum.
+
+	tid:    A topic ID from which to show posts. If a tid is supplied,
+			fid and nfid are ignored.
+
+	show:   Any integer value between 1 and 50. The default is 15.
+
+	order:  last_post - show topics ordered by when they were last
+						posted in, giving information about the reply.
+			posted - show topics ordered by when they were first
+					 posted, giving information about the original post.
+
+-----------------------------------------------------------------------------*/
+
+define('PUN_QUIET_VISIT', 1);
+
+if (!defined('PUN_ROOT'))
+	define('PUN_ROOT', dirname(__FILE__).'/');
+require PUN_ROOT.'include/common.php';
+
+// The length at which topic subjects will be truncated (for HTML output)
+if (!defined('FORUM_EXTERN_MAX_SUBJECT_LENGTH'))
+	define('FORUM_EXTERN_MAX_SUBJECT_LENGTH', 30);
+
+// If we're a guest and we've sent a username/pass, we can try to authenticate using those details
+if ($pun_user['is_guest'] && isset($_SERVER['PHP_AUTH_USER']))
+	authenticate_user($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']);
+
+if ($pun_user['g_read_board'] == '0')
+{
+	http_authenticate_user();
+	exit($lang_common['No view']);
+}
+
+$action = isset($_GET['action']) ? strtolower($_GET['action']) : 'feed';
+
+// Handle a couple old formats, from FluxBB 1.2
+switch ($action)
+{
+	case 'active':
+		$action = 'feed';
+		$_GET['order'] = 'last_post';
+		break;
+
+	case 'new':
+		$action = 'feed';
+		$_GET['order'] = 'posted';
+		break;
+}
+
+//
+// Sends the proper headers for Basic HTTP Authentication
+//
+function http_authenticate_user()
+{
+	global $pun_config, $pun_user;
+
+	if (!$pun_user['is_guest'])
+		return;
+
+	header('WWW-Authenticate: Basic realm="'.$pun_config['o_board_title'].' External Syndication"');
+	header('HTTP/1.0 401 Unauthorized');
+}
+
+
+//
+// Output $feed as RSS 2.0
+//
+function output_rss($feed)
+{
+	global $lang_common, $pun_config;
+
+	// Send XML/no cache headers
+	header('Content-Type: application/xml; charset=utf-8');
+	header('Expires: '.gmdate('D, d M Y H:i:s').' GMT');
+	header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
+	header('Pragma: public');
+
+	echo '<?xml version="1.0" encoding="utf-8"?>'."\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";
+	echo "\t\t".'<lastBuildDate>'.gmdate('r', count($feed['items']) ? $feed['items'][0]['pubdate'] : time()).'</lastBuildDate>'."\n";
+
+	if ($pun_config['o_show_version'] == '1')
+		echo "\t\t".'<generator>FluxBB '.$pun_config['o_cur_version'].'</generator>'."\n";
+	else
+		echo "\t\t".'<generator>FluxBB</generator>'."\n";
+
+	foreach ($feed['items'] as $item)
+	{
+		echo "\t\t".'<item>'."\n";
+		echo "\t\t\t".'<title><![CDATA['.escape_cdata($item['title']).']]></title>'."\n";
+		echo "\t\t\t".'<link>'.pun_htmlspecialchars($item['link']).'</link>'."\n";
+		echo "\t\t\t".'<description><![CDATA['.escape_cdata($item['description']).']]></description>'."\n";
+		echo "\t\t\t".'<author><![CDATA['.(isset($item['author']['email']) ? escape_cdata($item['author']['email']) : 'dummy@example.com').' ('.escape_cdata($item['author']['name']).')]]></author>'."\n";
+		echo "\t\t\t".'<pubDate>'.gmdate('r', $item['pubdate']).'</pubDate>'."\n";
+		echo "\t\t\t".'<guid>'.pun_htmlspecialchars($item['link']).'</guid>'."\n";
+
+		echo "\t\t".'</item>'."\n";
+	}
+
+	echo "\t".'</channel>'."\n";
+	echo '</rss>'."\n";
+}
+
+
+//
+// Output $feed as Atom 1.0
+//
+function output_atom($feed)
+{
+	global $lang_common, $pun_config;
+
+	// Send XML/no cache headers
+	header('Content-Type: application/atom+xml; charset=utf-8');
+	header('Expires: '.gmdate('D, d M Y H:i:s').' GMT');
+	header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
+	header('Pragma: public');
+
+	echo '<?xml version="1.0" encoding="utf-8"?>'."\n";
+	echo '<feed xmlns="http://www.w3.org/2005/Atom">'."\n";
+
+	echo "\t".'<title type="html"><![CDATA['.escape_cdata($feed['title']).']]></title>'."\n";
+	echo "\t".'<link rel="self" href="'.pun_htmlspecialchars(get_current_url()).'"/>'."\n";
+	echo "\t".'<link href="'.pun_htmlspecialchars($feed['link']).'"/>'."\n";
+	echo "\t".'<updated>'.gmdate('Y-m-d\TH:i:s\Z', count($feed['items']) ? $feed['items'][0]['pubdate'] : time()).'</updated>'."\n";
+
+	if ($pun_config['o_show_version'] == '1')
+		echo "\t".'<generator version="'.$pun_config['o_cur_version'].'">FluxBB</generator>'."\n";
+	else
+		echo "\t".'<generator>FluxBB</generator>'."\n";
+
+	echo "\t".'<id>'.pun_htmlspecialchars($feed['link']).'</id>'."\n";
+
+	$content_tag = ($feed['type'] == 'posts') ? 'content' : 'summary';
+
+	foreach ($feed['items'] as $item)
+	{
+		echo "\t".'<entry>'."\n";
+		echo "\t\t".'<title type="html"><![CDATA['.escape_cdata($item['title']).']]></title>'."\n";
+		echo "\t\t".'<link rel="alternate" href="'.pun_htmlspecialchars($item['link']).'"/>'."\n";
+		echo "\t\t".'<'.$content_tag.' type="html"><![CDATA['.escape_cdata($item['description']).']]></'.$content_tag.'>'."\n";
+		echo "\t\t".'<author>'."\n";
+		echo "\t\t\t".'<name><![CDATA['.escape_cdata($item['author']['name']).']]></name>'."\n";
+
+		if (isset($item['author']['email']))
+			echo "\t\t\t".'<email><![CDATA['.escape_cdata($item['author']['email']).']]></email>'."\n";
+
+		if (isset($item['author']['uri']))
+			echo "\t\t\t".'<uri>'.pun_htmlspecialchars($item['author']['uri']).'</uri>'."\n";
+
+		echo "\t\t".'</author>'."\n";
+		echo "\t\t".'<updated>'.gmdate('Y-m-d\TH:i:s\Z', $item['pubdate']).'</updated>'."\n";
+
+		echo "\t\t".'<id>'.pun_htmlspecialchars($item['link']).'</id>'."\n";
+		echo "\t".'</entry>'."\n";
+	}
+
+	echo '</feed>'."\n";
+}
+
+
+//
+// Output $feed as XML
+//
+function output_xml($feed)
+{
+	global $lang_common, $pun_config;
+
+	// Send XML/no cache headers
+	header('Content-Type: application/xml; charset=utf-8');
+	header('Expires: '.gmdate('D, d M Y H:i:s').' GMT');
+	header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
+	header('Pragma: public');
+
+	echo '<?xml version="1.0" encoding="utf-8"?>'."\n";
+	echo '<source>'."\n";
+	echo "\t".'<url>'.pun_htmlspecialchars($feed['link']).'</url>'."\n";
+
+	$forum_tag = ($feed['type'] == 'posts') ? 'post' : 'topic';
+
+	foreach ($feed['items'] as $item)
+	{
+		echo "\t".'<'.$forum_tag.' id="'.$item['id'].'">'."\n";
+
+		echo "\t\t".'<title><![CDATA['.escape_cdata($item['title']).']]></title>'."\n";
+		echo "\t\t".'<link>'.pun_htmlspecialchars($item['link']).'</link>'."\n";
+		echo "\t\t".'<content><![CDATA['.escape_cdata($item['description']).']]></content>'."\n";
+		echo "\t\t".'<author>'."\n";
+		echo "\t\t\t".'<name><![CDATA['.escape_cdata($item['author']['name']).']]></name>'."\n";
+
+		if (isset($item['author']['email']))
+			echo "\t\t\t".'<email><![CDATA['.escape_cdata($item['author']['email']).']]></email>'."\n";
+
+		if (isset($item['author']['uri']))
+			echo "\t\t\t".'<uri>'.pun_htmlspecialchars($item['author']['uri']).'</uri>'."\n";
+
+		echo "\t\t".'</author>'."\n";
+		echo "\t\t".'<posted>'.gmdate('r', $item['pubdate']).'</posted>'."\n";
+
+		echo "\t".'</'.$forum_tag.'>'."\n";
+	}
+
+	echo '</source>'."\n";
+}
+
+
+//
+// Output $feed as HTML (using <li> tags)
+//
+function output_html($feed)
+{
+
+	// Send the Content-type header in case the web server is setup to send something else
+	header('Content-type: text/html; charset=utf-8');
+	header('Expires: '.gmdate('D, d M Y H:i:s').' GMT');
+	header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
+	header('Pragma: public');
+
+	foreach ($feed['items'] as $item)
+	{
+		if (utf8_strlen($item['title']) > FORUM_EXTERN_MAX_SUBJECT_LENGTH)
+			$subject_truncated = pun_htmlspecialchars(pun_trim(utf8_substr($item['title'], 0, (FORUM_EXTERN_MAX_SUBJECT_LENGTH - 5)))).' …';
+		else
+			$subject_truncated = pun_htmlspecialchars($item['title']);
+
+		echo '<li><a href="'.pun_htmlspecialchars($item['link']).'" title="'.pun_htmlspecialchars($item['title']).'">'.$subject_truncated.'</a></li>'."\n";
+	}
+}
+
+// Show recent discussions
+if ($action == 'feed')
+{
+	require PUN_ROOT.'include/parser.php';
+
+	// Determine what type of feed to output
+	$type = isset($_GET['type']) ? strtolower($_GET['type']) : 'html';
+	if (!in_array($type, array('html', 'rss', 'atom', 'xml')))
+		$type = 'html';
+
+	$show = isset($_GET['show']) ? intval($_GET['show']) : 15;
+	if ($show < 1 || $show > 50)
+		$show = 15;
+
+	// Was a topic ID supplied?
+	if (isset($_GET['tid']))
+	{
+		$tid = intval($_GET['tid']);
+
+		// Fetch topic subject
+		$result = $db->query('SELECT t.subject, t.first_post_id FROM '.$db->prefix.'topics AS t 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 AND t.id='.$tid) or error('Unable to fetch topic info', __FILE__, __LINE__, $db->error());
+		if (!$db->num_rows($result))
+		{
+			http_authenticate_user();
+			exit($lang_common['Bad request']);
+		}
+
+		$cur_topic = $db->fetch_assoc($result);
+
+		if ($pun_config['o_censoring'] == '1')
+			$cur_topic['subject'] = censor_words($cur_topic['subject']);
+
+		// Setup the feed
+		$feed = array(
+			'title' 		=>	$pun_config['o_board_title'].$lang_common['Title separator'].$cur_topic['subject'],
+			'link'			=>	get_base_url(true).'/viewtopic.php?id='.$tid,
+			'description'		=>	sprintf($lang_common['RSS description topic'], $cur_topic['subject']),
+			'items'			=>	array(),
+			'type'			=>	'posts'
+		);
+
+		// Fetch $show posts
+		$result = $db->query('SELECT p.id, p.poster, p.message, p.hide_smilies, p.posted, p.poster_id, u.email_setting, u.email, p.poster_email FROM '.$db->prefix.'posts AS p INNER JOIN '.$db->prefix.'users AS u ON u.id=p.poster_id WHERE p.topic_id='.$tid.' ORDER BY p.posted DESC LIMIT '.$show) or error('Unable to fetch post info', __FILE__, __LINE__, $db->error());
+		while ($cur_post = $db->fetch_assoc($result))
+		{
+			$cur_post['message'] = parse_message($cur_post['message'], $cur_post['hide_smilies']);
+
+			$item = array(
+				'id'			=>	$cur_post['id'],
+				'title'			=>	$cur_topic['first_post_id'] == $cur_post['id'] ? $cur_topic['subject'] : $lang_common['RSS reply'].$cur_topic['subject'],
+				'link'			=>	get_base_url(true).'/viewtopic.php?pid='.$cur_post['id'].'#p'.$cur_post['id'],
+				'description'		=>	$cur_post['message'],
+				'author'		=>	array(
+					'name'	=> $cur_post['poster'],
+				),
+				'pubdate'		=>	$cur_post['posted']
+			);
+
+			if ($cur_post['poster_id'] > 1)
+			{
+				if ($cur_post['email_setting'] == '0' && !$pun_user['is_guest'])
+					$item['author']['email'] = $cur_post['email'];
+
+				$item['author']['uri'] = get_base_url(true).'/profile.php?id='.$cur_post['poster_id'];
+			}
+			else if ($cur_post['poster_email'] != '' && !$pun_user['is_guest'])
+				$item['author']['email'] = $cur_post['poster_email'];
+
+			$feed['items'][] = $item;
+		}
+
+		$output_func = 'output_'.$type;
+		$output_func($feed);
+	}
+	else
+	{
+		$order_posted = isset($_GET['order']) && strtolower($_GET['order']) == 'posted';
+		$forum_name = '';
+		$forum_sql = '';
+
+		// Were any forum IDs supplied?
+		if (isset($_GET['fid']) && is_scalar($_GET['fid']) && $_GET['fid'] != '')
+		{
+			$fids = explode(',', pun_trim($_GET['fid']));
+			$fids = array_map('intval', $fids);
+
+			if (!empty($fids))
+				$forum_sql .= ' AND t.forum_id IN('.implode(',', $fids).')';
+
+			if (count($fids) == 1)
+			{
+				// Fetch forum name
+				$result = $db->query('SELECT f.forum_name FROM '.$db->prefix.'forums AS f 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.id='.$fids[0]) or error('Unable to fetch forum name', __FILE__, __LINE__, $db->error());
+				if ($db->num_rows($result))
+					$forum_name = $lang_common['Title separator'].$db->result($result);
+			}
+		}
+
+		// Any forum IDs to exclude?
+		if (isset($_GET['nfid']) && is_scalar($_GET['nfid']) && $_GET['nfid'] != '')
+		{
+			$nfids = explode(',', pun_trim($_GET['nfid']));
+			$nfids = array_map('intval', $nfids);
+
+			if (!empty($nfids))
+				$forum_sql .= ' AND t.forum_id NOT IN('.implode(',', $nfids).')';
+		}
+
+		// 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]));
+
+		// Load cached feed
+		if (isset($cache_id) && file_exists(FORUM_CACHE_DIR.'cache_'.$cache_id.'.php'))
+			include FORUM_CACHE_DIR.'cache_'.$cache_id.'.php';
+
+		$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'
+			);
+
+			// 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))
+			{
+				$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);
+
+				if (function_exists('apc_delete_file'))
+					@apc_delete_file(FORUM_CACHE_DIR.'cache_'.$cache_id.'.php');
+			}
+		}
+
+		// 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;
+		$output_func($feed);
+	}
+
+	exit;
+}
+
+// Show users online
+else if ($action == 'online' || $action == 'online_full')
+{
+	// Load the index.php language file
+	require PUN_ROOT.'lang/'.$pun_config['o_default_lang'].'/index.php';
+
+	// Fetch users online info and generate strings for output
+	$num_guests = $num_users = 0;
+	$users = array();
+
+	$result = $db->query('SELECT user_id, ident FROM '.$db->prefix.'online WHERE idle=0 ORDER BY ident', true) or error('Unable to fetch online list', __FILE__, __LINE__, $db->error());
+
+	while ($pun_user_online = $db->fetch_assoc($result))
+	{
+		if ($pun_user_online['user_id'] > 1)
+		{
+			$users[] = ($pun_user['g_view_users'] == '1') ? '<a href="'.pun_htmlspecialchars(get_base_url(true)).'/profile.php?id='.$pun_user_online['user_id'].'">'.pun_htmlspecialchars($pun_user_online['ident']).'</a>' : pun_htmlspecialchars($pun_user_online['ident']);
+			++$num_users;
+		}
+		else
+			++$num_guests;
+	}
+
+	// Send the Content-type header in case the web server is setup to send something else
+	header('Content-type: text/html; charset=utf-8');
+	header('Expires: '.gmdate('D, d M Y H:i:s').' GMT');
+	header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
+	header('Pragma: public');
+
+	echo sprintf($lang_index['Guests online'], forum_number_format($num_guests)).'<br />'."\n";
+
+	if ($action == 'online_full' && !empty($users))
+		echo sprintf($lang_index['Users online'], implode(', ', $users)).'<br />'."\n";
+	else
+		echo sprintf($lang_index['Users online'], forum_number_format($num_users)).'<br />'."\n";
+
+	exit;
+}
+
+// Show board statistics
+else if ($action == 'stats')
+{
+	// Load the index.php language file
+	require PUN_ROOT.'lang/'.$pun_config['o_default_lang'].'/index.php';
+
+	// Collect some statistics from the database
+	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';
+
+		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);
+
+	// Send the Content-type header in case the web server is setup to send something else
+	header('Content-type: text/html; charset=utf-8');
+	header('Expires: '.gmdate('D, d M Y H:i:s').' GMT');
+	header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
+	header('Pragma: public');
+
+	echo sprintf($lang_index['No of users'], forum_number_format($stats['total_users'])).'<br />'."\n";
+	echo sprintf($lang_index['Newest user'], (($pun_user['g_view_users'] == '1') ? '<a href="'.pun_htmlspecialchars(get_base_url(true)).'/profile.php?id='.$stats['last_user']['id'].'">'.pun_htmlspecialchars($stats['last_user']['username']).'</a>' : pun_htmlspecialchars($stats['last_user']['username']))).'<br />'."\n";
+	echo sprintf($lang_index['No of topics'], forum_number_format($stats['total_topics'])).'<br />'."\n";
+	echo sprintf($lang_index['No of posts'], forum_number_format($stats['total_posts'])).'<br />'."\n";
+
+	exit;
+}
+
+// If we end up here, the script was called with some wacky parameters
+exit($lang_common['Bad request']);
diff --git a/footer.php b/footer.php
new file mode 100644
index 0000000..b6b8a7a
--- /dev/null
+++ b/footer.php
@@ -0,0 +1,163 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// Make sure no one attempts to run this script "directly"
+if (!defined('PUN'))
+	exit;
+
+$tpl_temp = trim(ob_get_contents());
+$tpl_main = str_replace('<pun_main>', $tpl_temp, $tpl_main);
+ob_end_clean();
+// END SUBST - <pun_main>
+
+
+// START SUBST - <pun_footer>
+ob_start();
+
+?>
+<div id="brdfooter" class="block">
+	<h2><span><?php echo $lang_common['Board footer'] ?></span></h2>
+	<div class="box">
+<?php
+
+if (isset($footer_style) && ($footer_style == 'viewforum' || $footer_style == 'viewtopic') && $is_admmod)
+{
+	echo "\t\t".'<div id="modcontrols" class="inbox">'."\n";
+
+	if ($footer_style == 'viewforum')
+	{
+		echo "\t\t\t".'<dl>'."\n";
+		echo "\t\t\t\t".'<dt><strong>'.$lang_forum['Mod controls'].'</strong></dt>'."\n";
+		echo "\t\t\t\t".'<dd><span><a href="moderate.php?fid='.$forum_id.'&amp;p='.$p.'">'.$lang_common['Moderate forum'].'</a></span></dd>'."\n";
+		echo "\t\t\t".'</dl>'."\n";
+	}
+	else if ($footer_style == 'viewtopic')
+	{
+		echo "\t\t\t".'<dl>'."\n";
+		echo "\t\t\t\t".'<dt><strong>'.$lang_topic['Mod controls'].'</strong></dt>'."\n";
+		echo "\t\t\t\t".'<dd><span><a href="moderate.php?fid='.$forum_id.'&amp;tid='.$id.'&amp;p='.$p.'">'.$lang_common['Moderate topic'].'</a></span></dd>'."\n";
+		echo "\t\t\t\t".'<dd><span><a href="moderate.php?fid='.$forum_id.'&amp;move_topics='.$id.'">'.$lang_common['Move topic'].'</a></span></dd>'."\n";
+
+		if ($cur_topic['closed'] == '1')
+			echo "\t\t\t\t".'<dd><span><a href="moderate.php?fid='.$forum_id.'&amp;open='.$id.'">'.$lang_common['Open topic'].'</a></span></dd>'."\n";
+		else
+			echo "\t\t\t\t".'<dd><span><a href="moderate.php?fid='.$forum_id.'&amp;close='.$id.'">'.$lang_common['Close topic'].'</a></span></dd>'."\n";
+
+		if ($cur_topic['sticky'] == '1')
+			echo "\t\t\t\t".'<dd><span><a href="moderate.php?fid='.$forum_id.'&amp;unstick='.$id.'">'.$lang_common['Unstick topic'].'</a></span></dd>'."\n";
+		else
+			echo "\t\t\t\t".'<dd><span><a href="moderate.php?fid='.$forum_id.'&amp;stick='.$id.'">'.$lang_common['Stick topic'].'</a></span></dd>'."\n";
+
+		echo "\t\t\t".'</dl>'."\n";
+	}
+
+	echo "\t\t\t".'<div class="clearer"></div>'."\n\t\t".'</div>'."\n";
+}
+
+?>
+		<div id="brdfooternav" class="inbox">
+<?php
+
+echo "\t\t\t".'<div class="conl">'."\n";
+
+// Display the "Jump to" drop list
+if ($pun_config['o_quickjump'] == '1')
+{
+	// Load cached quick jump
+	if (file_exists(FORUM_CACHE_DIR.'cache_quickjump_'.$pun_user['g_id'].'.php'))
+		include FORUM_CACHE_DIR.'cache_quickjump_'.$pun_user['g_id'].'.php';
+
+	if (!defined('PUN_QJ_LOADED'))
+	{
+		if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+			require PUN_ROOT.'include/cache.php';
+
+		generate_quickjump_cache($pun_user['g_id']);
+		require FORUM_CACHE_DIR.'cache_quickjump_'.$pun_user['g_id'].'.php';
+	}
+}
+
+echo "\t\t\t".'</div>'."\n";
+
+?>
+			<div class="conr">
+<?php
+
+// If no footer style has been specified, we use the default (only copyright/debug info)
+$footer_style = isset($footer_style) ? $footer_style : NULL;
+
+if ($footer_style == 'index')
+{
+	if ($pun_config['o_feed_type'] == '1')
+		echo "\t\t\t\t".'<p id="feedlinks"><span class="rss"><a href="extern.php?action=feed&amp;type=rss">'.$lang_common['RSS active topics feed'].'</a></span></p>'."\n";
+	else if ($pun_config['o_feed_type'] == '2')
+		echo "\t\t\t\t".'<p id="feedlinks"><span class="atom"><a href="extern.php?action=feed&amp;type=atom">'.$lang_common['Atom active topics feed'].'</a></span></p>'."\n";
+}
+else if ($footer_style == 'viewforum')
+{
+	if ($pun_config['o_feed_type'] == '1')
+		echo "\t\t\t\t".'<p id="feedlinks"><span class="rss"><a href="extern.php?action=feed&amp;fid='.$forum_id.'&amp;type=rss">'.$lang_common['RSS forum feed'].'</a></span></p>'."\n";
+	else if ($pun_config['o_feed_type'] == '2')
+		echo "\t\t\t\t".'<p id="feedlinks"><span class="atom"><a href="extern.php?action=feed&amp;fid='.$forum_id.'&amp;type=atom">'.$lang_common['Atom forum feed'].'</a></span></p>'."\n";
+}
+else if ($footer_style == 'viewtopic')
+{
+	if ($pun_config['o_feed_type'] == '1')
+		echo "\t\t\t\t".'<p id="feedlinks"><span class="rss"><a href="extern.php?action=feed&amp;tid='.$id.'&amp;type=rss">'.$lang_common['RSS topic feed'].'</a></span></p>'."\n";
+	else if ($pun_config['o_feed_type'] == '2')
+		echo "\t\t\t\t".'<p id="feedlinks"><span class="atom"><a href="extern.php?action=feed&amp;tid='.$id.'&amp;type=atom">'.$lang_common['Atom topic feed'].'</a></span></p>'."\n";
+}
+
+?>
+				<p id="poweredby"><?php printf($lang_common['Powered by'], '<a href="http://fluxbb.org/">FluxBB</a>'.(($pun_config['o_show_version'] == '1') ? ' '.$pun_config['o_cur_version'] : '')) ?></p>
+			</div>
+			<div class="clearer"></div>
+		</div>
+	</div>
+</div>
+<?php
+
+// Display debug info (if enabled/defined)
+if (defined('PUN_DEBUG'))
+{
+	echo '<p id="debugtime">[ ';
+
+	// Calculate script generation time
+	$time_diff = sprintf('%.3f', get_microtime() - $pun_start);
+	echo sprintf($lang_common['Querytime'], $time_diff, $db->get_num_queries());
+
+	if (function_exists('memory_get_usage'))
+	{
+		echo ' - '.sprintf($lang_common['Memory usage'], file_size(memory_get_usage()));
+
+		if (function_exists('memory_get_peak_usage'))
+			echo ' '.sprintf($lang_common['Peak usage'], file_size(memory_get_peak_usage()));
+	}
+
+	echo ' ]</p>'."\n";
+}
+
+
+// End the transaction
+$db->end_transaction();
+
+// Display executed queries (if enabled)
+if (defined('PUN_SHOW_QUERIES'))
+	display_saved_queries();
+
+$tpl_temp = trim(ob_get_contents());
+$tpl_main = str_replace('<pun_footer>', $tpl_temp, $tpl_main);
+ob_end_clean();
+// END SUBST - <pun_footer>
+
+
+// Close the db connection (and free up any result data)
+$db->close();
+
+// Spit out the page
+exit($tpl_main);
diff --git a/header.php b/header.php
new file mode 100644
index 0000000..b4905aa
--- /dev/null
+++ b/header.php
@@ -0,0 +1,317 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// Make sure no one attempts to run this script "directly"
+if (!defined('PUN'))
+	exit;
+
+// 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');
+
+// Load the template
+if (defined('PUN_ADMIN_CONSOLE'))
+	$tpl_file = 'admin.tpl';
+else if (defined('PUN_HELP'))
+	$tpl_file = 'help.tpl';
+else
+	$tpl_file = 'main.tpl';
+
+if (file_exists(PUN_ROOT.'style/'.$pun_user['style'].'/'.$tpl_file))
+{
+	$tpl_file = PUN_ROOT.'style/'.$pun_user['style'].'/'.$tpl_file;
+	$tpl_inc_dir = PUN_ROOT.'style/'.$pun_user['style'].'/';
+}
+else
+{
+	$tpl_file = PUN_ROOT.'include/template/'.$tpl_file;
+	$tpl_inc_dir = PUN_ROOT.'include/user/';
+}
+
+$tpl_main = file_get_contents($tpl_file);
+
+// START SUBST - <pun_include "*">
+preg_match_all('%<pun_include "([^/\\\\]*?)\.(php[45]?|inc|html?|txt)">%i', $tpl_main, $pun_includes, PREG_SET_ORDER);
+
+foreach ($pun_includes as $cur_include)
+{
+	ob_start();
+
+	// Allow for overriding user includes, too.
+	if (file_exists($tpl_inc_dir.$cur_include[1].'.'.$cur_include[2]))
+		require $tpl_inc_dir.$cur_include[1].'.'.$cur_include[2];
+	else if (file_exists(PUN_ROOT.'include/user/'.$cur_include[1].'.'.$cur_include[2]))
+		require PUN_ROOT.'include/user/'.$cur_include[1].'.'.$cur_include[2];
+	else
+		error(sprintf($lang_common['Pun include error'], htmlspecialchars($cur_include[0]), basename($tpl_file)));
+
+	$tpl_temp = ob_get_contents();
+	$tpl_main = str_replace($cur_include[0], $tpl_temp, $tpl_main);
+	ob_end_clean();
+}
+// END SUBST - <pun_include "*">
+
+
+// START SUBST - <pun_language>
+$tpl_main = str_replace('<pun_language>', $lang_common['lang_identifier'], $tpl_main);
+// END SUBST - <pun_language>
+
+
+// START SUBST - <pun_content_direction>
+$tpl_main = str_replace('<pun_content_direction>', $lang_common['lang_direction'], $tpl_main);
+// END SUBST - <pun_content_direction>
+
+
+// START SUBST - <pun_head>
+ob_start();
+
+// Define $p if its not set to avoid a PHP notice
+$p = isset($p) ? $p : null;
+
+// Is this a page that we want search index spiders to index?
+if (!defined('PUN_ALLOW_INDEX'))
+	echo '<meta name="ROBOTS" content="NOINDEX, FOLLOW" />'."\n";
+
+?>
+<title><?php echo generate_page_title($page_title, $p) ?></title>
+<link rel="stylesheet" type="text/css" href="style/<?php echo $pun_user['style'].'.css' ?>" />
+<?php
+
+if (defined('PUN_ADMIN_CONSOLE'))
+{
+	if (file_exists(PUN_ROOT.'style/'.$pun_user['style'].'/base_admin.css'))
+		echo '<link rel="stylesheet" type="text/css" href="style/'.$pun_user['style'].'/base_admin.css" />'."\n";
+	else
+		echo '<link rel="stylesheet" type="text/css" href="style/imports/base_admin.css" />'."\n";
+}
+
+if (isset($required_fields))
+{
+	// Output JavaScript to validate form (make sure required fields are filled out)
+
+?>
+<script type="text/javascript">
+/* <![CDATA[ */
+function process_form(the_form)
+{
+	var required_fields = {
+<?php
+	// Output a JavaScript object with localised field names
+	$tpl_temp = count($required_fields);
+	foreach ($required_fields as $elem_orig => $elem_trans)
+	{
+		echo "\t\t\"".$elem_orig.'": "'.addslashes(str_replace('&#160;', ' ', $elem_trans));
+		if (--$tpl_temp) echo "\",\n";
+		else echo "\"\n\t};\n";
+	}
+?>
+	if (document.all || document.getElementById)
+	{
+		for (var i = 0; i < the_form.length; ++i)
+		{
+			var elem = the_form.elements[i];
+			if (elem.name && required_fields[elem.name] && !elem.value && elem.type && (/^(?:text(?:area)?|password|file)$/i.test(elem.type)))
+			{
+				alert('"' + required_fields[elem.name] + '" <?php echo $lang_common['required field'] ?>');
+				elem.focus();
+				return false;
+			}
+		}
+	}
+	return true;
+}
+/* ]]> */
+</script>
+<?php
+
+}
+
+// JavaScript tricks for IE6 and older
+echo '<!--[if lte IE 6]><script type="text/javascript" src="style/imports/minmax.js"></script><![endif]-->'."\n";
+
+if (isset($page_head))
+	echo implode("\n", $page_head)."\n";
+
+$tpl_temp = trim(ob_get_contents());
+$tpl_main = str_replace('<pun_head>', $tpl_temp, $tpl_main);
+ob_end_clean();
+// END SUBST - <pun_head>
+
+
+// START SUBST - <body>
+if (isset($focus_element))
+{
+	$tpl_main = str_replace('<body onload="', '<body onload="document.getElementById(\''.$focus_element[0].'\').elements[\''.$focus_element[1].'\'].focus();', $tpl_main);
+	$tpl_main = str_replace('<body>', '<body onload="document.getElementById(\''.$focus_element[0].'\').elements[\''.$focus_element[1].'\'].focus()">', $tpl_main);
+}
+// END SUBST - <body>
+
+
+// START SUBST - <pun_page>
+$tpl_main = str_replace('<pun_page>', htmlspecialchars(basename($_SERVER['PHP_SELF'], '.php')), $tpl_main);
+// END SUBST - <pun_page>
+
+
+// START SUBST - <pun_title>
+$tpl_main = str_replace('<pun_title>', '<h1><a href="index.php">'.pun_htmlspecialchars($pun_config['o_board_title']).'</a></h1>', $tpl_main);
+// END SUBST - <pun_title>
+
+
+// START SUBST - <pun_desc>
+$tpl_main = str_replace('<pun_desc>', '<div id="brddesc">'.$pun_config['o_board_desc'].'</div>', $tpl_main);
+// END SUBST - <pun_desc>
+
+
+// START SUBST - <pun_navlinks>
+$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&amp;id='.$pun_user['id'].'&amp;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>
+
+
+// START SUBST - <pun_status>
+$page_statusinfo = $page_topicsearches = array();
+
+if ($pun_user['is_guest'])
+	$page_statusinfo = '<p class="conl">'.$lang_common['Not logged in'].'</p>';
+else
+{
+	$page_statusinfo[] = '<li><span>'.$lang_common['Logged in as'].' <strong>'.pun_htmlspecialchars($pun_user['username']).'</strong></span></li>';
+	$page_statusinfo[] = '<li><span>'.sprintf($lang_common['Last visit'], format_time($pun_user['last_visit'])).'</span></li>';
+
+	if ($pun_user['is_admmod'])
+	{
+		if ($pun_config['o_report_method'] == '0' || $pun_config['o_report_method'] == '2')
+		{
+			$result_header = $db->query('SELECT 1 FROM '.$db->prefix.'reports WHERE zapped IS NULL') or error('Unable to fetch reports info', __FILE__, __LINE__, $db->error());
+
+			if ($db->result($result_header))
+				$page_statusinfo[] = '<li class="reportlink"><span><strong><a href="admin_reports.php">'.$lang_common['New reports'].'</a></strong></span></li>';
+		}
+
+		if ($pun_config['o_maintenance'] == '1')
+			$page_statusinfo[] = '<li class="maintenancelink"><span><strong><a href="admin_options.php#maintenance">'.$lang_common['Maintenance mode enabled'].'</a></strong></span></li>';
+	}
+
+	if ($pun_user['g_read_board'] == '1' && $pun_user['g_search'] == '1')
+	{
+		$page_topicsearches[] = '<a href="search.php?action=show_replies" title="'.$lang_common['Show posted topics'].'">'.$lang_common['Posted topics'].'</a>';
+		$page_topicsearches[] = '<a href="search.php?action=show_new" title="'.$lang_common['Show new posts'].'">'.$lang_common['New posts header'].'</a>';
+	}
+}
+
+// Quick searches
+if ($pun_user['g_read_board'] == '1' && $pun_user['g_search'] == '1')
+{
+	$page_topicsearches[] = '<a href="search.php?action=show_recent" title="'.$lang_common['Show active topics'].'">'.$lang_common['Active topics'].'</a>';
+	$page_topicsearches[] = '<a href="search.php?action=show_unanswered" title="'.$lang_common['Show unanswered topics'].'">'.$lang_common['Unanswered topics'].'</a>';
+}
+
+
+// Generate all that jazz
+$tpl_temp = '<div id="brdwelcome" class="inbox">';
+
+// The status information
+if (is_array($page_statusinfo))
+{
+	$tpl_temp .= "\n\t\t\t".'<ul class="conl">';
+	$tpl_temp .= "\n\t\t\t\t".implode("\n\t\t\t\t", $page_statusinfo);
+	$tpl_temp .= "\n\t\t\t".'</ul>';
+}
+else
+	$tpl_temp .= "\n\t\t\t".$page_statusinfo;
+
+// Generate quicklinks
+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>';
+	$tpl_temp .= "\n\t\t\t".'</ul>';
+}
+
+$tpl_temp .= "\n\t\t\t".'<div class="clearer"></div>'."\n\t\t".'</div>';
+
+$tpl_main = str_replace('<pun_status>', $tpl_temp, $tpl_main);
+// END SUBST - <pun_status>
+
+
+// START SUBST - <pun_announcement>
+if ($pun_user['g_read_board'] == '1' && $pun_config['o_announcement'] == '1')
+{
+	ob_start();
+
+?>
+<div id="announce" class="block">
+	<div class="hd"><h2><span><?php echo $lang_common['Announcement'] ?></span></h2></div>
+	<div class="box">
+		<div id="announce-block" class="inbox">
+			<div class="usercontent"><?php echo $pun_config['o_announcement_message'] ?></div>
+		</div>
+	</div>
+</div>
+<?php
+
+	$tpl_temp = trim(ob_get_contents());
+	$tpl_main = str_replace('<pun_announcement>', $tpl_temp, $tpl_main);
+	ob_end_clean();
+}
+else
+	$tpl_main = str_replace('<pun_announcement>', '', $tpl_main);
+// END SUBST - <pun_announcement>
+
+
+// START SUBST - <pun_main>
+ob_start();
+
+
+define('PUN_HEADER', 1);
diff --git a/help.php b/help.php
new file mode 100644
index 0000000..caa7c23
--- /dev/null
+++ b/help.php
@@ -0,0 +1,154 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// Tell header.php to use the help template
+define('PUN_HELP', 1);
+
+define('PUN_ROOT', dirname(__FILE__).'/');
+require PUN_ROOT.'include/common.php';
+
+
+if ($pun_user['g_read_board'] == '0')
+	message($lang_common['No view'], false, '403 Forbidden');
+
+
+// Load the help.php language file
+require PUN_ROOT.'lang/'.$pun_user['language'].'/help.php';
+
+
+$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_help['Help']);
+define('PUN_ACTIVE_PAGE', 'help');
+require PUN_ROOT.'header.php';
+
+?>
+<h2><span><?php echo $lang_help['BBCode'] ?></span></h2>
+<div class="box">
+	<div class="inbox">
+		<p><a name="bbcode"></a><?php echo $lang_help['BBCode info 1'] ?></p>
+		<p><?php echo $lang_help['BBCode info 2'] ?></p>
+	</div>
+</div>
+<h2><span><?php echo $lang_help['Text style'] ?></span></h2>
+<div class="box">
+	<div class="inbox">
+		<p><?php echo $lang_help['Text style info'] ?></p>
+		<p><code>[b]<?php echo $lang_help['Bold text'] ?>[/b]</code> <?php echo $lang_help['produces'] ?> <samp><strong><?php echo $lang_help['Bold text'] ?></strong></samp></p>
+		<p><code>[u]<?php echo $lang_help['Underlined text'] ?>[/u]</code> <?php echo $lang_help['produces'] ?> <samp><span class="bbu"><?php echo $lang_help['Underlined text'] ?></span></samp></p>
+		<p><code>[i]<?php echo $lang_help['Italic text'] ?>[/i]</code> <?php echo $lang_help['produces'] ?> <samp><em><?php echo $lang_help['Italic text'] ?></em></samp></p>
+		<p><code>[s]<?php echo $lang_help['Strike-through text'] ?>[/s]</code> <?php echo $lang_help['produces'] ?> <samp><span class="bbs"><?php echo $lang_help['Strike-through text'] ?></span></samp></p>
+		<p><code>[del]<?php echo $lang_help['Deleted text'] ?>[/del]</code> <?php echo $lang_help['produces'] ?> <samp><del><?php echo $lang_help['Deleted text'] ?></del></samp></p>
+		<p><code>[ins]<?php echo $lang_help['Inserted text'] ?>[/ins]</code> <?php echo $lang_help['produces'] ?> <samp><ins><?php echo $lang_help['Inserted text'] ?></ins></samp></p>
+		<p><code>[em]<?php echo $lang_help['Emphasised text'] ?>[/em]</code> <?php echo $lang_help['produces'] ?> <samp><em><?php echo $lang_help['Emphasised text'] ?></em></samp></p>
+		<p><code>[color=#FF0000]<?php echo $lang_help['Red text'] ?>[/color]</code> <?php echo $lang_help['produces'] ?> <samp><span style="color: #ff0000"><?php echo $lang_help['Red text'] ?></span></samp></p>
+		<p><code>[color=blue]<?php echo $lang_help['Blue text'] ?>[/color]</code> <?php echo $lang_help['produces'] ?> <samp><span style="color: blue"><?php echo $lang_help['Blue text'] ?></span></samp></p>
+		<p><code>[h]<?php echo $lang_help['Heading text'] ?>[/h]</code> <?php echo $lang_help['produces'] ?></p> <div class="postmsg"><h5><?php echo $lang_help['Heading text'] ?></h5></div>
+	</div>
+</div>
+<h2><span><?php echo $lang_help['Links and images'] ?></span></h2>
+<div class="box">
+	<div class="inbox">
+		<p><?php echo $lang_help['Links info'] ?></p>
+		<p><code>[url=<?php echo pun_htmlspecialchars(get_base_url(true).'/') ?>]<?php echo pun_htmlspecialchars($pun_config['o_board_title']) ?>[/url]</code> <?php echo $lang_help['produces'] ?> <samp><a href="<?php echo pun_htmlspecialchars(get_base_url(true).'/') ?>"><?php echo pun_htmlspecialchars($pun_config['o_board_title']) ?></a></samp></p>
+		<p><code>[url]<?php echo pun_htmlspecialchars(get_base_url(true).'/') ?>[/url]</code> <?php echo $lang_help['produces'] ?> <samp><a href="<?php echo pun_htmlspecialchars(get_base_url(true).'/') ?>"><?php echo pun_htmlspecialchars(get_base_url(true).'/') ?></a></samp></p>
+		<p><code>[url=/help.php]<?php echo $lang_help['This help page'] ?>[/url]</code> <?php echo $lang_help['produces'] ?> <samp><a href="<?php echo pun_htmlspecialchars(get_base_url(true).'/help.php') ?>"><?php echo $lang_help['This help page'] ?></a></samp></p>
+		<p><code>[email]myname@mydomain.com[/email]</code> <?php echo $lang_help['produces'] ?> <samp><a href="mailto:myname@mydomain.com">myname@mydomain.com</a></samp></p>
+		<p><code>[email=myname@mydomain.com]<?php echo $lang_help['My email address'] ?>[/email]</code> <?php echo $lang_help['produces'] ?> <samp><a href="mailto:myname@mydomain.com"><?php echo $lang_help['My email address'] ?></a></samp></p>
+		<p><code>[topic=1]<?php echo $lang_help['Test topic'] ?>[/topic]</code> <?php echo $lang_help['produces'] ?> <samp><a href="<?php echo pun_htmlspecialchars(get_base_url(true).'/viewtopic.php?id=1') ?>"><?php echo $lang_help['Test topic'] ?></a></samp></p>
+		<p><code>[topic]1[/topic]</code> <?php echo $lang_help['produces'] ?> <samp><a href="<?php echo pun_htmlspecialchars(get_base_url(true).'/viewtopic.php?id=1') ?>"><?php echo pun_htmlspecialchars(get_base_url(true).'/viewtopic.php?id=1') ?></a></samp></p>
+		<p><code>[post=1]<?php echo $lang_help['Test post'] ?>[/post]</code> <?php echo $lang_help['produces'] ?> <samp><a href="<?php echo pun_htmlspecialchars(get_base_url(true).'/viewtopic.php?pid=1#p1') ?>"><?php echo $lang_help['Test post'] ?></a></samp></p>
+		<p><code>[post]1[/post]</code> <?php echo $lang_help['produces'] ?> <samp><a href="<?php echo pun_htmlspecialchars(get_base_url(true).'/viewtopic.php?pid=1#p1') ?>"><?php echo pun_htmlspecialchars(get_base_url(true).'/viewtopic.php?pid=1#p1') ?></a></samp></p>
+		<p><code>[forum=1]<?php echo $lang_help['Test forum'] ?>[/forum]</code> <?php echo $lang_help['produces'] ?> <samp><a href="<?php echo pun_htmlspecialchars(get_base_url(true).'/viewforum.php?id=1') ?>"><?php echo $lang_help['Test forum'] ?></a></samp></p>
+		<p><code>[forum]1[/forum]</code> <?php echo $lang_help['produces'] ?> <samp><a href="<?php echo pun_htmlspecialchars(get_base_url(true).'/viewforum.php?id=1') ?>"><?php echo pun_htmlspecialchars(get_base_url(true).'/viewforum.php?id=1') ?></a></samp></p>
+		<p><code>[user=2]<?php echo $lang_help['Test user'] ?>[/user]</code> <?php echo $lang_help['produces'] ?> <samp><a href="<?php echo pun_htmlspecialchars(get_base_url(true).'/profile.php?id=2') ?>"><?php echo $lang_help['Test user'] ?></a></samp></p>
+		<p><code>[user]2[/user]</code> <?php echo $lang_help['produces'] ?> <samp><a href="<?php echo pun_htmlspecialchars(get_base_url(true).'/profile.php?id=2') ?>"><?php echo pun_htmlspecialchars(get_base_url(true).'/profile.php?id=2') ?></a></samp></p>
+	</div>
+	<div class="inbox">
+		<p><a name="img"></a><?php echo $lang_help['Images info'] ?></p>
+		<p><code>[img=<?php echo $lang_help['FluxBB bbcode test'] ?>]<?php echo pun_htmlspecialchars(get_base_url(true)) ?>/img/test.png[/img]</code> <?php echo $lang_help['produces'] ?> <samp><img style="height: 21px" src="<?php echo pun_htmlspecialchars(get_base_url(true)) ?>/img/test.png" alt="<?php echo $lang_help['FluxBB bbcode test'] ?>" /></samp></p>
+	</div>
+</div>
+<h2><span><?php echo $lang_help['Quotes'] ?></span></h2>
+<div class="box">
+	<div class="inbox">
+		<p><?php echo $lang_help['Quotes info'] ?></p>
+		<p><code>[quote=James]<?php echo $lang_help['Quote text'] ?>[/quote]</code></p>
+		<p><?php echo $lang_help['produces quote box'] ?></p>
+		<div class="postmsg">
+			<div class="quotebox"><cite>James <?php echo $lang_common['wrote'] ?></cite><blockquote><div><p><?php echo $lang_help['Quote text'] ?></p></div></blockquote></div>
+		</div>
+		<p><?php echo $lang_help['Quotes info 2'] ?></p>
+		<p><code>[quote]<?php echo $lang_help['Quote text'] ?>[/quote]</code></p>
+		<p><?php echo $lang_help['produces quote box'] ?></p>
+		<div class="postmsg">
+			<div class="quotebox"><blockquote><div><p><?php echo $lang_help['Quote text'] ?></p></div></blockquote></div>
+		</div>
+		<p><?php echo $lang_help['quote note'] ?></p>
+	</div>
+</div>
+<h2><span><?php echo $lang_help['Code'] ?></span></h2>
+<div class="box">
+	<div class="inbox">
+		<p><?php echo $lang_help['Code info'] ?></p>
+		<p><code>[code]<?php echo $lang_help['Code text'] ?>[/code]</code></p>
+		<p><?php echo $lang_help['produces code box'] ?></p>
+		<div class="postmsg">
+			<div class="codebox"><pre><code><?php echo $lang_help['Code text'] ?></code></pre></div>
+		</div>
+	</div>
+</div>
+<h2><span><?php echo $lang_help['Lists'] ?></span></h2>
+<div class="box">
+	<div class="inbox">
+		<p><a name="lists"></a><?php echo $lang_help['List info'] ?></p>
+		<p><code>[list][*]<?php echo $lang_help['List text 1'] ?>[/*][*]<?php echo $lang_help['List text 2'] ?>[/*][*]<?php echo $lang_help['List text 3'] ?>[/*][/list]</code>
+		<br /><span><?php echo $lang_help['produces list'] ?></span></p>
+		<div class="postmsg">
+			<ul><li><p><?php echo $lang_help['List text 1'] ?></p></li><li><p><?php echo $lang_help['List text 2'] ?></p></li><li><p><?php echo $lang_help['List text 3'] ?></p></li></ul>
+		</div>
+		<p><code>[list=1][*]<?php echo $lang_help['List text 1'] ?>[/*][*]<?php echo $lang_help['List text 2'] ?>[/*][*]<?php echo $lang_help['List text 3'] ?>[/*][/list]</code>
+		<br /><span><?php echo $lang_help['produces decimal list'] ?></span></p>
+		<div class="postmsg">
+			<ol class="decimal"><li><p><?php echo $lang_help['List text 1'] ?></p></li><li><p><?php echo $lang_help['List text 2'] ?></p></li><li><p><?php echo $lang_help['List text 3'] ?></p></li></ol>
+		</div>
+		<p><code>[list=a][*]<?php echo $lang_help['List text 1'] ?>[/*][*]<?php echo $lang_help['List text 2'] ?>[/*][*]<?php echo $lang_help['List text 3'] ?>[/*][/list]</code>
+		<br /><span><?php echo $lang_help['produces alpha list'] ?></span></p>
+		<div class="postmsg">
+			<ol class="alpha"><li><p><?php echo $lang_help['List text 1'] ?></p></li><li><p><?php echo $lang_help['List text 2'] ?></p></li><li><p><?php echo $lang_help['List text 3'] ?></p></li></ol>
+		</div>
+	</div>
+</div>
+<h2><span><?php echo $lang_help['Nested tags'] ?></span></h2>
+<div class="box">
+	<div class="inbox">
+		<p><?php echo $lang_help['Nested tags info'] ?></p>
+		<p><code>[b][u]<?php echo $lang_help['Bold, underlined text'] ?>[/u][/b]</code> <?php echo $lang_help['produces'] ?> <samp><strong><span class="bbu"><?php echo $lang_help['Bold, underlined text'] ?></span></strong></samp></p>
+	</div>
+</div>
+<h2><span><?php echo $lang_help['Smilies'] ?></span></h2>
+<div class="box">
+	<div class="inbox">
+		<p><a name="smilies"></a><?php echo $lang_help['Smilies info'] ?></p>
+<?php
+
+// Display the smiley set
+require PUN_ROOT.'include/parser.php';
+
+$smiley_groups = array();
+
+foreach ($smilies as $smiley_text => $smiley_img)
+	$smiley_groups[$smiley_img][] = $smiley_text;
+
+foreach ($smiley_groups as $smiley_img => $smiley_texts)
+	echo "\t\t".'<p><code>'.implode('</code> '.$lang_common['and'].' <code>', $smiley_texts).'</code> <span>'.$lang_help['produces'].'</span> <samp><img src="'.pun_htmlspecialchars(get_base_url(true)).'/img/smilies/'.$smiley_img.'" width="15" height="15" alt="'.$smiley_texts[0].'" /></samp></p>'."\n";
+
+?>
+	</div>
+</div>
+<?php
+
+require PUN_ROOT.'footer.php';
diff --git a/img/avatars/index.html b/img/avatars/index.html
new file mode 100644
index 0000000..89337b2
--- /dev/null
+++ b/img/avatars/index.html
@@ -0,0 +1 @@
+<html><head><title>.</title></head><body>.</body></html>
diff --git a/img/index.html b/img/index.html
new file mode 100644
index 0000000..89337b2
--- /dev/null
+++ b/img/index.html
@@ -0,0 +1 @@
+<html><head><title>.</title></head><body>.</body></html>
diff --git a/img/smilies/big_smile.png b/img/smilies/big_smile.png
new file mode 100644
index 0000000..dad2fe8
Binary files /dev/null and b/img/smilies/big_smile.png differ
diff --git a/img/smilies/cool.png b/img/smilies/cool.png
new file mode 100644
index 0000000..6b74528
Binary files /dev/null and b/img/smilies/cool.png differ
diff --git a/img/smilies/hmm.png b/img/smilies/hmm.png
new file mode 100644
index 0000000..2dbc8aa
Binary files /dev/null and b/img/smilies/hmm.png differ
diff --git a/img/smilies/index.html b/img/smilies/index.html
new file mode 100644
index 0000000..89337b2
--- /dev/null
+++ b/img/smilies/index.html
@@ -0,0 +1 @@
+<html><head><title>.</title></head><body>.</body></html>
diff --git a/img/smilies/lol.png b/img/smilies/lol.png
new file mode 100644
index 0000000..45cb27e
Binary files /dev/null and b/img/smilies/lol.png differ
diff --git a/img/smilies/mad.png b/img/smilies/mad.png
new file mode 100644
index 0000000..3a26df0
Binary files /dev/null and b/img/smilies/mad.png differ
diff --git a/img/smilies/neutral.png b/img/smilies/neutral.png
new file mode 100644
index 0000000..2471da5
Binary files /dev/null and b/img/smilies/neutral.png differ
diff --git a/img/smilies/roll.png b/img/smilies/roll.png
new file mode 100644
index 0000000..f0ef64b
Binary files /dev/null and b/img/smilies/roll.png differ
diff --git a/img/smilies/sad.png b/img/smilies/sad.png
new file mode 100644
index 0000000..f03ae85
Binary files /dev/null and b/img/smilies/sad.png differ
diff --git a/img/smilies/smile.png b/img/smilies/smile.png
new file mode 100644
index 0000000..27299dc
Binary files /dev/null and b/img/smilies/smile.png differ
diff --git a/img/smilies/tongue.png b/img/smilies/tongue.png
new file mode 100644
index 0000000..5de9741
Binary files /dev/null and b/img/smilies/tongue.png differ
diff --git a/img/smilies/wink.png b/img/smilies/wink.png
new file mode 100644
index 0000000..6807feb
Binary files /dev/null and b/img/smilies/wink.png differ
diff --git a/img/smilies/yikes.png b/img/smilies/yikes.png
new file mode 100644
index 0000000..64a2c70
Binary files /dev/null and b/img/smilies/yikes.png differ
diff --git a/img/test.png b/img/test.png
new file mode 100644
index 0000000..34eb90a
Binary files /dev/null and b/img/test.png differ
diff --git a/include/cache.php b/include/cache.php
new file mode 100644
index 0000000..e4b9112
--- /dev/null
+++ b/include/cache.php
@@ -0,0 +1,285 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// Make sure no one attempts to run this script "directly"
+if (!defined('PUN'))
+	exit;
+
+
+//
+// Generate the config cache PHP script
+//
+function generate_config_cache()
+{
+	global $db;
+
+	// Get the forum config from the DB
+	$result = $db->query('SELECT * FROM '.$db->prefix.'config', true) or error('Unable to fetch forum config', __FILE__, __LINE__, $db->error());
+
+	$output = array();
+	while ($cur_config_item = $db->fetch_row($result))
+		$output[$cur_config_item[0]] = $cur_config_item[1];
+
+	// Output config as PHP code
+	$fh = @fopen(FORUM_CACHE_DIR.'cache_config.php', 'wb');
+	if (!$fh)
+		error('Unable to write configuration 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_CONFIG_LOADED\', 1);'."\n\n".'$pun_config = '.var_export($output, true).';'."\n\n".'?>');
+
+	fclose($fh);
+
+	if (function_exists('apc_delete_file'))
+		@apc_delete_file(FORUM_CACHE_DIR.'cache_config.php');
+}
+
+
+//
+// Generate the bans cache PHP script
+//
+function generate_bans_cache()
+{
+	global $db;
+
+	// Get the ban list from the DB
+	$result = $db->query('SELECT * FROM '.$db->prefix.'bans', true) or error('Unable to fetch ban list', __FILE__, __LINE__, $db->error());
+
+	$output = array();
+	while ($cur_ban = $db->fetch_assoc($result))
+		$output[] = $cur_ban;
+
+	// Output ban list as PHP code
+	$fh = @fopen(FORUM_CACHE_DIR.'cache_bans.php', 'wb');
+	if (!$fh)
+		error('Unable to write bans 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_BANS_LOADED\', 1);'."\n\n".'$pun_bans = '.var_export($output, true).';'."\n\n".'?>');
+
+	fclose($fh);
+
+	if (function_exists('apc_delete_file'))
+		@apc_delete_file(FORUM_CACHE_DIR.'cache_bans.php');
+}
+
+
+//
+// Generate the ranks cache PHP script
+//
+function generate_ranks_cache()
+{
+	global $db;
+
+	// Get the rank list from the DB
+	$result = $db->query('SELECT * FROM '.$db->prefix.'ranks ORDER BY min_posts', true) or error('Unable to fetch rank list', __FILE__, __LINE__, $db->error());
+
+	$output = array();
+	while ($cur_rank = $db->fetch_assoc($result))
+		$output[] = $cur_rank;
+
+	// Output ranks list as PHP code
+	$fh = @fopen(FORUM_CACHE_DIR.'cache_ranks.php', 'wb');
+	if (!$fh)
+		error('Unable to write ranks 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_RANKS_LOADED\', 1);'."\n\n".'$pun_ranks = '.var_export($output, true).';'."\n\n".'?>');
+
+	fclose($fh);
+
+	if (function_exists('apc_delete_file'))
+		@apc_delete_file(FORUM_CACHE_DIR.'cache_ranks.php');
+}
+
+
+//
+// Generate quick jump cache PHP scripts
+//
+function generate_quickjump_cache($group_id = false)
+{
+	global $db, $lang_common, $pun_user;
+
+	$groups = array();
+
+	// If a group_id was supplied, we generate the quick jump cache for that group only
+	if ($group_id !== false)
+	{
+		// Is this group even allowed to read forums?
+		$result = $db->query('SELECT g_read_board FROM '.$db->prefix.'groups WHERE g_id='.$group_id) or error('Unable to fetch user group read permission', __FILE__, __LINE__, $db->error());
+		$read_board = $db->result($result);
+
+		$groups[$group_id] = $read_board;
+	}
+	else
+	{
+		// A group_id was not supplied, so we generate the quick jump cache for all groups
+		$result = $db->query('SELECT g_id, g_read_board FROM '.$db->prefix.'groups') or error('Unable to fetch user group list', __FILE__, __LINE__, $db->error());
+		$num_groups = $db->num_rows($result);
+
+		while ($row = $db->fetch_row($result))
+			$groups[$row[0]] = $row[1];
+	}
+
+	// Loop through the groups in $groups and output the cache for each of them
+	foreach ($groups as $group_id => $read_board)
+	{
+		// Output quick jump as PHP code
+		$fh = @fopen(FORUM_CACHE_DIR.'cache_quickjump_'.$group_id.'.php', 'wb');
+		if (!$fh)
+			error('Unable to write quick jump cache file to cache directory. Please make sure PHP has write access to the directory \''.pun_htmlspecialchars(FORUM_CACHE_DIR).'\'', __FILE__, __LINE__);
+
+		$output = '<?php'."\n\n".'if (!defined(\'PUN\')) exit;'."\n".'define(\'PUN_QJ_LOADED\', 1);'."\n".'$forum_id = isset($forum_id) ? $forum_id : 0;'."\n\n".'?>';
+
+		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') or error('Unable to fetch category/forum list', __FILE__, __LINE__, $db->error());
+
+			if ($db->num_rows($result))
+			{
+				$output .= "\t\t\t\t".'<form id="qjump" method="get" action="viewforum.php">'."\n\t\t\t\t\t".'<div><label><span><?php echo $lang_common[\'Jump to\'] ?>'.'<br /></span>'."\n\t\t\t\t\t".'<select name="id" onchange="window.location=(\'viewforum.php?id=\'+this.options[this.selectedIndex].value)">'."\n";
+
+				$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)
+							$output .= "\t\t\t\t\t\t".'</optgroup>'."\n";
+
+						$output .= "\t\t\t\t\t\t".'<optgroup label="'.pun_htmlspecialchars($cur_forum['cat_name']).'">'."\n";
+						$cur_category = $cur_forum['cid'];
+					}
+
+					$redirect_tag = ($cur_forum['redirect_url'] != '') ? ' &gt;&gt;&gt;' : '';
+					$output .= "\t\t\t\t\t\t\t".'<option value="'.$cur_forum['fid'].'"<?php echo ($forum_id == '.$cur_forum['fid'].') ? \' selected="selected"\' : \'\' ?>>'.pun_htmlspecialchars($cur_forum['forum_name']).$redirect_tag.'</option>'."\n";
+				}
+
+				$output .= "\t\t\t\t\t\t".'</optgroup>'."\n\t\t\t\t\t".'</select>'."\n\t\t\t\t\t".'<input type="submit" value="<?php echo $lang_common[\'Go\'] ?>" accesskey="g" />'."\n\t\t\t\t\t".'</label></div>'."\n\t\t\t\t".'</form>'."\n";
+			}
+		}
+
+		fwrite($fh, $output);
+
+		fclose($fh);
+
+		if (function_exists('apc_delete_file'))
+			@apc_delete_file(FORUM_CACHE_DIR.'cache_quickjump_'.$group_id.'.php');
+	}
+}
+
+
+//
+// Generate the censoring cache PHP script
+//
+function generate_censoring_cache()
+{
+	global $db;
+
+	$result = $db->query('SELECT search_for, replace_with FROM '.$db->prefix.'censoring') or error('Unable to fetch censoring list', __FILE__, __LINE__, $db->error());
+	$num_words = $db->num_rows($result);
+
+	$search_for = $replace_with = array();
+	for ($i = 0; $i < $num_words; $i++)
+	{
+		list($search_for[$i], $replace_with[$i]) = $db->fetch_row($result);
+		$search_for[$i] = '%(?<=[^\p{L}\p{N}])('.str_replace('\*', '[\p{L}\p{N}]*?', preg_quote($search_for[$i], '%')).')(?=[^\p{L}\p{N}])%iu';
+	}
+
+	// Output censored words as PHP code
+	$fh = @fopen(FORUM_CACHE_DIR.'cache_censoring.php', 'wb');
+	if (!$fh)
+		error('Unable to write censoring 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_CENSOR_LOADED\', 1);'."\n\n".'$search_for = '.var_export($search_for, true).';'."\n\n".'$replace_with = '.var_export($replace_with, true).';'."\n\n".'?>');
+
+	fclose($fh);
+
+	if (function_exists('apc_delete_file'))
+		@apc_delete_file(FORUM_CACHE_DIR.'cache_censoring.php');
+}
+
+
+//
+// Generate the stopwords cache PHP script
+//
+function generate_stopwords_cache()
+{
+	$stopwords = array();
+
+	$d = dir(PUN_ROOT.'lang');
+	while (($entry = $d->read()) !== false)
+	{
+		if ($entry{0} == '.')
+			continue;
+
+		if (is_dir(PUN_ROOT.'lang/'.$entry) && file_exists(PUN_ROOT.'lang/'.$entry.'/stopwords.txt'))
+			$stopwords = array_merge($stopwords, file(PUN_ROOT.'lang/'.$entry.'/stopwords.txt'));
+	}
+	$d->close();
+
+	// Tidy up and filter the stopwords
+	$stopwords = array_map('pun_trim', $stopwords);
+	$stopwords = array_filter($stopwords);
+
+	// Output stopwords as PHP code
+	$fh = @fopen(FORUM_CACHE_DIR.'cache_stopwords.php', 'wb');
+	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".'$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);
+
+	if (function_exists('apc_delete_file'))
+		@apc_delete_file(FORUM_CACHE_DIR.'cache_stopwords.php');
+}
+
+
+//
+// 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
new file mode 100644
index 0000000..5e16fc2
--- /dev/null
+++ b/include/common.php
@@ -0,0 +1,199 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+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.12');
+
+define('FORUM_DB_REVISION', 15);
+define('FORUM_SI_REVISION', 2);
+define('FORUM_PARSER_REVISION', 2);
+
+// Block prefetch requests
+if (isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch')
+{
+	header('HTTP/1.1 403 Prefetching Forbidden');
+
+	// 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
+
+	exit;
+}
+
+// Attempt to load the configuration file config.php
+if (file_exists(PUN_ROOT.'config.php'))
+	require PUN_ROOT.'config.php';
+
+// If we have the 1.3-legacy constant defined, define the proper 1.4 constant so we don't get an incorrect "need to install" message
+if (defined('FORUM'))
+	define('PUN', FORUM);
+
+// Load the functions script
+require PUN_ROOT.'include/functions.php';
+
+// Load UTF-8 functions
+require PUN_ROOT.'include/utf8/utf8.php';
+
+// Strip out "bad" UTF-8 characters
+forum_remove_bad_characters();
+
+// Reverse the effect of register_globals
+forum_unregister_globals();
+
+// If PUN isn't defined, config.php is missing or corrupt
+if (!defined('PUN'))
+{
+	header('Location: install.php');
+	exit;
+}
+
+// Record the start time (will be used to calculate the generation time for the page)
+$pun_start = get_microtime();
+
+// Make sure PHP reports all errors except E_NOTICE. FluxBB supports E_ALL, but a lot of scripts it may interact with, do not
+error_reporting(E_ALL ^ E_NOTICE);
+
+// Force POSIX locale (to prevent functions such as strtolower() from messing up UTF-8 strings)
+setlocale(LC_CTYPE, 'C');
+
+// Turn off magic_quotes_runtime
+if (get_magic_quotes_runtime())
+	set_magic_quotes_runtime(0);
+
+// Strip slashes from GET/POST/COOKIE/REQUEST/FILES (if magic_quotes_gpc is enabled)
+if (!defined('FORUM_DISABLE_STRIPSLASHES') && get_magic_quotes_gpc())
+{
+	function stripslashes_array($array)
+	{
+		return is_array($array) ? array_map('stripslashes_array', $array) : stripslashes($array);
+	}
+
+	$_GET = stripslashes_array($_GET);
+	$_POST = stripslashes_array($_POST);
+	$_COOKIE = stripslashes_array($_COOKIE);
+	$_REQUEST = stripslashes_array($_REQUEST);
+	if (is_array($_FILES))
+	{
+		// Don't strip valid slashes from tmp_name path on Windows
+		foreach ($_FILES AS $key => $value)
+			$_FILES[$key]['tmp_name'] = str_replace('\\', '\\\\', $value['tmp_name']);
+		$_FILES = stripslashes_array($_FILES);
+	}
+}
+
+// If a cookie name is not specified in config.php, we use the default (pun_cookie)
+if (empty($cookie_name))
+	$cookie_name = 'pun_cookie';
+
+// If the cache directory is not specified, we use the default setting
+if (!defined('FORUM_CACHE_DIR'))
+	define('FORUM_CACHE_DIR', PUN_ROOT.'cache/');
+
+// Define a few commonly used constants
+define('PUN_UNVERIFIED', 0);
+define('PUN_ADMIN', 1);
+define('PUN_MOD', 2);
+define('PUN_GUEST', 3);
+define('PUN_MEMBER', 4);
+
+// Load DB abstraction layer and connect
+require PUN_ROOT.'include/dblayer/common_db.php';
+
+// Start a transaction
+$db->start_transaction();
+
+// Load cached config
+if (file_exists(FORUM_CACHE_DIR.'cache_config.php'))
+	include FORUM_CACHE_DIR.'cache_config.php';
+
+if (!defined('PUN_CONFIG_LOADED'))
+{
+	if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+		require PUN_ROOT.'include/cache.php';
+
+	generate_config_cache();
+	require FORUM_CACHE_DIR.'cache_config.php';
+}
+
+// Verify that we are running the proper database schema revision
+if (!isset($pun_config['o_database_revision']) || $pun_config['o_database_revision'] < FORUM_DB_REVISION ||
+	!isset($pun_config['o_searchindex_revision']) || $pun_config['o_searchindex_revision'] < FORUM_SI_REVISION ||
+	!isset($pun_config['o_parser_revision']) || $pun_config['o_parser_revision'] < FORUM_PARSER_REVISION ||
+	version_compare($pun_config['o_cur_version'], FORUM_VERSION, '<'))
+{
+	header('Location: db_update.php');
+	exit;
+}
+
+// Enable output buffering
+if (!defined('PUN_DISABLE_BUFFERING'))
+{
+	// Should we use gzip output compression?
+	if ($pun_config['o_gzip'] && extension_loaded('zlib'))
+		ob_start('ob_gzhandler');
+	else
+		ob_start();
+}
+
+// Define standard date/time formats
+$forum_time_formats = array($pun_config['o_time_format'], 'H:i:s', 'H:i', 'g:i:s a', 'g:i a');
+$forum_date_formats = array($pun_config['o_date_format'], 'Y-m-d', 'Y-d-m', 'd-m-Y', 'm-d-Y', 'M j Y', 'jS M Y');
+
+// Check/update/set cookie and fetch user info
+$pun_user = array();
+check_cookie($pun_user);
+
+// Attempt to load the common language file
+if (file_exists(PUN_ROOT.'lang/'.$pun_user['language'].'/common.php'))
+	include PUN_ROOT.'lang/'.$pun_user['language'].'/common.php';
+else
+	error('There is no valid language pack \''.pun_htmlspecialchars($pun_user['language']).'\' installed. Please reinstall a language of that name');
+
+// Check if we are to display a maintenance message
+if ($pun_config['o_maintenance'] && $pun_user['g_id'] > PUN_ADMIN && !defined('PUN_TURN_OFF_MAINT'))
+	maintenance_message();
+
+// Load cached bans
+if (file_exists(FORUM_CACHE_DIR.'cache_bans.php'))
+	include FORUM_CACHE_DIR.'cache_bans.php';
+
+if (!defined('PUN_BANS_LOADED'))
+{
+	if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+		require PUN_ROOT.'include/cache.php';
+
+	generate_bans_cache();
+	require FORUM_CACHE_DIR.'cache_bans.php';
+}
+
+// Check if current user is banned
+check_bans();
+
+// Update online list
+update_users_online();
+
+// Check to see if we logged in without a cookie being set
+if ($pun_user['is_guest'] && isset($_GET['login']))
+	message($lang_common['No cookie']);
+
+// The maximum size of a post, in bytes, since the field is now MEDIUMTEXT this allows ~16MB but lets cap at 1MB...
+if (!defined('PUN_MAX_POSTSIZE'))
+	define('PUN_MAX_POSTSIZE', 1048576);
+
+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
new file mode 100644
index 0000000..594b401
--- /dev/null
+++ b/include/common_admin.php
@@ -0,0 +1,147 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// Make sure no one attempts to run this script "directly"
+if (!defined('PUN'))
+	exit;
+
+// Make sure we have a usable language pack for admin.
+if (file_exists(PUN_ROOT.'lang/'.$pun_user['language'].'/admin_common.php'))
+	$admin_language = $pun_user['language'];
+else if (file_exists(PUN_ROOT.'lang/'.$pun_config['o_default_lang'].'/admin_common.php'))
+	$admin_language = $pun_config['o_default_lang'];
+else
+	$admin_language = 'English';
+
+// Attempt to load the admin_common language file
+require PUN_ROOT.'lang/'.$admin_language.'/admin_common.php';
+
+//
+// Display the admin navigation menu
+//
+function generate_admin_menu($page = '')
+{
+	global $pun_config, $pun_user, $lang_admin_common;
+
+	$is_admin = $pun_user['g_id'] == PUN_ADMIN ? true : false;
+
+?>
+<div id="adminconsole" class="block2col">
+	<div id="adminmenu" class="blockmenu">
+		<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>
+					<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);
+
+	// Did we find any plugins?
+	if (!empty($plugins))
+	{
+
+?>
+		<h2 class="block2"><span><?php echo $lang_admin_common['Plugins menu'] ?></span></h2>
+		<div class="box">
+			<div class="inbox">
+				<ul>
+<?php
+
+		foreach ($plugins as $plugin_name => $plugin)
+			echo "\t\t\t\t\t".'<li'.(($page == $plugin_name) ? ' class="isactive"' : '').'><a href="admin_loader.php?plugin='.$plugin_name.'">'.str_replace('_', ' ', $plugin).'</a></li>'."\n";
+
+?>
+				</ul>
+			</div>
+		</div>
+<?php
+
+	}
+
+?>
+	</div>
+
+<?php
+
+}
+
+
+//
+// Delete topics from $forum_id that are "older than" $prune_date (if $prune_sticky is 1, sticky topics will also be deleted)
+//
+function prune($forum_id, $prune_sticky, $prune_date)
+{
+	global $db;
+
+	$extra_sql = ($prune_date != -1) ? ' AND last_post<'.$prune_date : '';
+
+	if (!$prune_sticky)
+		$extra_sql .= ' AND sticky=\'0\'';
+
+	// Fetch topics to prune
+	$result = $db->query('SELECT id FROM '.$db->prefix.'topics WHERE forum_id='.$forum_id.$extra_sql, true) or error('Unable to fetch topics', __FILE__, __LINE__, $db->error());
+
+	$topic_ids = '';
+	while ($row = $db->fetch_row($result))
+		$topic_ids .= (($topic_ids != '') ? ',' : '').$row[0];
+
+	if ($topic_ids != '')
+	{
+		// Fetch posts to prune
+		$result = $db->query('SELECT id FROM '.$db->prefix.'posts WHERE topic_id IN('.$topic_ids.')', true) or error('Unable to fetch posts', __FILE__, __LINE__, $db->error());
+
+		$post_ids = '';
+		while ($row = $db->fetch_row($result))
+			$post_ids .= (($post_ids != '') ? ',' : '').$row[0];
+
+		if ($post_ids != '')
+		{
+			// Delete topics
+			$db->query('DELETE FROM '.$db->prefix.'topics WHERE id IN('.$topic_ids.')') or error('Unable to prune topics', __FILE__, __LINE__, $db->error());
+			// Delete subscriptions
+			$db->query('DELETE FROM '.$db->prefix.'topic_subscriptions WHERE topic_id IN('.$topic_ids.')') or error('Unable to prune subscriptions', __FILE__, __LINE__, $db->error());
+			// Delete posts
+			$db->query('DELETE FROM '.$db->prefix.'posts WHERE id IN('.$post_ids.')') or error('Unable to prune posts', __FILE__, __LINE__, $db->error());
+
+			// We removed a bunch of posts, so now we have to update the search index
+			require_once PUN_ROOT.'include/search_idx.php';
+			strip_search_index($post_ids);
+		}
+	}
+}
diff --git a/include/dblayer/common_db.php b/include/dblayer/common_db.php
new file mode 100644
index 0000000..5b9e67e
--- /dev/null
+++ b/include/dblayer/common_db.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// Make sure no one attempts to run this script "directly"
+if (!defined('PUN'))
+	exit;
+
+
+// Load the appropriate DB layer class
+switch ($db_type)
+{
+	case 'mysql':
+		require_once PUN_ROOT.'include/dblayer/mysql.php';
+		break;
+
+	case 'mysql_innodb':
+		require_once PUN_ROOT.'include/dblayer/mysql_innodb.php';
+		break;
+
+	case 'mysqli':
+		require_once PUN_ROOT.'include/dblayer/mysqli.php';
+		break;
+
+	case 'mysqli_innodb':
+		require_once PUN_ROOT.'include/dblayer/mysqli_innodb.php';
+		break;
+
+	case 'pgsql':
+		require_once PUN_ROOT.'include/dblayer/pgsql.php';
+		break;
+
+	case 'sqlite':
+		require_once PUN_ROOT.'include/dblayer/sqlite.php';
+		break;
+
+	default:
+		error('\''.$db_type.'\' is not a valid database type. Please check settings in config.php.', __FILE__, __LINE__);
+		break;
+}
+
+
+// Create the database adapter object (and open/connect to/select db)
+$db = new DBLayer($db_host, $db_username, $db_password, $db_name, $db_prefix, $p_connect);
diff --git a/include/dblayer/index.html b/include/dblayer/index.html
new file mode 100644
index 0000000..89337b2
--- /dev/null
+++ b/include/dblayer/index.html
@@ -0,0 +1 @@
+<html><head><title>.</title></head><body>.</body></html>
diff --git a/include/dblayer/mysql.php b/include/dblayer/mysql.php
new file mode 100644
index 0000000..256125e
--- /dev/null
+++ b/include/dblayer/mysql.php
@@ -0,0 +1,372 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// Make sure we have built in support for MySQL
+if (!function_exists('mysql_connect'))
+	exit('This PHP environment doesn\'t have MySQL support built in. MySQL support is required if you want to use a MySQL database to run this forum. Consult the PHP documentation for further assistance.');
+
+
+class DBLayer
+{
+	var $prefix;
+	var $link_id;
+	var $query_result;
+
+	var $saved_queries = array();
+	var $num_queries = 0;
+
+	var $error_no = false;
+	var $error_msg = 'Unknown';
+
+	var $datatype_transformations = array(
+		'%^SERIAL$%'	=>	'INT(10) UNSIGNED AUTO_INCREMENT'
+	);
+
+
+	function DBLayer($db_host, $db_username, $db_password, $db_name, $db_prefix, $p_connect)
+	{
+		$this->prefix = $db_prefix;
+
+		if ($p_connect)
+			$this->link_id = @mysql_pconnect($db_host, $db_username, $db_password);
+		else
+			$this->link_id = @mysql_connect($db_host, $db_username, $db_password);
+
+		if ($this->link_id)
+		{
+			if (!@mysql_select_db($db_name, $this->link_id))
+				error('Unable to select database. MySQL reported: '.mysql_error(), __FILE__, __LINE__);
+		}
+		else
+			error('Unable to connect to MySQL server. MySQL reported: '.mysql_error(), __FILE__, __LINE__);
+
+		// Setup the client-server character set (UTF-8)
+		if (!defined('FORUM_NO_SET_NAMES'))
+			$this->set_names('utf8');
+
+		return $this->link_id;
+	}
+
+
+	function start_transaction()
+	{
+		return;
+	}
+
+
+	function end_transaction()
+	{
+		return;
+	}
+
+
+	function query($sql, $unbuffered = false)
+	{
+		if (defined('PUN_SHOW_QUERIES'))
+			$q_start = get_microtime();
+
+		if ($unbuffered)
+			$this->query_result = @mysql_unbuffered_query($sql, $this->link_id);
+		else
+			$this->query_result = @mysql_query($sql, $this->link_id);
+
+		if ($this->query_result)
+		{
+			if (defined('PUN_SHOW_QUERIES'))
+				$this->saved_queries[] = array($sql, sprintf('%.5f', get_microtime() - $q_start));
+
+			++$this->num_queries;
+
+			return $this->query_result;
+		}
+		else
+		{
+			if (defined('PUN_SHOW_QUERIES'))
+				$this->saved_queries[] = array($sql, 0);
+
+			$this->error_no = @mysql_errno($this->link_id);
+			$this->error_msg = @mysql_error($this->link_id);
+
+			return false;
+		}
+	}
+
+
+	function result($query_id = 0, $row = 0, $col = 0)
+	{
+		return ($query_id) ? @mysql_result($query_id, $row, $col) : false;
+	}
+
+
+	function fetch_assoc($query_id = 0)
+	{
+		return ($query_id) ? @mysql_fetch_assoc($query_id) : false;
+	}
+
+
+	function fetch_row($query_id = 0)
+	{
+		return ($query_id) ? @mysql_fetch_row($query_id) : false;
+	}
+
+
+	function num_rows($query_id = 0)
+	{
+		return ($query_id) ? @mysql_num_rows($query_id) : false;
+	}
+
+
+	function affected_rows()
+	{
+		return ($this->link_id) ? @mysql_affected_rows($this->link_id) : false;
+	}
+
+
+	function insert_id()
+	{
+		return ($this->link_id) ? @mysql_insert_id($this->link_id) : false;
+	}
+
+
+	function get_num_queries()
+	{
+		return $this->num_queries;
+	}
+
+
+	function get_saved_queries()
+	{
+		return $this->saved_queries;
+	}
+
+
+	function free_result($query_id = false)
+	{
+		return ($query_id) ? @mysql_free_result($query_id) : false;
+	}
+
+
+	function escape($str)
+	{
+		if (is_array($str))
+			return '';
+		else if (function_exists('mysql_real_escape_string'))
+			return mysql_real_escape_string($str, $this->link_id);
+		else
+			return mysql_escape_string($str);
+	}
+
+
+	function error()
+	{
+		$result['error_sql'] = @current(@end($this->saved_queries));
+		$result['error_no'] = $this->error_no;
+		$result['error_msg'] = $this->error_msg;
+
+		return $result;
+	}
+
+
+	function close()
+	{
+		if ($this->link_id)
+		{
+			if ($this->query_result)
+				@mysql_free_result($this->query_result);
+
+			return @mysql_close($this->link_id);
+		}
+		else
+			return false;
+	}
+
+	function get_names()
+	{
+		$result = $this->query('SHOW VARIABLES LIKE \'character_set_connection\'');
+		return $this->result($result, 0, 1);
+	}
+
+
+	function set_names($names)
+	{
+		return $this->query('SET NAMES \''.$this->escape($names).'\'');
+	}
+
+
+	function get_version()
+	{
+		$result = $this->query('SELECT VERSION()');
+
+		return array(
+			'name'		=> 'MySQL Standard',
+			'version'	=> preg_replace('%^([^-]+).*$%', '\\1', $this->result($result))
+		);
+	}
+
+
+	function table_exists($table_name, $no_prefix = false)
+	{
+		$result = $this->query('SHOW TABLES LIKE \''.($no_prefix ? '' : $this->prefix).$this->escape($table_name).'\'');
+		return $this->num_rows($result) > 0;
+	}
+
+
+	function field_exists($table_name, $field_name, $no_prefix = false)
+	{
+		$result = $this->query('SHOW COLUMNS FROM '.($no_prefix ? '' : $this->prefix).$table_name.' LIKE \''.$this->escape($field_name).'\'');
+		return $this->num_rows($result) > 0;
+	}
+
+
+	function index_exists($table_name, $index_name, $no_prefix = false)
+	{
+		$exists = false;
+
+		$result = $this->query('SHOW INDEX FROM '.($no_prefix ? '' : $this->prefix).$table_name);
+		while ($cur_index = $this->fetch_assoc($result))
+		{
+			if (strtolower($cur_index['Key_name']) == strtolower(($no_prefix ? '' : $this->prefix).$table_name.'_'.$index_name))
+			{
+				$exists = true;
+				break;
+			}
+		}
+
+		return $exists;
+	}
+
+
+	function create_table($table_name, $schema, $no_prefix = false)
+	{
+		if ($this->table_exists($table_name, $no_prefix))
+			return true;
+
+		$query = 'CREATE TABLE '.($no_prefix ? '' : $this->prefix).$table_name." (\n";
+
+		// Go through every schema element and add it to the query
+		foreach ($schema['FIELDS'] as $field_name => $field_data)
+		{
+			$field_data['datatype'] = preg_replace(array_keys($this->datatype_transformations), array_values($this->datatype_transformations), $field_data['datatype']);
+
+			$query .= $field_name.' '.$field_data['datatype'];
+
+			if (isset($field_data['collation']))
+				$query .= 'CHARACTER SET utf8 COLLATE utf8_'.$field_data['collation'];
+
+			if (!$field_data['allow_null'])
+				$query .= ' NOT NULL';
+
+			if (isset($field_data['default']))
+				$query .= ' DEFAULT '.$field_data['default'];
+
+			$query .= ",\n";
+		}
+
+		// If we have a primary key, add it
+		if (isset($schema['PRIMARY KEY']))
+			$query .= 'PRIMARY KEY ('.implode(',', $schema['PRIMARY KEY']).'),'."\n";
+
+		// Add unique keys
+		if (isset($schema['UNIQUE KEYS']))
+		{
+			foreach ($schema['UNIQUE KEYS'] as $key_name => $key_fields)
+				$query .= 'UNIQUE KEY '.($no_prefix ? '' : $this->prefix).$table_name.'_'.$key_name.'('.implode(',', $key_fields).'),'."\n";
+		}
+
+		// Add indexes
+		if (isset($schema['INDEXES']))
+		{
+			foreach ($schema['INDEXES'] as $index_name => $index_fields)
+				$query .= 'KEY '.($no_prefix ? '' : $this->prefix).$table_name.'_'.$index_name.'('.implode(',', $index_fields).'),'."\n";
+		}
+
+		// We remove the last two characters (a newline and a comma) and add on the ending
+		$query = substr($query, 0, strlen($query) - 2)."\n".') ENGINE = '.(isset($schema['ENGINE']) ? $schema['ENGINE'] : 'MyISAM').' CHARACTER SET utf8';
+
+		return $this->query($query) ? true : false;
+	}
+
+
+	function drop_table($table_name, $no_prefix = false)
+	{
+		if (!$this->table_exists($table_name, $no_prefix))
+			return true;
+
+		return $this->query('DROP TABLE '.($no_prefix ? '' : $this->prefix).$table_name) ? true : false;
+	}
+
+
+	function rename_table($old_table, $new_table, $no_prefix = false)
+	{
+		// If there new table exists and the old one doesn't, then we're happy
+		if ($this->table_exists($new_table, $no_prefix) && !$this->table_exists($old_table, $no_prefix))
+			return true;
+
+		return $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$old_table.' RENAME TO '.($no_prefix ? '' : $this->prefix).$new_table) ? true : false;
+	}
+
+
+	function add_field($table_name, $field_name, $field_type, $allow_null, $default_value = null, $after_field = null, $no_prefix = false)
+	{
+		if ($this->field_exists($table_name, $field_name, $no_prefix))
+			return true;
+
+		$field_type = preg_replace(array_keys($this->datatype_transformations), array_values($this->datatype_transformations), $field_type);
+
+		if (!is_null($default_value) && !is_int($default_value) && !is_float($default_value))
+			$default_value = '\''.$this->escape($default_value).'\'';
+
+		return $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$table_name.' ADD '.$field_name.' '.$field_type.($allow_null ? ' ' : ' NOT NULL').(!is_null($default_value) ? ' DEFAULT '.$default_value : ' ').(!is_null($after_field) ? ' AFTER '.$after_field : '')) ? true : false;
+	}
+
+
+	function alter_field($table_name, $field_name, $field_type, $allow_null, $default_value = null, $after_field = null, $no_prefix = false)
+	{
+		if (!$this->field_exists($table_name, $field_name, $no_prefix))
+			return true;
+
+		$field_type = preg_replace(array_keys($this->datatype_transformations), array_values($this->datatype_transformations), $field_type);
+
+		if (!is_null($default_value) && !is_int($default_value) && !is_float($default_value))
+			$default_value = '\''.$this->escape($default_value).'\'';
+
+		return $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$table_name.' MODIFY '.$field_name.' '.$field_type.($allow_null ? ' ' : ' NOT NULL').(!is_null($default_value) ? ' DEFAULT '.$default_value : ' ').(!is_null($after_field) ? ' AFTER '.$after_field : '')) ? true : false;
+	}
+
+
+	function drop_field($table_name, $field_name, $no_prefix = false)
+	{
+		if (!$this->field_exists($table_name, $field_name, $no_prefix))
+			return true;
+
+		return $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$table_name.' DROP '.$field_name) ? true : false;
+	}
+
+
+	function add_index($table_name, $index_name, $index_fields, $unique = false, $no_prefix = false)
+	{
+		if ($this->index_exists($table_name, $index_name, $no_prefix))
+			return true;
+
+		return $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$table_name.' ADD '.($unique ? 'UNIQUE ' : '').'INDEX '.($no_prefix ? '' : $this->prefix).$table_name.'_'.$index_name.' ('.implode(',', $index_fields).')') ? true : false;
+	}
+
+
+	function drop_index($table_name, $index_name, $no_prefix = false)
+	{
+		if (!$this->index_exists($table_name, $index_name, $no_prefix))
+			return true;
+
+		return $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$table_name.' DROP INDEX '.($no_prefix ? '' : $this->prefix).$table_name.'_'.$index_name) ? true : false;
+	}
+
+	function truncate_table($table_name, $no_prefix = false)
+	{
+		return $this->query('TRUNCATE TABLE '.($no_prefix ? '' : $this->prefix).$table_name) ? true : false;
+	}
+}
diff --git a/include/dblayer/mysql_innodb.php b/include/dblayer/mysql_innodb.php
new file mode 100644
index 0000000..01ca724
--- /dev/null
+++ b/include/dblayer/mysql_innodb.php
@@ -0,0 +1,386 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// Make sure we have built in support for MySQL
+if (!function_exists('mysql_connect'))
+	exit('This PHP environment doesn\'t have MySQL support built in. MySQL support is required if you want to use a MySQL database to run this forum. Consult the PHP documentation for further assistance.');
+
+
+class DBLayer
+{
+	var $prefix;
+	var $link_id;
+	var $query_result;
+	var $in_transaction = 0;
+
+	var $saved_queries = array();
+	var $num_queries = 0;
+
+	var $error_no = false;
+	var $error_msg = 'Unknown';
+
+	var $datatype_transformations = array(
+		'%^SERIAL$%'	=>	'INT(10) UNSIGNED AUTO_INCREMENT'
+	);
+
+
+	function DBLayer($db_host, $db_username, $db_password, $db_name, $db_prefix, $p_connect)
+	{
+		$this->prefix = $db_prefix;
+
+		if ($p_connect)
+			$this->link_id = @mysql_pconnect($db_host, $db_username, $db_password);
+		else
+			$this->link_id = @mysql_connect($db_host, $db_username, $db_password);
+
+		if ($this->link_id)
+		{
+			if (!@mysql_select_db($db_name, $this->link_id))
+				error('Unable to select database. MySQL reported: '.mysql_error(), __FILE__, __LINE__);
+		}
+		else
+			error('Unable to connect to MySQL server. MySQL reported: '.mysql_error(), __FILE__, __LINE__);
+
+		// Setup the client-server character set (UTF-8)
+		if (!defined('FORUM_NO_SET_NAMES'))
+			$this->set_names('utf8');
+
+		return $this->link_id;
+	}
+
+
+	function start_transaction()
+	{
+		++$this->in_transaction;
+
+		mysql_query('START TRANSACTION', $this->link_id);
+		return;
+	}
+
+
+	function end_transaction()
+	{
+		--$this->in_transaction;
+
+		mysql_query('COMMIT', $this->link_id);
+		return;
+	}
+
+
+	function query($sql, $unbuffered = false)
+	{
+		if (defined('PUN_SHOW_QUERIES'))
+			$q_start = get_microtime();
+
+		if ($unbuffered)
+			$this->query_result = @mysql_unbuffered_query($sql, $this->link_id);
+		else
+			$this->query_result = @mysql_query($sql, $this->link_id);
+
+		if ($this->query_result)
+		{
+			if (defined('PUN_SHOW_QUERIES'))
+				$this->saved_queries[] = array($sql, sprintf('%.5f', get_microtime() - $q_start));
+
+			++$this->num_queries;
+
+			return $this->query_result;
+		}
+		else
+		{
+			if (defined('PUN_SHOW_QUERIES'))
+				$this->saved_queries[] = array($sql, 0);
+
+			$this->error_no = @mysql_errno($this->link_id);
+			$this->error_msg = @mysql_error($this->link_id);
+
+			// Rollback transaction
+			if ($this->in_transaction)
+				mysql_query('ROLLBACK', $this->link_id);
+
+			--$this->in_transaction;
+
+			return false;
+		}
+	}
+
+
+	function result($query_id = 0, $row = 0, $col = 0)
+	{
+		return ($query_id) ? @mysql_result($query_id, $row, $col) : false;
+	}
+
+
+	function fetch_assoc($query_id = 0)
+	{
+		return ($query_id) ? @mysql_fetch_assoc($query_id) : false;
+	}
+
+
+	function fetch_row($query_id = 0)
+	{
+		return ($query_id) ? @mysql_fetch_row($query_id) : false;
+	}
+
+
+	function num_rows($query_id = 0)
+	{
+		return ($query_id) ? @mysql_num_rows($query_id) : false;
+	}
+
+
+	function affected_rows()
+	{
+		return ($this->link_id) ? @mysql_affected_rows($this->link_id) : false;
+	}
+
+
+	function insert_id()
+	{
+		return ($this->link_id) ? @mysql_insert_id($this->link_id) : false;
+	}
+
+
+	function get_num_queries()
+	{
+		return $this->num_queries;
+	}
+
+
+	function get_saved_queries()
+	{
+		return $this->saved_queries;
+	}
+
+
+	function free_result($query_id = false)
+	{
+		return ($query_id) ? @mysql_free_result($query_id) : false;
+	}
+
+
+	function escape($str)
+	{
+		if (is_array($str))
+			return '';
+		else if (function_exists('mysql_real_escape_string'))
+			return mysql_real_escape_string($str, $this->link_id);
+		else
+			return mysql_escape_string($str);
+	}
+
+
+	function error()
+	{
+		$result['error_sql'] = @current(@end($this->saved_queries));
+		$result['error_no'] = $this->error_no;
+		$result['error_msg'] = $this->error_msg;
+
+		return $result;
+	}
+
+
+	function close()
+	{
+		if ($this->link_id)
+		{
+			if ($this->query_result)
+				@mysql_free_result($this->query_result);
+
+			return @mysql_close($this->link_id);
+		}
+		else
+			return false;
+	}
+
+
+	function get_names()
+	{
+		$result = $this->query('SHOW VARIABLES LIKE \'character_set_connection\'');
+		return $this->result($result, 0, 1);
+	}
+
+
+	function set_names($names)
+	{
+		return $this->query('SET NAMES \''.$this->escape($names).'\'');
+	}
+
+
+	function get_version()
+	{
+		$result = $this->query('SELECT VERSION()');
+
+		return array(
+			'name'		=> 'MySQL Standard (InnoDB)',
+			'version'	=> preg_replace('%^([^-]+).*$%', '\\1', $this->result($result))
+		);
+	}
+
+
+	function table_exists($table_name, $no_prefix = false)
+	{
+		$result = $this->query('SHOW TABLES LIKE \''.($no_prefix ? '' : $this->prefix).$this->escape($table_name).'\'');
+		return $this->num_rows($result) > 0;
+	}
+
+
+	function field_exists($table_name, $field_name, $no_prefix = false)
+	{
+		$result = $this->query('SHOW COLUMNS FROM '.($no_prefix ? '' : $this->prefix).$table_name.' LIKE \''.$this->escape($field_name).'\'');
+		return $this->num_rows($result) > 0;
+	}
+
+
+	function index_exists($table_name, $index_name, $no_prefix = false)
+	{
+		$exists = false;
+
+		$result = $this->query('SHOW INDEX FROM '.($no_prefix ? '' : $this->prefix).$table_name);
+		while ($cur_index = $this->fetch_assoc($result))
+		{
+			if (strtolower($cur_index['Key_name']) == strtolower(($no_prefix ? '' : $this->prefix).$table_name.'_'.$index_name))
+			{
+				$exists = true;
+				break;
+			}
+		}
+
+		return $exists;
+	}
+
+
+	function create_table($table_name, $schema, $no_prefix = false)
+	{
+		if ($this->table_exists($table_name, $no_prefix))
+			return true;
+
+		$query = 'CREATE TABLE '.($no_prefix ? '' : $this->prefix).$table_name." (\n";
+
+		// Go through every schema element and add it to the query
+		foreach ($schema['FIELDS'] as $field_name => $field_data)
+		{
+			$field_data['datatype'] = preg_replace(array_keys($this->datatype_transformations), array_values($this->datatype_transformations), $field_data['datatype']);
+
+			$query .= $field_name.' '.$field_data['datatype'];
+
+			if (isset($field_data['collation']))
+				$query .= 'CHARACTER SET utf8 COLLATE utf8_'.$field_data['collation'];
+
+			if (!$field_data['allow_null'])
+				$query .= ' NOT NULL';
+
+			if (isset($field_data['default']))
+				$query .= ' DEFAULT '.$field_data['default'];
+
+			$query .= ",\n";
+		}
+
+		// If we have a primary key, add it
+		if (isset($schema['PRIMARY KEY']))
+			$query .= 'PRIMARY KEY ('.implode(',', $schema['PRIMARY KEY']).'),'."\n";
+
+		// Add unique keys
+		if (isset($schema['UNIQUE KEYS']))
+		{
+			foreach ($schema['UNIQUE KEYS'] as $key_name => $key_fields)
+				$query .= 'UNIQUE KEY '.($no_prefix ? '' : $this->prefix).$table_name.'_'.$key_name.'('.implode(',', $key_fields).'),'."\n";
+		}
+
+		// Add indexes
+		if (isset($schema['INDEXES']))
+		{
+			foreach ($schema['INDEXES'] as $index_name => $index_fields)
+				$query .= 'KEY '.($no_prefix ? '' : $this->prefix).$table_name.'_'.$index_name.'('.implode(',', $index_fields).'),'."\n";
+		}
+
+		// We remove the last two characters (a newline and a comma) and add on the ending
+		$query = substr($query, 0, strlen($query) - 2)."\n".') ENGINE = '.(isset($schema['ENGINE']) ? $schema['ENGINE'] : 'InnoDB').' CHARACTER SET utf8';
+
+		return $this->query($query) ? true : false;
+	}
+
+
+	function drop_table($table_name, $no_prefix = false)
+	{
+		if (!$this->table_exists($table_name, $no_prefix))
+			return true;
+
+		return $this->query('DROP TABLE '.($no_prefix ? '' : $this->prefix).$table_name) ? true : false;
+	}
+
+
+	function rename_table($old_table, $new_table, $no_prefix = false)
+	{
+		// If there new table exists and the old one doesn't, then we're happy
+		if ($this->table_exists($new_table, $no_prefix) && !$this->table_exists($old_table, $no_prefix))
+			return true;
+
+		return $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$old_table.' RENAME TO '.($no_prefix ? '' : $this->prefix).$new_table) ? true : false;
+	}
+
+
+	function add_field($table_name, $field_name, $field_type, $allow_null, $default_value = null, $after_field = null, $no_prefix = false)
+	{
+		if ($this->field_exists($table_name, $field_name, $no_prefix))
+			return true;
+
+		$field_type = preg_replace(array_keys($this->datatype_transformations), array_values($this->datatype_transformations), $field_type);
+
+		if (!is_null($default_value) && !is_int($default_value) && !is_float($default_value))
+			$default_value = '\''.$this->escape($default_value).'\'';
+
+		return $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$table_name.' ADD '.$field_name.' '.$field_type.($allow_null ? ' ' : ' NOT NULL').(!is_null($default_value) ? ' DEFAULT '.$default_value : ' ').(!is_null($after_field) ? ' AFTER '.$after_field : '')) ? true : false;
+	}
+
+
+	function alter_field($table_name, $field_name, $field_type, $allow_null, $default_value = null, $after_field = null, $no_prefix = false)
+	{
+		if (!$this->field_exists($table_name, $field_name, $no_prefix))
+			return true;
+
+		$field_type = preg_replace(array_keys($this->datatype_transformations), array_values($this->datatype_transformations), $field_type);
+
+		if (!is_null($default_value) && !is_int($default_value) && !is_float($default_value))
+			$default_value = '\''.$this->escape($default_value).'\'';
+
+		return $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$table_name.' MODIFY '.$field_name.' '.$field_type.($allow_null ? ' ' : ' NOT NULL').(!is_null($default_value) ? ' DEFAULT '.$default_value : ' ').(!is_null($after_field) ? ' AFTER '.$after_field : '')) ? true : false;
+	}
+
+
+	function drop_field($table_name, $field_name, $no_prefix = false)
+	{
+		if (!$this->field_exists($table_name, $field_name, $no_prefix))
+			return true;
+
+		return $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$table_name.' DROP '.$field_name) ? true : false;
+	}
+
+
+	function add_index($table_name, $index_name, $index_fields, $unique = false, $no_prefix = false)
+	{
+		if ($this->index_exists($table_name, $index_name, $no_prefix))
+			return true;
+
+		return $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$table_name.' ADD '.($unique ? 'UNIQUE ' : '').'INDEX '.($no_prefix ? '' : $this->prefix).$table_name.'_'.$index_name.' ('.implode(',', $index_fields).')') ? true : false;
+	}
+
+
+	function drop_index($table_name, $index_name, $no_prefix = false)
+	{
+		if (!$this->index_exists($table_name, $index_name, $no_prefix))
+			return true;
+
+		return $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$table_name.' DROP INDEX '.($no_prefix ? '' : $this->prefix).$table_name.'_'.$index_name) ? true : false;
+	}
+
+	function truncate_table($table_name, $no_prefix = false)
+	{
+		return $this->query('TRUNCATE TABLE '.($no_prefix ? '' : $this->prefix).$table_name) ? true : false;
+	}
+}
diff --git a/include/dblayer/mysqli.php b/include/dblayer/mysqli.php
new file mode 100644
index 0000000..8b2675b
--- /dev/null
+++ b/include/dblayer/mysqli.php
@@ -0,0 +1,379 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// Make sure we have built in support for MySQL
+if (!function_exists('mysqli_connect'))
+	exit('This PHP environment doesn\'t have Improved MySQL (mysqli) support built in. Improved MySQL support is required if you want to use a MySQL 4.1 (or later) database to run this forum. Consult the PHP documentation for further assistance.');
+
+
+class DBLayer
+{
+	var $prefix;
+	var $link_id;
+	var $query_result;
+
+	var $saved_queries = array();
+	var $num_queries = 0;
+
+	var $error_no = false;
+	var $error_msg = 'Unknown';
+
+	var $datatype_transformations = array(
+		'%^SERIAL$%'	=>	'INT(10) UNSIGNED AUTO_INCREMENT'
+	);
+
+
+	function DBLayer($db_host, $db_username, $db_password, $db_name, $db_prefix, $p_connect)
+	{
+		$this->prefix = $db_prefix;
+
+		// Was a custom port supplied with $db_host?
+		if (strpos($db_host, ':') !== false)
+			list($db_host, $db_port) = explode(':', $db_host);
+
+		// Persistent connection in MySQLi are only available in PHP 5.3 and later releases
+		$p_connect = $p_connect && version_compare(PHP_VERSION, '5.3.0', '>=') ? 'p:' : '';
+
+		if (isset($db_port))
+			$this->link_id = @mysqli_connect($p_connect.$db_host, $db_username, $db_password, $db_name, $db_port);
+		else
+			$this->link_id = @mysqli_connect($p_connect.$db_host, $db_username, $db_password, $db_name);
+
+		if (!$this->link_id)
+			error('Unable to connect to MySQL and select database. MySQL reported: '.mysqli_connect_error(), __FILE__, __LINE__);
+
+		// Setup the client-server character set (UTF-8)
+		if (!defined('FORUM_NO_SET_NAMES'))
+			$this->set_names('utf8');
+
+		return $this->link_id;
+	}
+
+
+	function start_transaction()
+	{
+		return;
+	}
+
+
+	function end_transaction()
+	{
+		return;
+	}
+
+
+	function query($sql, $unbuffered = false)
+	{
+		if (defined('PUN_SHOW_QUERIES'))
+			$q_start = get_microtime();
+
+		$this->query_result = @mysqli_query($this->link_id, $sql);
+
+		if ($this->query_result)
+		{
+			if (defined('PUN_SHOW_QUERIES'))
+				$this->saved_queries[] = array($sql, sprintf('%.5f', get_microtime() - $q_start));
+
+			++$this->num_queries;
+
+			return $this->query_result;
+		}
+		else
+		{
+			if (defined('PUN_SHOW_QUERIES'))
+				$this->saved_queries[] = array($sql, 0);
+
+			$this->error_no = @mysqli_errno($this->link_id);
+			$this->error_msg = @mysqli_error($this->link_id);
+
+			return false;
+		}
+	}
+
+
+	function result($query_id = 0, $row = 0, $col = 0)
+	{
+		if ($query_id)
+		{
+			if ($row !== 0 && @mysqli_data_seek($query_id, $row) === false)
+				return false;
+
+			$cur_row = @mysqli_fetch_row($query_id);
+			if ($cur_row === false)
+				return false;
+
+			return $cur_row[$col];
+		}
+		else
+			return false;
+	}
+
+
+	function fetch_assoc($query_id = 0)
+	{
+		return ($query_id) ? @mysqli_fetch_assoc($query_id) : false;
+	}
+
+
+	function fetch_row($query_id = 0)
+	{
+		return ($query_id) ? @mysqli_fetch_row($query_id) : false;
+	}
+
+
+	function num_rows($query_id = 0)
+	{
+		return ($query_id) ? @mysqli_num_rows($query_id) : false;
+	}
+
+
+	function affected_rows()
+	{
+		return ($this->link_id) ? @mysqli_affected_rows($this->link_id) : false;
+	}
+
+
+	function insert_id()
+	{
+		return ($this->link_id) ? @mysqli_insert_id($this->link_id) : false;
+	}
+
+
+	function get_num_queries()
+	{
+		return $this->num_queries;
+	}
+
+
+	function get_saved_queries()
+	{
+		return $this->saved_queries;
+	}
+
+
+	function free_result($query_id = false)
+	{
+		return ($query_id) ? @mysqli_free_result($query_id) : false;
+	}
+
+
+	function escape($str)
+	{
+		return is_array($str) ? '' : mysqli_real_escape_string($this->link_id, $str);
+	}
+
+
+	function error()
+	{
+		$result['error_sql'] = @current(@end($this->saved_queries));
+		$result['error_no'] = $this->error_no;
+		$result['error_msg'] = $this->error_msg;
+
+		return $result;
+	}
+
+
+	function close()
+	{
+		if ($this->link_id)
+		{
+			if ($this->query_result)
+				@mysqli_free_result($this->query_result);
+
+			return @mysqli_close($this->link_id);
+		}
+		else
+			return false;
+	}
+
+
+	function get_names()
+	{
+		$result = $this->query('SHOW VARIABLES LIKE \'character_set_connection\'');
+		return $this->result($result, 0, 1);
+	}
+
+
+	function set_names($names)
+	{
+		return $this->query('SET NAMES \''.$this->escape($names).'\'');
+	}
+
+
+	function get_version()
+	{
+		$result = $this->query('SELECT VERSION()');
+
+		return array(
+			'name'		=> 'MySQL Improved',
+			'version'	=> preg_replace('%^([^-]+).*$%', '\\1', $this->result($result))
+		);
+	}
+
+
+	function table_exists($table_name, $no_prefix = false)
+	{
+		$result = $this->query('SHOW TABLES LIKE \''.($no_prefix ? '' : $this->prefix).$this->escape($table_name).'\'');
+		return $this->num_rows($result) > 0;
+	}
+
+
+	function field_exists($table_name, $field_name, $no_prefix = false)
+	{
+		$result = $this->query('SHOW COLUMNS FROM '.($no_prefix ? '' : $this->prefix).$table_name.' LIKE \''.$this->escape($field_name).'\'');
+		return $this->num_rows($result) > 0;
+	}
+
+
+	function index_exists($table_name, $index_name, $no_prefix = false)
+	{
+		$exists = false;
+
+		$result = $this->query('SHOW INDEX FROM '.($no_prefix ? '' : $this->prefix).$table_name);
+		while ($cur_index = $this->fetch_assoc($result))
+		{
+			if (strtolower($cur_index['Key_name']) == strtolower(($no_prefix ? '' : $this->prefix).$table_name.'_'.$index_name))
+			{
+				$exists = true;
+				break;
+			}
+		}
+
+		return $exists;
+	}
+
+
+	function create_table($table_name, $schema, $no_prefix = false)
+	{
+		if ($this->table_exists($table_name, $no_prefix))
+			return true;
+
+		$query = 'CREATE TABLE '.($no_prefix ? '' : $this->prefix).$table_name." (\n";
+
+		// Go through every schema element and add it to the query
+		foreach ($schema['FIELDS'] as $field_name => $field_data)
+		{
+			$field_data['datatype'] = preg_replace(array_keys($this->datatype_transformations), array_values($this->datatype_transformations), $field_data['datatype']);
+
+			$query .= $field_name.' '.$field_data['datatype'];
+
+			if (isset($field_data['collation']))
+				$query .= 'CHARACTER SET utf8 COLLATE utf8_'.$field_data['collation'];
+
+			if (!$field_data['allow_null'])
+				$query .= ' NOT NULL';
+
+			if (isset($field_data['default']))
+				$query .= ' DEFAULT '.$field_data['default'];
+
+			$query .= ",\n";
+		}
+
+		// If we have a primary key, add it
+		if (isset($schema['PRIMARY KEY']))
+			$query .= 'PRIMARY KEY ('.implode(',', $schema['PRIMARY KEY']).'),'."\n";
+
+		// Add unique keys
+		if (isset($schema['UNIQUE KEYS']))
+		{
+			foreach ($schema['UNIQUE KEYS'] as $key_name => $key_fields)
+				$query .= 'UNIQUE KEY '.($no_prefix ? '' : $this->prefix).$table_name.'_'.$key_name.'('.implode(',', $key_fields).'),'."\n";
+		}
+
+		// Add indexes
+		if (isset($schema['INDEXES']))
+		{
+			foreach ($schema['INDEXES'] as $index_name => $index_fields)
+				$query .= 'KEY '.($no_prefix ? '' : $this->prefix).$table_name.'_'.$index_name.'('.implode(',', $index_fields).'),'."\n";
+		}
+
+		// We remove the last two characters (a newline and a comma) and add on the ending
+		$query = substr($query, 0, strlen($query) - 2)."\n".') ENGINE = '.(isset($schema['ENGINE']) ? $schema['ENGINE'] : 'MyISAM').' CHARACTER SET utf8';
+
+		return $this->query($query) ? true : false;
+	}
+
+
+	function drop_table($table_name, $no_prefix = false)
+	{
+		if (!$this->table_exists($table_name, $no_prefix))
+			return true;
+
+		return $this->query('DROP TABLE '.($no_prefix ? '' : $this->prefix).$table_name) ? true : false;
+	}
+
+
+	function rename_table($old_table, $new_table, $no_prefix = false)
+	{
+		// If there new table exists and the old one doesn't, then we're happy
+		if ($this->table_exists($new_table, $no_prefix) && !$this->table_exists($old_table, $no_prefix))
+			return true;
+
+		return $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$old_table.' RENAME TO '.($no_prefix ? '' : $this->prefix).$new_table) ? true : false;
+	}
+
+
+	function add_field($table_name, $field_name, $field_type, $allow_null, $default_value = null, $after_field = null, $no_prefix = false)
+	{
+		if ($this->field_exists($table_name, $field_name, $no_prefix))
+			return true;
+
+		$field_type = preg_replace(array_keys($this->datatype_transformations), array_values($this->datatype_transformations), $field_type);
+
+		if (!is_null($default_value) && !is_int($default_value) && !is_float($default_value))
+			$default_value = '\''.$this->escape($default_value).'\'';
+
+		return $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$table_name.' ADD '.$field_name.' '.$field_type.($allow_null ? ' ' : ' NOT NULL').(!is_null($default_value) ? ' DEFAULT '.$default_value : ' ').(!is_null($after_field) ? ' AFTER '.$after_field : '')) ? true : false;
+	}
+
+
+	function alter_field($table_name, $field_name, $field_type, $allow_null, $default_value = null, $after_field = null, $no_prefix = false)
+	{
+		if (!$this->field_exists($table_name, $field_name, $no_prefix))
+			return true;
+
+		$field_type = preg_replace(array_keys($this->datatype_transformations), array_values($this->datatype_transformations), $field_type);
+
+		if (!is_null($default_value) && !is_int($default_value) && !is_float($default_value))
+			$default_value = '\''.$this->escape($default_value).'\'';
+
+		return $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$table_name.' MODIFY '.$field_name.' '.$field_type.($allow_null ? ' ' : ' NOT NULL').(!is_null($default_value) ? ' DEFAULT '.$default_value : ' ').(!is_null($after_field) ? ' AFTER '.$after_field : '')) ? true : false;
+	}
+
+
+	function drop_field($table_name, $field_name, $no_prefix = false)
+	{
+		if (!$this->field_exists($table_name, $field_name, $no_prefix))
+			return true;
+
+		return $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$table_name.' DROP '.$field_name) ? true : false;
+	}
+
+
+	function add_index($table_name, $index_name, $index_fields, $unique = false, $no_prefix = false)
+	{
+		if ($this->index_exists($table_name, $index_name, $no_prefix))
+			return true;
+
+		return $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$table_name.' ADD '.($unique ? 'UNIQUE ' : '').'INDEX '.($no_prefix ? '' : $this->prefix).$table_name.'_'.$index_name.' ('.implode(',', $index_fields).')') ? true : false;
+	}
+
+
+	function drop_index($table_name, $index_name, $no_prefix = false)
+	{
+		if (!$this->index_exists($table_name, $index_name, $no_prefix))
+			return true;
+
+		return $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$table_name.' DROP INDEX '.($no_prefix ? '' : $this->prefix).$table_name.'_'.$index_name) ? true : false;
+	}
+
+	function truncate_table($table_name, $no_prefix = false)
+	{
+		return $this->query('TRUNCATE TABLE '.($no_prefix ? '' : $this->prefix).$table_name) ? true : false;
+	}
+}
diff --git a/include/dblayer/mysqli_innodb.php b/include/dblayer/mysqli_innodb.php
new file mode 100644
index 0000000..3a07431
--- /dev/null
+++ b/include/dblayer/mysqli_innodb.php
@@ -0,0 +1,392 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// Make sure we have built in support for MySQL
+if (!function_exists('mysqli_connect'))
+	exit('This PHP environment doesn\'t have Improved MySQL (mysqli) support built in. Improved MySQL support is required if you want to use a MySQL 4.1 (or later) database to run this forum. Consult the PHP documentation for further assistance.');
+
+
+class DBLayer
+{
+	var $prefix;
+	var $link_id;
+	var $query_result;
+
+	var $saved_queries = array();
+	var $num_queries = 0;
+	var $in_transaction = 0;
+
+	var $error_no = false;
+	var $error_msg = 'Unknown';
+
+	var $datatype_transformations = array(
+		'%^SERIAL$%'	=>	'INT(10) UNSIGNED AUTO_INCREMENT'
+	);
+
+
+	function DBLayer($db_host, $db_username, $db_password, $db_name, $db_prefix, $p_connect)
+	{
+		$this->prefix = $db_prefix;
+
+		// Was a custom port supplied with $db_host?
+		if (strpos($db_host, ':') !== false)
+			list($db_host, $db_port) = explode(':', $db_host);
+
+		// Persistent connection in MySQLi are only available in PHP 5.3 and later releases
+		$p_connect = $p_connect && version_compare(PHP_VERSION, '5.3.0', '>=') ? 'p:' : '';
+
+		if (isset($db_port))
+			$this->link_id = @mysqli_connect($p_connect.$db_host, $db_username, $db_password, $db_name, $db_port);
+		else
+			$this->link_id = @mysqli_connect($p_connect.$db_host, $db_username, $db_password, $db_name);
+
+		if (!$this->link_id)
+			error('Unable to connect to MySQL and select database. MySQL reported: '.mysqli_connect_error(), __FILE__, __LINE__);
+
+		// Setup the client-server character set (UTF-8)
+		if (!defined('FORUM_NO_SET_NAMES'))
+			$this->set_names('utf8');
+
+		return $this->link_id;
+	}
+
+
+	function start_transaction()
+	{
+		++$this->in_transaction;
+
+		mysqli_query($this->link_id, 'START TRANSACTION');
+		return;
+	}
+
+
+	function end_transaction()
+	{
+		--$this->in_transaction;
+
+		mysqli_query($this->link_id, 'COMMIT');
+		return;
+	}
+
+
+	function query($sql, $unbuffered = false)
+	{
+		if (defined('PUN_SHOW_QUERIES'))
+			$q_start = get_microtime();
+
+		$this->query_result = @mysqli_query($this->link_id, $sql);
+
+		if ($this->query_result)
+		{
+			if (defined('PUN_SHOW_QUERIES'))
+				$this->saved_queries[] = array($sql, sprintf('%.5f', get_microtime() - $q_start));
+
+			++$this->num_queries;
+
+			return $this->query_result;
+		}
+		else
+		{
+			if (defined('PUN_SHOW_QUERIES'))
+				$this->saved_queries[] = array($sql, 0);
+
+			$this->error_no = @mysqli_errno($this->link_id);
+			$this->error_msg = @mysqli_error($this->link_id);
+
+			// Rollback transaction
+			if ($this->in_transaction)
+				mysqli_query($this->link_id, 'ROLLBACK');
+
+			--$this->in_transaction;
+
+			return false;
+		}
+	}
+
+
+	function result($query_id = 0, $row = 0, $col = 0)
+	{
+		if ($query_id)
+		{
+			if ($row !== 0 && @mysqli_data_seek($query_id, $row) === false)
+				return false;
+
+			$cur_row = @mysqli_fetch_row($query_id);
+			if ($cur_row === false)
+				return false;
+
+			return $cur_row[$col];
+		}
+		else
+			return false;
+	}
+
+
+	function fetch_assoc($query_id = 0)
+	{
+		return ($query_id) ? @mysqli_fetch_assoc($query_id) : false;
+	}
+
+
+	function fetch_row($query_id = 0)
+	{
+		return ($query_id) ? @mysqli_fetch_row($query_id) : false;
+	}
+
+
+	function num_rows($query_id = 0)
+	{
+		return ($query_id) ? @mysqli_num_rows($query_id) : false;
+	}
+
+
+	function affected_rows()
+	{
+		return ($this->link_id) ? @mysqli_affected_rows($this->link_id) : false;
+	}
+
+
+	function insert_id()
+	{
+		return ($this->link_id) ? @mysqli_insert_id($this->link_id) : false;
+	}
+
+
+	function get_num_queries()
+	{
+		return $this->num_queries;
+	}
+
+
+	function get_saved_queries()
+	{
+		return $this->saved_queries;
+	}
+
+
+	function free_result($query_id = false)
+	{
+		return ($query_id) ? @mysqli_free_result($query_id) : false;
+	}
+
+
+	function escape($str)
+	{
+		return is_array($str) ? '' : mysqli_real_escape_string($this->link_id, $str);
+	}
+
+
+	function error()
+	{
+		$result['error_sql'] = @current(@end($this->saved_queries));
+		$result['error_no'] = $this->error_no;
+		$result['error_msg'] = $this->error_msg;
+
+		return $result;
+	}
+
+
+	function close()
+	{
+		if ($this->link_id)
+		{
+			if ($this->query_result)
+				@mysqli_free_result($this->query_result);
+
+			return @mysqli_close($this->link_id);
+		}
+		else
+			return false;
+	}
+
+
+	function get_names()
+	{
+		$result = $this->query('SHOW VARIABLES LIKE \'character_set_connection\'');
+		return $this->result($result, 0, 1);
+	}
+
+
+	function set_names($names)
+	{
+		return $this->query('SET NAMES \''.$this->escape($names).'\'');
+	}
+
+
+	function get_version()
+	{
+		$result = $this->query('SELECT VERSION()');
+
+		return array(
+			'name'		=> 'MySQL Improved (InnoDB)',
+			'version'	=> preg_replace('%^([^-]+).*$%', '\\1', $this->result($result))
+		);
+	}
+
+
+	function table_exists($table_name, $no_prefix = false)
+	{
+		$result = $this->query('SHOW TABLES LIKE \''.($no_prefix ? '' : $this->prefix).$this->escape($table_name).'\'');
+		return $this->num_rows($result) > 0;
+	}
+
+
+	function field_exists($table_name, $field_name, $no_prefix = false)
+	{
+		$result = $this->query('SHOW COLUMNS FROM '.($no_prefix ? '' : $this->prefix).$table_name.' LIKE \''.$this->escape($field_name).'\'');
+		return $this->num_rows($result) > 0;
+	}
+
+
+	function index_exists($table_name, $index_name, $no_prefix = false)
+	{
+		$exists = false;
+
+		$result = $this->query('SHOW INDEX FROM '.($no_prefix ? '' : $this->prefix).$table_name);
+		while ($cur_index = $this->fetch_assoc($result))
+		{
+			if (strtolower($cur_index['Key_name']) == strtolower(($no_prefix ? '' : $this->prefix).$table_name.'_'.$index_name))
+			{
+				$exists = true;
+				break;
+			}
+		}
+
+		return $exists;
+	}
+
+
+	function create_table($table_name, $schema, $no_prefix = false)
+	{
+		if ($this->table_exists($table_name, $no_prefix))
+			return true;
+
+		$query = 'CREATE TABLE '.($no_prefix ? '' : $this->prefix).$table_name." (\n";
+
+		// Go through every schema element and add it to the query
+		foreach ($schema['FIELDS'] as $field_name => $field_data)
+		{
+			$field_data['datatype'] = preg_replace(array_keys($this->datatype_transformations), array_values($this->datatype_transformations), $field_data['datatype']);
+
+			$query .= $field_name.' '.$field_data['datatype'];
+
+			if (isset($field_data['collation']))
+				$query .= 'CHARACTER SET utf8 COLLATE utf8_'.$field_data['collation'];
+
+			if (!$field_data['allow_null'])
+				$query .= ' NOT NULL';
+
+			if (isset($field_data['default']))
+				$query .= ' DEFAULT '.$field_data['default'];
+
+			$query .= ",\n";
+		}
+
+		// If we have a primary key, add it
+		if (isset($schema['PRIMARY KEY']))
+			$query .= 'PRIMARY KEY ('.implode(',', $schema['PRIMARY KEY']).'),'."\n";
+
+		// Add unique keys
+		if (isset($schema['UNIQUE KEYS']))
+		{
+			foreach ($schema['UNIQUE KEYS'] as $key_name => $key_fields)
+				$query .= 'UNIQUE KEY '.($no_prefix ? '' : $this->prefix).$table_name.'_'.$key_name.'('.implode(',', $key_fields).'),'."\n";
+		}
+
+		// Add indexes
+		if (isset($schema['INDEXES']))
+		{
+			foreach ($schema['INDEXES'] as $index_name => $index_fields)
+				$query .= 'KEY '.($no_prefix ? '' : $this->prefix).$table_name.'_'.$index_name.'('.implode(',', $index_fields).'),'."\n";
+		}
+
+		// We remove the last two characters (a newline and a comma) and add on the ending
+		$query = substr($query, 0, strlen($query) - 2)."\n".') ENGINE = '.(isset($schema['ENGINE']) ? $schema['ENGINE'] : 'InnoDB').' CHARACTER SET utf8';
+
+		return $this->query($query) ? true : false;
+	}
+
+
+	function drop_table($table_name, $no_prefix = false)
+	{
+		if (!$this->table_exists($table_name, $no_prefix))
+			return true;
+
+		return $this->query('DROP TABLE '.($no_prefix ? '' : $this->prefix).$table_name) ? true : false;
+	}
+
+
+	function rename_table($old_table, $new_table, $no_prefix = false)
+	{
+		// If there new table exists and the old one doesn't, then we're happy
+		if ($this->table_exists($new_table, $no_prefix) && !$this->table_exists($old_table, $no_prefix))
+			return true;
+
+		return $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$old_table.' RENAME TO '.($no_prefix ? '' : $this->prefix).$new_table) ? true : false;
+	}
+
+
+	function add_field($table_name, $field_name, $field_type, $allow_null, $default_value = null, $after_field = null, $no_prefix = false)
+	{
+		if ($this->field_exists($table_name, $field_name, $no_prefix))
+			return true;
+
+		$field_type = preg_replace(array_keys($this->datatype_transformations), array_values($this->datatype_transformations), $field_type);
+
+		if (!is_null($default_value) && !is_int($default_value) && !is_float($default_value))
+			$default_value = '\''.$this->escape($default_value).'\'';
+
+		return $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$table_name.' ADD '.$field_name.' '.$field_type.($allow_null ? ' ' : ' NOT NULL').(!is_null($default_value) ? ' DEFAULT '.$default_value : ' ').(!is_null($after_field) ? ' AFTER '.$after_field : '')) ? true : false;
+	}
+
+
+	function alter_field($table_name, $field_name, $field_type, $allow_null, $default_value = null, $after_field = null, $no_prefix = false)
+	{
+		if (!$this->field_exists($table_name, $field_name, $no_prefix))
+			return true;
+
+		$field_type = preg_replace(array_keys($this->datatype_transformations), array_values($this->datatype_transformations), $field_type);
+
+		if (!is_null($default_value) && !is_int($default_value) && !is_float($default_value))
+			$default_value = '\''.$this->escape($default_value).'\'';
+
+		return $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$table_name.' MODIFY '.$field_name.' '.$field_type.($allow_null ? ' ' : ' NOT NULL').(!is_null($default_value) ? ' DEFAULT '.$default_value : ' ').(!is_null($after_field) ? ' AFTER '.$after_field : '')) ? true : false;
+	}
+
+
+	function drop_field($table_name, $field_name, $no_prefix = false)
+	{
+		if (!$this->field_exists($table_name, $field_name, $no_prefix))
+			return true;
+
+		return $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$table_name.' DROP '.$field_name) ? true : false;
+	}
+
+
+	function add_index($table_name, $index_name, $index_fields, $unique = false, $no_prefix = false)
+	{
+		if ($this->index_exists($table_name, $index_name, $no_prefix))
+			return true;
+
+		return $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$table_name.' ADD '.($unique ? 'UNIQUE ' : '').'INDEX '.($no_prefix ? '' : $this->prefix).$table_name.'_'.$index_name.' ('.implode(',', $index_fields).')') ? true : false;
+	}
+
+
+	function drop_index($table_name, $index_name, $no_prefix = false)
+	{
+		if (!$this->index_exists($table_name, $index_name, $no_prefix))
+			return true;
+
+		return $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$table_name.' DROP INDEX '.($no_prefix ? '' : $this->prefix).$table_name.'_'.$index_name) ? true : false;
+	}
+
+	function truncate_table($table_name, $no_prefix = false)
+	{
+		return $this->query('TRUNCATE TABLE '.($no_prefix ? '' : $this->prefix).$table_name) ? true : false;
+	}
+}
diff --git a/include/dblayer/pgsql.php b/include/dblayer/pgsql.php
new file mode 100644
index 0000000..3a73118
--- /dev/null
+++ b/include/dblayer/pgsql.php
@@ -0,0 +1,436 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// Make sure we have built in support for PostgreSQL
+if (!function_exists('pg_connect'))
+	exit('This PHP environment doesn\'t have PostgreSQL support built in. PostgreSQL support is required if you want to use a PostgreSQL database to run this forum. Consult the PHP documentation for further assistance.');
+
+
+class DBLayer
+{
+	var $prefix;
+	var $link_id;
+	var $query_result;
+	var $last_query_text = array();
+	var $in_transaction = 0;
+
+	var $saved_queries = array();
+	var $num_queries = 0;
+
+	var $error_no = false;
+	var $error_msg = 'Unknown';
+
+	var $datatype_transformations = array(
+		'%^(TINY|SMALL)INT( )?(\\([0-9]+\\))?( )?(UNSIGNED)?$%i'			=>	'SMALLINT',
+		'%^(MEDIUM)?INT( )?(\\([0-9]+\\))?( )?(UNSIGNED)?$%i'				=>	'INTEGER',
+		'%^BIGINT( )?(\\([0-9]+\\))?( )?(UNSIGNED)?$%i'						=>	'BIGINT',
+		'%^(TINY|MEDIUM|LONG)?TEXT$%i'										=>	'TEXT',
+		'%^DOUBLE( )?(\\([0-9,]+\\))?( )?(UNSIGNED)?$%i'					=>	'DOUBLE PRECISION',
+		'%^FLOAT( )?(\\([0-9]+\\))?( )?(UNSIGNED)?$%i'						=>	'REAL'
+	);
+
+
+	function DBLayer($db_host, $db_username, $db_password, $db_name, $db_prefix, $p_connect)
+	{
+		$this->prefix = $db_prefix;
+
+		if ($db_host)
+		{
+			if (strpos($db_host, ':') !== false)
+			{
+				list($db_host, $dbport) = explode(':', $db_host);
+				$connect_str[] = 'host='.$db_host.' port='.$dbport;
+			}
+			else
+				$connect_str[] = 'host='.$db_host;
+		}
+
+		if ($db_name)
+			$connect_str[] = 'dbname='.$db_name;
+
+		if ($db_username)
+			$connect_str[] = 'user='.$db_username;
+
+		if ($db_password)
+			$connect_str[] = 'password='.$db_password;
+
+		if ($p_connect)
+			$this->link_id = @pg_pconnect(implode(' ', $connect_str));
+		else
+			$this->link_id = @pg_connect(implode(' ', $connect_str));
+
+		if (!$this->link_id)
+			error('Unable to connect to PostgreSQL server', __FILE__, __LINE__);
+
+		// Setup the client-server character set (UTF-8)
+		if (!defined('FORUM_NO_SET_NAMES'))
+			$this->set_names('utf8');
+
+		return $this->link_id;
+	}
+
+
+	function start_transaction()
+	{
+		++$this->in_transaction;
+
+		return (@pg_query($this->link_id, 'BEGIN')) ? true : false;
+	}
+
+
+	function end_transaction()
+	{
+		--$this->in_transaction;
+
+		if (@pg_query($this->link_id, 'COMMIT'))
+			return true;
+		else
+		{
+			@pg_query($this->link_id, 'ROLLBACK');
+			return false;
+		}
+	}
+
+
+	function query($sql, $unbuffered = false) // $unbuffered is ignored since there is no pgsql_unbuffered_query()
+	{
+		if (strrpos($sql, 'LIMIT') !== false)
+			$sql = preg_replace('%LIMIT ([0-9]+),([ 0-9]+)%', 'LIMIT \\2 OFFSET \\1', $sql);
+
+		if (defined('PUN_SHOW_QUERIES'))
+			$q_start = get_microtime();
+
+		@pg_send_query($this->link_id, $sql);
+		$this->query_result = @pg_get_result($this->link_id);
+
+		if (pg_result_status($this->query_result) != PGSQL_FATAL_ERROR)
+		{
+			if (defined('PUN_SHOW_QUERIES'))
+				$this->saved_queries[] = array($sql, sprintf('%.5f', get_microtime() - $q_start));
+
+			++$this->num_queries;
+
+			$this->last_query_text[$this->query_result] = $sql;
+
+			return $this->query_result;
+		}
+		else
+		{
+			if (defined('PUN_SHOW_QUERIES'))
+				$this->saved_queries[] = array($sql, 0);
+
+			$this->error_no = false;
+			$this->error_msg = @pg_result_error($this->query_result);
+
+			if ($this->in_transaction)
+				@pg_query($this->link_id, 'ROLLBACK');
+
+			--$this->in_transaction;
+
+			return false;
+		}
+	}
+
+
+	function result($query_id = 0, $row = 0, $col = 0)
+	{
+		return ($query_id) ? @pg_fetch_result($query_id, $row, $col) : false;
+	}
+
+
+	function fetch_assoc($query_id = 0)
+	{
+		return ($query_id) ? @pg_fetch_assoc($query_id) : false;
+	}
+
+
+	function fetch_row($query_id = 0)
+	{
+		return ($query_id) ? @pg_fetch_row($query_id) : false;
+	}
+
+
+	function num_rows($query_id = 0)
+	{
+		return ($query_id) ? @pg_num_rows($query_id) : false;
+	}
+
+
+	function affected_rows()
+	{
+		return ($this->query_result) ? @pg_affected_rows($this->query_result) : false;
+	}
+
+
+	function insert_id()
+	{
+		$query_id = $this->query_result;
+
+		if ($query_id && $this->last_query_text[$query_id] != '')
+		{
+			if (preg_match('%^INSERT INTO ([a-z0-9\_\-]+)%is', $this->last_query_text[$query_id], $table_name))
+			{
+				// Hack (don't ask)
+				if (substr($table_name[1], -6) == 'groups')
+					$table_name[1] .= '_g';
+
+				$temp_q_id = @pg_query($this->link_id, 'SELECT currval(\''.$table_name[1].'_id_seq\')');
+				return ($temp_q_id) ? intval(@pg_fetch_result($temp_q_id, 0)) : false;
+			}
+		}
+
+		return false;
+	}
+
+
+	function get_num_queries()
+	{
+		return $this->num_queries;
+	}
+
+
+	function get_saved_queries()
+	{
+		return $this->saved_queries;
+	}
+
+
+	function free_result($query_id = false)
+	{
+		if (!$query_id)
+			$query_id = $this->query_result;
+
+		return ($query_id) ? @pg_free_result($query_id) : false;
+	}
+
+
+	function escape($str)
+	{
+		return is_array($str) ? '' : pg_escape_string($str);
+	}
+
+
+	function error()
+	{
+		$result['error_sql'] = @current(@end($this->saved_queries));
+		$result['error_no'] = $this->error_no;
+		$result['error_msg'] = $this->error_msg;
+
+		return $result;
+	}
+
+
+	function close()
+	{
+		if ($this->link_id)
+		{
+			if ($this->in_transaction)
+			{
+				if (defined('PUN_SHOW_QUERIES'))
+					$this->saved_queries[] = array('COMMIT', 0);
+
+				@pg_query($this->link_id, 'COMMIT');
+			}
+
+			if ($this->query_result)
+				@pg_free_result($this->query_result);
+
+			return @pg_close($this->link_id);
+		}
+		else
+			return false;
+	}
+
+
+	function get_names()
+	{
+		$result = $this->query('SHOW client_encoding');
+		return strtolower($this->result($result)); // MySQL returns lowercase so lets be consistent
+	}
+
+
+	function set_names($names)
+	{
+		return $this->query('SET NAMES \''.$this->escape($names).'\'');
+	}
+
+
+	function get_version()
+	{
+		$result = $this->query('SELECT VERSION()');
+
+		return array(
+			'name'		=> 'PostgreSQL',
+			'version'	=> preg_replace('%^[^0-9]+([^\s,-]+).*$%', '\\1', $this->result($result))
+		);
+	}
+
+
+	function table_exists($table_name, $no_prefix = false)
+	{
+		$result = $this->query('SELECT 1 FROM pg_class WHERE relname = \''.($no_prefix ? '' : $this->prefix).$this->escape($table_name).'\'');
+		return $this->num_rows($result) > 0;
+	}
+
+
+	function field_exists($table_name, $field_name, $no_prefix = false)
+	{
+		$result = $this->query('SELECT 1 FROM pg_class c INNER JOIN pg_attribute a ON a.attrelid = c.oid WHERE c.relname = \''.($no_prefix ? '' : $this->prefix).$this->escape($table_name).'\' AND a.attname = \''.$this->escape($field_name).'\'');
+		return $this->num_rows($result) > 0;
+	}
+
+
+	function index_exists($table_name, $index_name, $no_prefix = false)
+	{
+		$result = $this->query('SELECT 1 FROM pg_index i INNER JOIN pg_class c1 ON c1.oid = i.indrelid INNER JOIN pg_class c2 ON c2.oid = i.indexrelid WHERE c1.relname = \''.($no_prefix ? '' : $this->prefix).$this->escape($table_name).'\' AND c2.relname = \''.($no_prefix ? '' : $this->prefix).$this->escape($table_name).'_'.$this->escape($index_name).'\'');
+		return $this->num_rows($result) > 0;
+	}
+
+
+	function create_table($table_name, $schema, $no_prefix = false)
+	{
+		if ($this->table_exists($table_name, $no_prefix))
+			return true;
+
+		$query = 'CREATE TABLE '.($no_prefix ? '' : $this->prefix).$table_name." (\n";
+
+		// Go through every schema element and add it to the query
+		foreach ($schema['FIELDS'] as $field_name => $field_data)
+		{
+			$field_data['datatype'] = preg_replace(array_keys($this->datatype_transformations), array_values($this->datatype_transformations), $field_data['datatype']);
+
+			$query .= $field_name.' '.$field_data['datatype'];
+
+			// The SERIAL datatype is a special case where we don't need to say not null
+			if (!$field_data['allow_null'] && $field_data['datatype'] != 'SERIAL')
+				$query .= ' NOT NULL';
+
+			if (isset($field_data['default']))
+				$query .= ' DEFAULT '.$field_data['default'];
+
+			$query .= ",\n";
+		}
+
+		// If we have a primary key, add it
+		if (isset($schema['PRIMARY KEY']))
+			$query .= 'PRIMARY KEY ('.implode(',', $schema['PRIMARY KEY']).'),'."\n";
+
+		// Add unique keys
+		if (isset($schema['UNIQUE KEYS']))
+		{
+			foreach ($schema['UNIQUE KEYS'] as $key_name => $key_fields)
+				$query .= 'UNIQUE ('.implode(',', $key_fields).'),'."\n";
+		}
+
+		// We remove the last two characters (a newline and a comma) and add on the ending
+		$query = substr($query, 0, strlen($query) - 2)."\n".')';
+
+		$result = $this->query($query) ? true : false;
+
+		// Add indexes
+		if (isset($schema['INDEXES']))
+		{
+			foreach ($schema['INDEXES'] as $index_name => $index_fields)
+				$result &= $this->add_index($table_name, $index_name, $index_fields, false, $no_prefix);
+		}
+
+		return $result;
+	}
+
+
+	function drop_table($table_name, $no_prefix = false)
+	{
+		if (!$this->table_exists($table_name, $no_prefix))
+			return true;
+
+		return $this->query('DROP TABLE '.($no_prefix ? '' : $this->prefix).$table_name) ? true : false;
+	}
+
+
+	function rename_table($old_table, $new_table, $no_prefix = false)
+	{
+		// If there new table exists and the old one doesn't, then we're happy
+		if ($this->table_exists($new_table, $no_prefix) && !$this->table_exists($old_table, $no_prefix))
+			return true;
+
+		return $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$old_table.' RENAME TO '.($no_prefix ? '' : $this->prefix).$new_table) ? true : false;
+	}
+
+
+	function add_field($table_name, $field_name, $field_type, $allow_null, $default_value = null, $after_field = null, $no_prefix = false)
+	{
+		if ($this->field_exists($table_name, $field_name, $no_prefix))
+			return true;
+
+		$field_type = preg_replace(array_keys($this->datatype_transformations), array_values($this->datatype_transformations), $field_type);
+
+		$result = $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$table_name.' ADD '.$field_name.' '.$field_type) ? true : false;
+
+		if (!is_null($default_value))
+		{
+			if (!is_int($default_value) && !is_float($default_value))
+				$default_value = '\''.$this->escape($default_value).'\'';
+
+			$result &= $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$table_name.' ALTER '.$field_name.' SET DEFAULT '.$default_value) ? true : false;
+			$result &= $this->query('UPDATE '.($no_prefix ? '' : $this->prefix).$table_name.' SET '.$field_name.'='.$default_value) ? true : false;
+		}
+
+		if (!$allow_null)
+			$result &= $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$table_name.' ALTER '.$field_name.' SET NOT NULL') ? true : false;
+
+		return $result;
+	}
+
+
+	function alter_field($table_name, $field_name, $field_type, $allow_null, $default_value = null, $after_field = null, $no_prefix = false)
+	{
+		if (!$this->field_exists($table_name, $field_name, $no_prefix))
+			return true;
+
+		$field_type = preg_replace(array_keys($this->datatype_transformations), array_values($this->datatype_transformations), $field_type);
+
+		$result = $this->add_field($table_name, 'tmp_'.$field_name, $field_type, $allow_null, $default_value, $after_field, $no_prefix);
+		$result &= $this->query('UPDATE '.($no_prefix ? '' : $this->prefix).$table_name.' SET tmp_'.$field_name.' = '.$field_name) ? true : false;
+		$result &= $this->drop_field($table_name, $field_name, $no_prefix);
+		$result &= $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$table_name.' RENAME COLUMN tmp_'.$field_name.' TO '.$field_name) ? true : false;
+
+		return $result;
+	}
+
+
+	function drop_field($table_name, $field_name, $no_prefix = false)
+	{
+		if (!$this->field_exists($table_name, $field_name, $no_prefix))
+			return true;
+
+		return $this->query('ALTER TABLE '.($no_prefix ? '' : $this->prefix).$table_name.' DROP '.$field_name) ? true : false;
+	}
+
+
+	function add_index($table_name, $index_name, $index_fields, $unique = false, $no_prefix = false)
+	{
+		if ($this->index_exists($table_name, $index_name, $no_prefix))
+			return true;
+
+		return $this->query('CREATE '.($unique ? 'UNIQUE ' : '').'INDEX '.($no_prefix ? '' : $this->prefix).$table_name.'_'.$index_name.' ON '.($no_prefix ? '' : $this->prefix).$table_name.'('.implode(',', $index_fields).')') ? true : false;
+	}
+
+
+	function drop_index($table_name, $index_name, $no_prefix = false)
+	{
+		if (!$this->index_exists($table_name, $index_name, $no_prefix))
+			return true;
+
+		return $this->query('DROP INDEX '.($no_prefix ? '' : $this->prefix).$table_name.'_'.$index_name) ? true : false;
+	}
+
+	function truncate_table($table_name, $no_prefix = false)
+	{
+		return $this->query('DELETE FROM '.($no_prefix ? '' : $this->prefix).$table_name) ? true : false;
+	}
+}
diff --git a/include/dblayer/sqlite.php b/include/dblayer/sqlite.php
new file mode 100644
index 0000000..e83ec70
--- /dev/null
+++ b/include/dblayer/sqlite.php
@@ -0,0 +1,567 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// Make sure we have built in support for SQLite
+if (!function_exists('sqlite_open'))
+	exit('This PHP environment doesn\'t have SQLite support built in. SQLite support is required if you want to use a SQLite database to run this forum. Consult the PHP documentation for further assistance.');
+
+
+class DBLayer
+{
+	var $prefix;
+	var $link_id;
+	var $query_result;
+	var $in_transaction = 0;
+
+	var $saved_queries = array();
+	var $num_queries = 0;
+
+	var $error_no = false;
+	var $error_msg = 'Unknown';
+
+	var $datatype_transformations = array(
+		'%^SERIAL$%'															=>	'INTEGER',
+		'%^(TINY|SMALL|MEDIUM|BIG)?INT( )?(\\([0-9]+\\))?( )?(UNSIGNED)?$%i'	=>	'INTEGER',
+		'%^(TINY|MEDIUM|LONG)?TEXT$%i'											=>	'TEXT'
+	);
+
+
+	function DBLayer($db_host, $db_username, $db_password, $db_name, $db_prefix, $p_connect)
+	{
+		// Prepend $db_name with the path to the forum root directory
+		$db_name = PUN_ROOT.$db_name;
+
+		$this->prefix = $db_prefix;
+
+		if (!file_exists($db_name))
+		{
+			@touch($db_name);
+			@chmod($db_name, 0666);
+			if (!file_exists($db_name))
+				error('Unable to create new database \''.$db_name.'\'. Permission denied', __FILE__, __LINE__);
+		}
+
+		if (!is_readable($db_name))
+			error('Unable to open database \''.$db_name.'\' for reading. Permission denied', __FILE__, __LINE__);
+
+		if (!forum_is_writable($db_name))
+			error('Unable to open database \''.$db_name.'\' for writing. Permission denied', __FILE__, __LINE__);
+
+		if ($p_connect)
+			$this->link_id = @sqlite_popen($db_name, 0666, $sqlite_error);
+		else
+			$this->link_id = @sqlite_open($db_name, 0666, $sqlite_error);
+
+		if (!$this->link_id)
+			error('Unable to open database \''.$db_name.'\'. SQLite reported: '.$sqlite_error, __FILE__, __LINE__);
+		else
+			return $this->link_id;
+	}
+
+
+	function start_transaction()
+	{
+		++$this->in_transaction;
+
+		return (@sqlite_query($this->link_id, 'BEGIN')) ? true : false;
+	}
+
+
+	function end_transaction()
+	{
+		--$this->in_transaction;
+
+		if (@sqlite_query($this->link_id, 'COMMIT'))
+			return true;
+		else
+		{
+			@sqlite_query($this->link_id, 'ROLLBACK');
+			return false;
+		}
+	}
+
+
+	function query($sql, $unbuffered = false)
+	{
+		if (defined('PUN_SHOW_QUERIES'))
+			$q_start = get_microtime();
+
+		if ($unbuffered)
+			$this->query_result = @sqlite_unbuffered_query($this->link_id, $sql);
+		else
+			$this->query_result = @sqlite_query($this->link_id, $sql);
+
+		if ($this->query_result)
+		{
+			if (defined('PUN_SHOW_QUERIES'))
+				$this->saved_queries[] = array($sql, sprintf('%.5f', get_microtime() - $q_start));
+
+			++$this->num_queries;
+
+			return $this->query_result;
+		}
+		else
+		{
+			if (defined('PUN_SHOW_QUERIES'))
+				$this->saved_queries[] = array($sql, 0);
+
+			$this->error_no = @sqlite_last_error($this->link_id);
+			$this->error_msg = @sqlite_error_string($this->error_no);
+
+			if ($this->in_transaction)
+				@sqlite_query($this->link_id, 'ROLLBACK');
+
+			--$this->in_transaction;
+
+			return false;
+		}
+	}
+
+
+	function result($query_id = 0, $row = 0, $col = 0)
+	{
+		if ($query_id)
+		{
+			if ($row !== 0 && @sqlite_seek($query_id, $row) === false)
+				return false;
+
+			$cur_row = @sqlite_current($query_id);
+			if ($cur_row === false)
+				return false;
+
+			return $cur_row[$col];
+		}
+		else
+			return false;
+	}
+
+
+	function fetch_assoc($query_id = 0)
+	{
+		if ($query_id)
+		{
+			$cur_row = @sqlite_fetch_array($query_id, SQLITE_ASSOC);
+			if ($cur_row)
+			{
+				// Horrible hack to get rid of table names and table aliases from the array keys
+				foreach ($cur_row as $key => $value)
+				{
+					$dot_spot = strpos($key, '.');
+					if ($dot_spot !== false)
+					{
+						unset($cur_row[$key]);
+						$key = substr($key, $dot_spot+1);
+						$cur_row[$key] = $value;
+					}
+				}
+			}
+
+			return $cur_row;
+		}
+		else
+			return false;
+	}
+
+
+	function fetch_row($query_id = 0)
+	{
+		return ($query_id) ? @sqlite_fetch_array($query_id, SQLITE_NUM) : false;
+	}
+
+
+	function num_rows($query_id = 0)
+	{
+		return ($query_id) ? @sqlite_num_rows($query_id) : false;
+	}
+
+
+	function affected_rows()
+	{
+		return ($this->link_id) ? @sqlite_changes($this->link_id) : false;
+	}
+
+
+	function insert_id()
+	{
+		return ($this->link_id) ? @sqlite_last_insert_rowid($this->link_id) : false;
+	}
+
+
+	function get_num_queries()
+	{
+		return $this->num_queries;
+	}
+
+
+	function get_saved_queries()
+	{
+		return $this->saved_queries;
+	}
+
+
+	function free_result($query_id = false)
+	{
+		return true;
+	}
+
+
+	function escape($str)
+	{
+		return is_array($str) ? '' : sqlite_escape_string($str);
+	}
+
+
+	function error()
+	{
+		$result['error_sql'] = @current(@end($this->saved_queries));
+		$result['error_no'] = $this->error_no;
+		$result['error_msg'] = $this->error_msg;
+
+		return $result;
+	}
+
+
+	function close()
+	{
+		if ($this->link_id)
+		{
+			if ($this->in_transaction)
+			{
+				if (defined('PUN_SHOW_QUERIES'))
+					$this->saved_queries[] = array('COMMIT', 0);
+
+				@sqlite_query($this->link_id, 'COMMIT');
+			}
+
+			return @sqlite_close($this->link_id);
+		}
+		else
+			return false;
+	}
+
+
+	function get_names()
+	{
+		return '';
+	}
+
+
+	function set_names($names)
+	{
+		return true;
+	}
+
+
+	function get_version()
+	{
+		return array(
+			'name'		=> 'SQLite',
+			'version'	=> sqlite_libversion()
+		);
+	}
+
+
+	function table_exists($table_name, $no_prefix = false)
+	{
+		$result = $this->query('SELECT 1 FROM sqlite_master WHERE name = \''.($no_prefix ? '' : $this->prefix).$this->escape($table_name).'\' AND type=\'table\'');
+		return $this->num_rows($result) > 0;
+	}
+
+
+	function field_exists($table_name, $field_name, $no_prefix = false)
+	{
+		$result = $this->query('SELECT sql FROM sqlite_master WHERE name = \''.($no_prefix ? '' : $this->prefix).$this->escape($table_name).'\' AND type=\'table\'');
+		if (!$this->num_rows($result))
+			return false;
+
+		return preg_match('%[\r\n]'.preg_quote($field_name, '%').' %', $this->result($result));
+	}
+
+
+	function index_exists($table_name, $index_name, $no_prefix = false)
+	{
+		$result = $this->query('SELECT 1 FROM sqlite_master WHERE tbl_name = \''.($no_prefix ? '' : $this->prefix).$this->escape($table_name).'\' AND name = \''.($no_prefix ? '' : $this->prefix).$this->escape($table_name).'_'.$this->escape($index_name).'\' AND type=\'index\'');
+		return $this->num_rows($result) > 0;
+	}
+
+
+	function create_table($table_name, $schema, $no_prefix = false)
+	{
+		if ($this->table_exists($table_name, $no_prefix))
+			return true;
+
+		$query = 'CREATE TABLE '.($no_prefix ? '' : $this->prefix).$table_name." (\n";
+
+		// Go through every schema element and add it to the query
+		foreach ($schema['FIELDS'] as $field_name => $field_data)
+		{
+			$field_data['datatype'] = preg_replace(array_keys($this->datatype_transformations), array_values($this->datatype_transformations), $field_data['datatype']);
+
+			$query .= $field_name.' '.$field_data['datatype'];
+
+			if (!$field_data['allow_null'])
+				$query .= ' NOT NULL';
+
+			if (isset($field_data['default']))
+				$query .= ' DEFAULT '.$field_data['default'];
+
+			$query .= ",\n";
+		}
+
+		// If we have a primary key, add it
+		if (isset($schema['PRIMARY KEY']))
+			$query .= 'PRIMARY KEY ('.implode(',', $schema['PRIMARY KEY']).'),'."\n";
+
+		// Add unique keys
+		if (isset($schema['UNIQUE KEYS']))
+		{
+			foreach ($schema['UNIQUE KEYS'] as $key_name => $key_fields)
+				$query .= 'UNIQUE ('.implode(',', $key_fields).'),'."\n";
+		}
+
+		// We remove the last two characters (a newline and a comma) and add on the ending
+		$query = substr($query, 0, strlen($query) - 2)."\n".')';
+
+		$result = $this->query($query) ? true : false;
+
+		// Add indexes
+		if (isset($schema['INDEXES']))
+		{
+			foreach ($schema['INDEXES'] as $index_name => $index_fields)
+				$result &= $this->add_index($table_name, $index_name, $index_fields, false, $no_prefix);
+		}
+
+		return $result;
+	}
+
+
+	function drop_table($table_name, $no_prefix = false)
+	{
+		if (!$this->table_exists($table_name, $no_prefix))
+			return true;
+
+		return $this->query('DROP TABLE '.($no_prefix ? '' : $this->prefix).$this->escape($table_name)) ? true : false;
+	}
+
+
+	function rename_table($old_name, $new_name, $no_prefix = false)
+	{
+		// If there new table exists and the old one doesn't, then we're happy
+		if ($this->table_exists($new_table, $no_prefix) && !$this->table_exists($old_table, $no_prefix))
+			return true;
+
+		$table = $this->get_table_info($old_name, $no_prefix);
+
+		// Create new table
+		$newtable = str_replace('CREATE TABLE '.($no_prefix ? '' : $this->prefix).$this->escape($old_name).' (', 'CREATE TABLE '.($no_prefix ? '' : $this->prefix).$this->escape($new_name).' (', $table['sql']);
+		$result = $this->query($newtable) ? true : false;
+
+		// Recreate indexes
+		if (!empty($table['indices']))
+		{
+			foreach ($table['indices'] as $cur_index)
+				$result &= $this->query($cur_index) ? true : false;
+		}
+
+		// Copy content across
+		$result &= $this->query('INSERT INTO '.($no_prefix ? '' : $this->prefix).$this->escape($new_name).' SELECT * FROM '.($no_prefix ? '' : $this->prefix).$this->escape($old_name)) ? true : false;
+
+		// Drop old table
+		$result &= $this->drop_table($table_name, $no_prefix);
+
+		return $result;
+	}
+
+
+	function get_table_info($table_name, $no_prefix = false)
+	{
+		// Grab table info
+		$result = $this->query('SELECT sql FROM sqlite_master WHERE tbl_name = \''.($no_prefix ? '' : $this->prefix).$this->escape($table_name).'\' ORDER BY type DESC') or error('Unable to fetch table information', __FILE__, __LINE__, $this->error());
+		$num_rows = $this->num_rows($result);
+
+		if ($num_rows == 0)
+			return;
+
+		$table = array();
+		$table['indices'] = array();
+		while ($cur_index = $this->fetch_assoc($result))
+		{
+			if (empty($cur_index['sql']))
+				continue;
+
+			if (!isset($table['sql']))
+				$table['sql'] = $cur_index['sql'];
+			else
+				$table['indices'][] = $cur_index['sql'];
+		}
+
+		// Work out the columns in the table currently
+		$table_lines = explode("\n", $table['sql']);
+		$table['columns'] = array();
+		foreach ($table_lines as $table_line)
+		{
+			$table_line = trim($table_line, " \t\n\r,"); // trim spaces, tabs, newlines, and commas
+			if (substr($table_line, 0, 12) == 'CREATE TABLE')
+				continue;
+			else if (substr($table_line, 0, 11) == 'PRIMARY KEY')
+				$table['primary_key'] = $table_line;
+			else if (substr($table_line, 0, 6) == 'UNIQUE')
+				$table['unique'] = $table_line;
+			else if (substr($table_line, 0, strpos($table_line, ' ')) != '')
+				$table['columns'][substr($table_line, 0, strpos($table_line, ' '))] = trim(substr($table_line, strpos($table_line, ' ')));
+		}
+
+		return $table;
+	}
+
+
+	function add_field($table_name, $field_name, $field_type, $allow_null, $default_value = null, $after_field = 0, $no_prefix = false)
+	{
+		if ($this->field_exists($table_name, $field_name, $no_prefix))
+			return true;
+
+		$table = $this->get_table_info($table_name, $no_prefix);
+
+		// Create temp table
+		$now = time();
+		$tmptable = str_replace('CREATE TABLE '.($no_prefix ? '' : $this->prefix).$this->escape($table_name).' (', 'CREATE TABLE '.($no_prefix ? '' : $this->prefix).$this->escape($table_name).'_t'.$now.' (', $table['sql']);
+		$result = $this->query($tmptable) ? true : false;
+		$result &= $this->query('INSERT INTO '.($no_prefix ? '' : $this->prefix).$this->escape($table_name).'_t'.$now.' SELECT * FROM '.($no_prefix ? '' : $this->prefix).$this->escape($table_name)) ? true : false;
+
+		// Create new table sql
+		$field_type = preg_replace(array_keys($this->datatype_transformations), array_values($this->datatype_transformations), $field_type);
+		$query = $field_type;
+		if (!$allow_null)
+			$query .= ' NOT NULL';
+		if (is_null($default_value) || $default_value === '')
+			$default_value = '\'\'';
+
+		$query .= ' DEFAULT '.$default_value;
+
+		$old_columns = array_keys($table['columns']);
+		array_insert($table['columns'], $after_field, $query, $field_name);
+
+		$new_table = 'CREATE TABLE '.($no_prefix ? '' : $this->prefix).$this->escape($table_name).' (';
+
+		foreach ($table['columns'] as $cur_column => $column_details)
+			$new_table .= "\n".$cur_column.' '.$column_details.',';
+
+		if (isset($table['unique']))
+			$new_table .= "\n".$table['unique'].',';
+
+		if (isset($table['primary_key']))
+			$new_table .= "\n".$table['primary_key'].',';
+
+		$new_table = trim($new_table, ',')."\n".');';
+
+		// Drop old table
+		$result &= $this->drop_table($table_name, $no_prefix);
+
+		// Create new table
+		$result &= $this->query($new_table) ? true : false;
+
+		// Recreate indexes
+		if (!empty($table['indices']))
+		{
+			foreach ($table['indices'] as $cur_index)
+				$result &= $this->query($cur_index) ? true : false;
+		}
+
+		// Copy content back
+		$result &= $this->query('INSERT INTO '.($no_prefix ? '' : $this->prefix).$this->escape($table_name).' ('.implode(', ', $old_columns).') SELECT * FROM '.($no_prefix ? '' : $this->prefix).$this->escape($table_name).'_t'.$now) ? true : false;
+
+		// Drop temp table
+		$result &= $this->drop_table($table_name.'_t'.$now, $no_prefix);
+
+		return $result;
+	}
+
+
+	function alter_field($table_name, $field_name, $field_type, $allow_null, $default_value = null, $after_field = 0, $no_prefix = false)
+	{
+		// Unneeded for SQLite
+		return true;
+	}
+
+
+	function drop_field($table_name, $field_name, $no_prefix = false)
+	{
+		if (!$this->field_exists($table_name, $field_name, $no_prefix))
+			return true;
+
+		$table = $this->get_table_info($table_name, $no_prefix);
+
+		// Create temp table
+		$now = time();
+		$tmptable = str_replace('CREATE TABLE '.($no_prefix ? '' : $this->prefix).$this->escape($table_name).' (', 'CREATE TABLE '.($no_prefix ? '' : $this->prefix).$this->escape($table_name).'_t'.$now.' (', $table['sql']);
+		$result = $this->query($tmptable) ? true : false;
+		$result &= $this->query('INSERT INTO '.($no_prefix ? '' : $this->prefix).$this->escape($table_name).'_t'.$now.' SELECT * FROM '.($no_prefix ? '' : $this->prefix).$this->escape($table_name)) ? true : false;
+
+		// Work out the columns we need to keep and the sql for the new table
+		unset($table['columns'][$field_name]);
+		$new_columns = array_keys($table['columns']);
+
+		$new_table = 'CREATE TABLE '.($no_prefix ? '' : $this->prefix).$this->escape($table_name).' (';
+
+		foreach ($table['columns'] as $cur_column => $column_details)
+			$new_table .= "\n".$cur_column.' '.$column_details.',';
+
+		if (isset($table['unique']))
+			$new_table .= "\n".$table['unique'].',';
+
+		if (isset($table['primary_key']))
+			$new_table .= "\n".$table['primary_key'].',';
+
+		$new_table = trim($new_table, ',')."\n".');';
+
+		// Drop old table
+		$result &= $this->drop_table($table_name, $no_prefix);
+
+		// Create new table
+		$result &= $this->query($new_table) ? true : false;
+
+		// Recreate indexes
+		if (!empty($table['indices']))
+		{
+			foreach ($table['indices'] as $cur_index)
+				if (!preg_match('%\('.preg_quote($field_name, '%').'\)%', $cur_index))
+					$result &= $this->query($cur_index) ? true : false;
+		}
+
+		// Copy content back
+		$result &= $this->query('INSERT INTO '.($no_prefix ? '' : $this->prefix).$this->escape($table_name).' SELECT '.implode(', ', $new_columns).' FROM '.($no_prefix ? '' : $this->prefix).$this->escape($table_name).'_t'.$now) ? true : false;
+
+		// Drop temp table
+		$result &= $this->drop_table($table_name.'_t'.$now, $no_prefix);
+
+		return $result;
+	}
+
+
+	function add_index($table_name, $index_name, $index_fields, $unique = false, $no_prefix = false)
+	{
+		if ($this->index_exists($table_name, $index_name, $no_prefix))
+			return true;
+
+		return $this->query('CREATE '.($unique ? 'UNIQUE ' : '').'INDEX '.($no_prefix ? '' : $this->prefix).$table_name.'_'.$index_name.' ON '.($no_prefix ? '' : $this->prefix).$table_name.'('.implode(',', $index_fields).')') ? true : false;
+	}
+
+
+	function drop_index($table_name, $index_name, $no_prefix = false)
+	{
+		if (!$this->index_exists($table_name, $index_name, $no_prefix))
+			return true;
+
+		return $this->query('DROP INDEX '.($no_prefix ? '' : $this->prefix).$table_name.'_'.$index_name) ? true : false;
+	}
+
+	function truncate_table($table_name, $no_prefix = false)
+	{
+		return $this->query('DELETE FROM '.($no_prefix ? '' : $this->prefix).$table_name) ? true : false;
+	}
+}
diff --git a/include/email.php b/include/email.php
new file mode 100644
index 0000000..85543d1
--- /dev/null
+++ b/include/email.php
@@ -0,0 +1,351 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// Make sure no one attempts to run this script "directly"
+if (!defined('PUN'))
+	exit;
+
+require PUN_ROOT.'include/utf8/utils/ascii.php';
+
+//
+// Validate an email address
+//
+function is_valid_email($email)
+{
+	if (strlen($email) > 80)
+		return false;
+
+	return preg_match('%^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|("[^"]+"))@((\[\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\])|(([a-zA-Z\d\-]+\.)+[a-zA-Z]{2,}))$%', $email);
+}
+
+
+//
+// Check if $email is banned
+//
+function is_banned_email($email)
+{
+	global $pun_bans;
+
+	foreach ($pun_bans as $cur_ban)
+	{
+		if ($cur_ban['email'] != '' &&
+			($email == $cur_ban['email'] ||
+			(strpos($cur_ban['email'], '@') === false && stristr($email, '@'.$cur_ban['email']))))
+			return true;
+	}
+
+	return false;
+}
+
+
+//
+// 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).'?=';
+}
+
+
+//
+// Make a post email safe
+//
+function bbcode2email($text, $wrap_length = 72)
+{
+	static $base_url;
+
+	if (!isset($base_url))
+		$base_url = get_base_url();
+
+	$text = pun_trim($text, "\t\n ");
+
+	$shortcut_urls = array(
+		'topic' => '/viewtopic.php?id=$1',
+		'post' => '/viewtopic.php?pid=$1#p$1',
+		'forum' => '/viewforum.php?id=$1',
+		'user' => '/profile.php?id=$1',
+	);
+
+	// Split code blocks and text so BBcode in codeblocks won't be touched
+	list($code, $text) = extract_blocks($text, '[code]', '[/code]');
+
+	// Strip all bbcodes, except the quote, url, img, email, code and list items bbcodes
+	$text = preg_replace(array(
+		'%\[/?(?!(?:quote|url|topic|post|user|forum|img|email|code|list|\*))[a-z]+(?:=[^\]]+)?\]%i',
+		'%\n\[/?list(?:=[^\]]+)?\]%i' // A separate regex for the list tags to get rid of some whitespace
+	), '', $text);
+
+	// Match the deepest nested bbcode
+	// An adapted example from Mastering Regular Expressions
+	$match_quote_regex = '%
+		\[(quote|\*|url|img|email|topic|post|user|forum)(?:=([^\]]+))?\]
+		(
+			(?>[^\[]*)
+			(?>
+				(?!\[/?\1(?:=[^\]]+)?\])
+				\[
+				[^\[]*
+			)*
+		)
+		\[/\1\]
+	%ix';
+
+	$url_index = 1;
+	$url_stack = array();
+	while (preg_match($match_quote_regex, $text, $matches))
+	{
+		// Quotes
+		if ($matches[1] == 'quote')
+		{
+			// Put '>' or '> ' at the start of a line
+			$replacement = preg_replace(
+				array('%^(?=\>)%m', '%^(?!\>)%m'),
+				array('>', '> '),
+				$matches[2]." said:\n".$matches[3]);
+		}
+
+		// List items
+		elseif ($matches[1] == '*')
+		{
+			$replacement = ' * '.$matches[3];
+		}
+
+		// URLs and emails
+		elseif (in_array($matches[1], array('url', 'email')))
+		{
+			if (!empty($matches[2]))
+			{
+				$replacement = '['.$matches[3].']['.$url_index.']';
+				$url_stack[$url_index] = $matches[2];
+				$url_index++;
+			}
+			else
+				$replacement = '['.$matches[3].']';
+		}
+
+		// Images
+		elseif ($matches[1] == 'img')
+		{
+			if (!empty($matches[2]))
+				$replacement = '['.$matches[2].']['.$url_index.']';
+			else
+				$replacement = '['.basename($matches[3]).']['.$url_index.']';
+
+			$url_stack[$url_index] = $matches[3];
+			$url_index++;
+		}
+
+		// Topic, post, forum and user URLs
+		elseif (in_array($matches[1], array('topic', 'post', 'forum', 'user')))
+		{
+			$url = isset($shortcut_urls[$matches[1]]) ? $base_url.$shortcut_urls[$matches[1]] : '';
+
+			if (!empty($matches[2]))
+			{
+				$replacement = '['.$matches[3].']['.$url_index.']';
+				$url_stack[$url_index] = str_replace('$1', $matches[2], $url);
+				$url_index++;
+			}
+			else
+				$replacement = '['.str_replace('$1', $matches[3], $url).']';
+		}
+
+		// Update the main text if there is a replacment
+		if (!is_null($replacement))
+		{
+			$text = str_replace($matches[0], $replacement, $text);
+			$replacement = null;
+		}
+	}
+
+	// Put code blocks and text together
+	if (isset($code))
+	{
+		$parts = explode("\1", $text);
+		$text = '';
+		foreach ($parts as $i => $part)
+		{
+			$text .= $part;
+			if (isset($code[$i]))
+				$text .= trim($code[$i], "\n\r");
+		}
+	}
+
+	// Put URLs at the bottom
+	if ($url_stack)
+	{
+		$text .= "\n\n";
+		foreach ($url_stack as $i => $url)
+			$text .= "\n".' ['.$i.']: '.$url;
+	}
+
+	// Wrap lines if $wrap_length is higher than -1
+	if ($wrap_length > -1)
+	{
+		// Split all lines and wrap them individually
+		$parts = explode("\n", $text);
+		foreach ($parts as $k => $part)
+		{
+			preg_match('%^(>+ )?(.*)%', $part, $matches);
+			$parts[$k] = wordwrap($matches[1].$matches[2], $wrap_length -
+				strlen($matches[1]), "\n".$matches[1]);
+		}
+
+		return implode("\n", $parts);
+	}
+	else
+		return $text;
+}
+
+
+//
+// Wrapper for PHP's mail()
+//
+function pun_mail($to, $subject, $message, $reply_to_email = '', $reply_to_name = '')
+{
+	global $pun_config, $lang_common;
+
+	// Default sender/return address
+	$from_name = sprintf($lang_common['Mailer'], $pun_config['o_board_title']);
+	$from_email = $pun_config['o_webmaster_email'];
+
+	// Do a little spring cleaning
+	$to = pun_trim(preg_replace('%[\n\r]+%s', '', $to));
+	$subject = pun_trim(preg_replace('%[\n\r]+%s', '', $subject));
+	$from_email = pun_trim(preg_replace('%[\n\r:]+%s', '', $from_email));
+	$from_name = pun_trim(preg_replace('%[\n\r:]+%s', '', str_replace('"', '', $from_name)));
+	$reply_to_email = pun_trim(preg_replace('%[\n\r:]+%s', '', $reply_to_email));
+	$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 = '"'.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 = '"'.encode_mail_text($reply_to_name).'" <'.$reply_to_email.'>';
+
+		$headers .= "\r\n".'Reply-To: '.$reply_to;
+	}
+
+	// Make sure all linebreaks are LF in message (and strip out any NULL bytes)
+	$message = str_replace("\0", '', pun_linebreaks($message));
+
+	if ($pun_config['o_smtp_host'] != '')
+	{
+		// Headers should be \r\n
+		// Message should be ??
+		$message = str_replace("\n", "\r\n", $message);
+		smtp_mail($to, $subject, $message, $headers);
+	}
+	else
+	{
+		// Headers should be \r\n
+		// Message should be \n
+		mail($to, $subject, $message, $headers);
+	}
+}
+
+
+//
+// This function was originally a part of the phpBB Group forum software phpBB2 (http://www.phpbb.com)
+// They deserve all the credit for writing it. I made small modifications for it to suit PunBB and it's coding standards
+//
+function server_parse($socket, $expected_response)
+{
+	$server_response = '';
+	while (substr($server_response, 3, 1) != ' ')
+	{
+		if (!($server_response = fgets($socket, 256)))
+			error('Couldn\'t get mail server response codes. Please contact the forum administrator.', __FILE__, __LINE__);
+	}
+
+	if (!(substr($server_response, 0, 3) == $expected_response))
+		error('Unable to send email. Please contact the forum administrator with the following error message reported by the SMTP server: "'.$server_response.'"', __FILE__, __LINE__);
+}
+
+
+//
+// This function was originally a part of the phpBB Group forum software phpBB2 (http://www.phpbb.com)
+// They deserve all the credit for writing it. I made small modifications for it to suit PunBB and it's coding standards.
+//
+function smtp_mail($to, $subject, $message, $headers = '')
+{
+	global $pun_config;
+
+	$recipients = explode(',', $to);
+
+	// Sanitize the message
+	$message = str_replace("\r\n.", "\r\n..", $message);
+	$message = (substr($message, 0, 1) == '.' ? '.'.$message : $message);
+
+	// Are we using port 25 or a custom port?
+	if (strpos($pun_config['o_smtp_host'], ':') !== false)
+		list($smtp_host, $smtp_port) = explode(':', $pun_config['o_smtp_host']);
+	else
+	{
+		$smtp_host = $pun_config['o_smtp_host'];
+		$smtp_port = 25;
+	}
+
+	if ($pun_config['o_smtp_ssl'] == '1')
+		$smtp_host = 'ssl://'.$smtp_host;
+
+	if (!($socket = fsockopen($smtp_host, $smtp_port, $errno, $errstr, 15)))
+		error('Could not connect to smtp host "'.$pun_config['o_smtp_host'].'" ('.$errno.') ('.$errstr.')', __FILE__, __LINE__);
+
+	server_parse($socket, '220');
+
+	if ($pun_config['o_smtp_user'] != '' && $pun_config['o_smtp_pass'] != '')
+	{
+		fwrite($socket, 'EHLO '.$smtp_host."\r\n");
+		server_parse($socket, '250');
+
+		fwrite($socket, 'AUTH LOGIN'."\r\n");
+		server_parse($socket, '334');
+
+		fwrite($socket, base64_encode($pun_config['o_smtp_user'])."\r\n");
+		server_parse($socket, '334');
+
+		fwrite($socket, base64_encode($pun_config['o_smtp_pass'])."\r\n");
+		server_parse($socket, '235');
+	}
+	else
+	{
+		fwrite($socket, 'HELO '.$smtp_host."\r\n");
+		server_parse($socket, '250');
+	}
+
+	fwrite($socket, 'MAIL FROM: <'.$pun_config['o_webmaster_email'].'>'."\r\n");
+	server_parse($socket, '250');
+
+	foreach ($recipients as $email)
+	{
+		fwrite($socket, 'RCPT TO: <'.$email.'>'."\r\n");
+		server_parse($socket, '250');
+	}
+
+	fwrite($socket, 'DATA'."\r\n");
+	server_parse($socket, '354');
+
+	fwrite($socket, 'Subject: '.$subject."\r\n".'To: <'.implode('>, <', $recipients).'>'."\r\n".$headers."\r\n\r\n".$message."\r\n");
+
+	fwrite($socket, '.'."\r\n");
+	server_parse($socket, '250');
+
+	fwrite($socket, 'QUIT'."\r\n");
+	fclose($socket);
+
+	return true;
+}
diff --git a/include/functions.php b/include/functions.php
new file mode 100644
index 0000000..d8f0b76
--- /dev/null
+++ b/include/functions.php
@@ -0,0 +1,2112 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+include PUN_ROOT.'include/srand.php'; 
+
+//
+// Return current timestamp (with microseconds) as a float
+//
+function get_microtime()
+{
+	list($usec, $sec) = explode(' ', microtime());
+	return ((float)$usec + (float)$sec);
+}
+
+//
+// Cookie stuff!
+//
+function check_cookie(&$pun_user)
+{
+	global $db, $db_type, $pun_config, $cookie_name, $cookie_seed;
+
+	$now = time();
+
+	// If the cookie is set and it matches the correct pattern, then read the values from it
+	if (isset($_COOKIE[$cookie_name]) && preg_match('%^(\d+)\|([0-9a-fA-F]+)\|(\d+)\|([0-9a-fA-F]+)$%', $_COOKIE[$cookie_name], $matches))
+	{
+		$cookie = array(
+			'user_id'			=> intval($matches[1]),
+			'password_hash' 	=> $matches[2],
+			'expiration_time'	=> intval($matches[3]),
+			'cookie_hash'		=> $matches[4],
+		);
+	}
+
+	// If it has a non-guest user, and hasn't expired
+	if (isset($cookie) && $cookie['user_id'] > 1 && $cookie['expiration_time'] > $now)
+	{
+		// If the cookie has been tampered with
+		if (forum_hmac($cookie['user_id'].'|'.$cookie['expiration_time'], $cookie_seed.'_cookie_hash') != $cookie['cookie_hash'])
+		{
+			$expire = $now + 31536000; // The cookie expires after a year
+			pun_setcookie(1, pun_hash(uniqid(rand(), true)), $expire);
+			set_default_user();
+
+			return;
+		}
+
+		// Check if there's a user with the user ID and password hash from the cookie
+		$result = $db->query('SELECT u.*, g.*, o.logged, o.idle FROM '.$db->prefix.'users AS u INNER JOIN '.$db->prefix.'groups AS g ON u.group_id=g.g_id LEFT JOIN '.$db->prefix.'online AS o ON o.user_id=u.id WHERE u.id='.intval($cookie['user_id'])) or error('Unable to fetch user information', __FILE__, __LINE__, $db->error());
+		$pun_user = $db->fetch_assoc($result);
+
+		// If user authorisation failed
+		if (!isset($pun_user['id']) || forum_hmac($pun_user['password'], $cookie_seed.'_password_hash') !== $cookie['password_hash'])
+		{
+			$expire = $now + 31536000; // The cookie expires after a year
+			pun_setcookie(1, pun_hash(uniqid(rand(), true)), $expire);
+			set_default_user();
+
+			return;
+		}
+
+		// Send a new, updated cookie with a new expiration timestamp
+		$expire = ($cookie['expiration_time'] > $now + $pun_config['o_timeout_visit']) ? $now + 1209600 : $now + $pun_config['o_timeout_visit'];
+		pun_setcookie($pun_user['id'], $pun_user['password'], $expire);
+
+		// Set a default language if the user selected language no longer exists
+		if (!file_exists(PUN_ROOT.'lang/'.$pun_user['language']))
+			$pun_user['language'] = $pun_config['o_default_lang'];
+
+		// Set a default style if the user selected style no longer exists
+		if (!file_exists(PUN_ROOT.'style/'.$pun_user['style'].'.css'))
+			$pun_user['style'] = $pun_config['o_default_style'];
+
+		if (!$pun_user['disp_topics'])
+			$pun_user['disp_topics'] = $pun_config['o_disp_topics_default'];
+		if (!$pun_user['disp_posts'])
+			$pun_user['disp_posts'] = $pun_config['o_disp_posts_default'];
+
+		// Define this if you want this visit to affect the online list and the users last visit data
+		if (!defined('PUN_QUIET_VISIT'))
+		{
+			// Update the online list
+			if (!$pun_user['logged'])
+			{
+				$pun_user['logged'] = $now;
+
+				// With MySQL/MySQLi/SQLite, REPLACE INTO avoids a user having two rows in the online table
+				switch ($db_type)
+				{
+					case 'mysql':
+					case 'mysqli':
+					case 'mysql_innodb':
+					case 'mysqli_innodb':
+					case 'sqlite':
+						$db->query('REPLACE INTO '.$db->prefix.'online (user_id, ident, logged) VALUES('.$pun_user['id'].', \''.$db->escape($pun_user['username']).'\', '.$pun_user['logged'].')') or error('Unable to insert into online list', __FILE__, __LINE__, $db->error());
+						break;
+
+					default:
+						$db->query('INSERT INTO '.$db->prefix.'online (user_id, ident, logged) SELECT '.$pun_user['id'].', \''.$db->escape($pun_user['username']).'\', '.$pun_user['logged'].' WHERE NOT EXISTS (SELECT 1 FROM '.$db->prefix.'online WHERE user_id='.$pun_user['id'].')') or error('Unable to insert into online list', __FILE__, __LINE__, $db->error());
+						break;
+				}
+
+				// Reset tracked topics
+				set_tracked_topics(null);
+			}
+			else
+			{
+				// Special case: We've timed out, but no other user has browsed the forums since we timed out
+				if ($pun_user['logged'] < ($now-$pun_config['o_timeout_visit']))
+				{
+					$db->query('UPDATE '.$db->prefix.'users SET last_visit='.$pun_user['logged'].' WHERE id='.$pun_user['id']) or error('Unable to update user visit data', __FILE__, __LINE__, $db->error());
+					$pun_user['last_visit'] = $pun_user['logged'];
+				}
+
+				$idle_sql = ($pun_user['idle'] == '1') ? ', idle=0' : '';
+				$db->query('UPDATE '.$db->prefix.'online SET logged='.$now.$idle_sql.' WHERE user_id='.$pun_user['id']) or error('Unable to update online list', __FILE__, __LINE__, $db->error());
+
+				// Update tracked topics with the current expire time
+				if (isset($_COOKIE[$cookie_name.'_track']))
+					forum_setcookie($cookie_name.'_track', $_COOKIE[$cookie_name.'_track'], $now + $pun_config['o_timeout_visit']);
+			}
+		}
+		else
+		{
+			if (!$pun_user['logged'])
+				$pun_user['logged'] = $pun_user['last_visit'];
+		}
+
+		$pun_user['is_guest'] = false;
+		$pun_user['is_admmod'] = $pun_user['g_id'] == PUN_ADMIN || $pun_user['g_moderator'] == '1';
+	}
+	else
+		set_default_user();
+}
+
+
+//
+// Converts the CDATA end sequence ]]> into ]]&gt;
+//
+function escape_cdata($str)
+{
+	return str_replace(']]>', ']]&gt;', $str);
+}
+
+
+//
+// Authenticates the provided username and password against the user database
+// $user can be either a user ID (integer) or a username (string)
+// $password can be either a plaintext password or a password hash including salt ($password_is_hash must be set accordingly)
+//
+function authenticate_user($user, $password, $password_is_hash = false)
+{
+	global $db, $pun_user;
+
+	// Check if there's a user matching $user and $password
+	$result = $db->query('SELECT u.*, g.*, o.logged, o.idle FROM '.$db->prefix.'users AS u INNER JOIN '.$db->prefix.'groups AS g ON g.g_id=u.group_id LEFT JOIN '.$db->prefix.'online AS o ON o.user_id=u.id WHERE '.(is_int($user) ? 'u.id='.intval($user) : 'u.username=\''.$db->escape($user).'\'')) or error('Unable to fetch user info', __FILE__, __LINE__, $db->error());
+	$pun_user = $db->fetch_assoc($result);
+
+	if (!isset($pun_user['id']) ||
+		($password_is_hash && $password != $pun_user['password']) ||
+		(!$password_is_hash && pun_hash($password) != $pun_user['password']))
+		set_default_user();
+	else
+		$pun_user['is_guest'] = false;
+}
+
+
+//
+// Try to determine the current URL
+//
+function get_current_url($max_length = 0)
+{
+	$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']);
+
+	if (strlen($url) <= $max_length || $max_length == 0)
+		return $url;
+
+	// We can't find a short enough url
+	return null;
+}
+
+
+//
+// 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)
+{
+	global $pun_config;
+	static $base_url;
+
+	if (!$support_https)
+		return $pun_config['o_base_url'];
+
+	if (!isset($base_url))
+	{
+		// Make sure we are using the correct protocol
+		$base_url = str_replace(array('http://', 'https://'), get_current_protocol().'://', $pun_config['o_base_url']);
+	}
+
+	return $base_url;
+}
+
+
+//
+// Fill $pun_user with default values (for guests)
+//
+function set_default_user()
+{
+	global $db, $db_type, $pun_user, $pun_config;
+
+	$remote_addr = get_remote_address();
+
+	// Fetch guest user
+	$result = $db->query('SELECT u.*, g.*, o.logged, o.last_post, o.last_search FROM '.$db->prefix.'users AS u INNER JOIN '.$db->prefix.'groups AS g ON u.group_id=g.g_id LEFT JOIN '.$db->prefix.'online AS o ON o.ident=\''.$db->escape($remote_addr).'\' WHERE u.id=1') or error('Unable to fetch guest information', __FILE__, __LINE__, $db->error());
+	if (!$db->num_rows($result))
+		exit('Unable to fetch guest information. Your database must contain both a guest user and a guest user group.');
+
+	$pun_user = $db->fetch_assoc($result);
+
+	// Update online list
+	if (!$pun_user['logged'])
+	{
+		$pun_user['logged'] = time();
+
+		// With MySQL/MySQLi/SQLite, REPLACE INTO avoids a user having two rows in the online table
+		switch ($db_type)
+		{
+			case 'mysql':
+			case 'mysqli':
+			case 'mysql_innodb':
+			case 'mysqli_innodb':
+			case 'sqlite':
+				$db->query('REPLACE INTO '.$db->prefix.'online (user_id, ident, logged) VALUES(1, \''.$db->escape($remote_addr).'\', '.$pun_user['logged'].')') or error('Unable to insert into online list', __FILE__, __LINE__, $db->error());
+				break;
+
+			default:
+				$db->query('INSERT INTO '.$db->prefix.'online (user_id, ident, logged) SELECT 1, \''.$db->escape($remote_addr).'\', '.$pun_user['logged'].' WHERE NOT EXISTS (SELECT 1 FROM '.$db->prefix.'online WHERE ident=\''.$db->escape($remote_addr).'\')') or error('Unable to insert into online list', __FILE__, __LINE__, $db->error());
+				break;
+		}
+	}
+	else
+		$db->query('UPDATE '.$db->prefix.'online SET logged='.time().' WHERE ident=\''.$db->escape($remote_addr).'\'') or error('Unable to update online list', __FILE__, __LINE__, $db->error());
+
+	$pun_user['disp_topics'] = $pun_config['o_disp_topics_default'];
+	$pun_user['disp_posts'] = $pun_config['o_disp_posts_default'];
+	$pun_user['timezone'] = $pun_config['o_default_timezone'];
+	$pun_user['dst'] = $pun_config['o_default_dst'];
+	$pun_user['language'] = $pun_config['o_default_lang'];
+	$pun_user['style'] = $pun_config['o_default_style'];
+	$pun_user['is_guest'] = true;
+	$pun_user['is_admmod'] = false;
+}
+
+
+//
+// SHA1 HMAC with PHP 4 fallback
+//
+function forum_hmac($data, $key, $raw_output = false)
+{
+	if (function_exists('hash_hmac'))
+		return hash_hmac('sha1', $data, $key, $raw_output);
+
+	// If key size more than blocksize then we hash it once
+	if (strlen($key) > 64)
+		$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));
+
+	$hmac_opad = str_repeat(chr(0x5C), 64);
+	$hmac_ipad = str_repeat(chr(0x36), 64);
+
+	// Do inner and outer padding
+	for ($i = 0;$i < 64;$i++) {
+		$hmac_opad[$i] = $hmac_opad[$i] ^ $key[$i];
+		$hmac_ipad[$i] = $hmac_ipad[$i] ^ $key[$i];
+	}
+
+	// Finally, calculate the HMAC
+	$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;
+}
+
+
+//
+// Set a cookie, FluxBB style!
+// Wrapper for forum_setcookie
+//
+function pun_setcookie($user_id, $password_hash, $expire)
+{
+	global $cookie_name, $cookie_seed;
+
+	forum_setcookie($cookie_name, $user_id.'|'.forum_hmac($password_hash, $cookie_seed.'_password_hash').'|'.$expire.'|'.forum_hmac($user_id.'|'.$expire, $cookie_seed.'_cookie_hash'), $expire);
+}
+
+
+//
+// Set a cookie, FluxBB style!
+//
+function forum_setcookie($name, $value, $expire)
+{
+	global $cookie_path, $cookie_domain, $cookie_secure;
+
+	// Enable sending of a P3P header
+	header('P3P: CP="CUR ADM"');
+
+	if (version_compare(PHP_VERSION, '5.2.0', '>='))
+		setcookie($name, $value, $expire, $cookie_path, $cookie_domain, $cookie_secure, true);
+	else
+		setcookie($name, $value, $expire, $cookie_path.'; HttpOnly', $cookie_domain, $cookie_secure);
+}
+
+
+//
+// Check whether the connecting user is banned (and delete any expired bans while we're at it)
+//
+function check_bans()
+{
+	global $db, $pun_config, $lang_common, $pun_user, $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
+	// 192.168.0.5 from matching e.g. 192.168.0.50
+	$user_ip = get_remote_address();
+	$user_ip .= (strpos($user_ip, '.') !== false) ? '.' : ':';
+
+	$bans_altered = false;
+	$is_banned = false;
+
+	foreach ($pun_bans as $cur_ban)
+	{
+		// Has this ban expired?
+		if ($cur_ban['expire'] != '' && $cur_ban['expire'] <= time())
+		{
+			$db->query('DELETE FROM '.$db->prefix.'bans WHERE id='.$cur_ban['id']) or error('Unable to delete expired ban', __FILE__, __LINE__, $db->error());
+			$bans_altered = true;
+			continue;
+		}
+
+		if ($cur_ban['username'] != '' && utf8_strtolower($pun_user['username']) == utf8_strtolower($cur_ban['username']))
+			$is_banned = true;
+
+		if ($cur_ban['ip'] != '')
+		{
+			$cur_ban_ips = explode(' ', $cur_ban['ip']);
+
+			$num_ips = count($cur_ban_ips);
+			for ($i = 0; $i < $num_ips; ++$i)
+			{
+				// Add the proper ending to the ban
+				if (strpos($user_ip, '.') !== false)
+					$cur_ban_ips[$i] = $cur_ban_ips[$i].'.';
+				else
+					$cur_ban_ips[$i] = $cur_ban_ips[$i].':';
+
+				if (substr($user_ip, 0, strlen($cur_ban_ips[$i])) == $cur_ban_ips[$i])
+				{
+					$is_banned = true;
+					break;
+				}
+			}
+		}
+
+		if ($is_banned)
+		{
+			$db->query('DELETE FROM '.$db->prefix.'online WHERE ident=\''.$db->escape($pun_user['username']).'\'') or error('Unable to delete from online list', __FILE__, __LINE__, $db->error());
+			message($lang_common['Ban message'].' '.(($cur_ban['expire'] != '') ? $lang_common['Ban message 2'].' '.strtolower(format_time($cur_ban['expire'], true)).'. ' : '').(($cur_ban['message'] != '') ? $lang_common['Ban message 3'].'<br /><br /><strong>'.pun_htmlspecialchars($cur_ban['message']).'</strong><br /><br />' : '<br /><br />').$lang_common['Ban message 4'].' <a href="mailto:'.$pun_config['o_admin_email'].'">'.$pun_config['o_admin_email'].'</a>.', true);
+		}
+	}
+
+	// If we removed any expired bans during our run-through, we need to regenerate the bans cache
+	if ($bans_altered)
+	{
+		if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+			require PUN_ROOT.'include/cache.php';
+
+		generate_bans_cache();
+	}
+}
+
+
+//
+// Check username
+//
+function check_username($username, $exclude_id = null)
+{
+	global $db, $pun_config, $errors, $lang_prof_reg, $lang_register, $lang_common, $pun_bans;
+
+	// Convert multiple whitespace characters into one (to prevent people from registering with indistinguishable usernames)
+	$username = preg_replace('%\s+%s', ' ', $username);
+
+	// Validate username
+	if (pun_strlen($username) < 2)
+		$errors[] = $lang_prof_reg['Username too short'];
+	else if (pun_strlen($username) > 25) // This usually doesn't happen since the form element only accepts 25 characters
+		$errors[] = $lang_prof_reg['Username too long'];
+	else if (!strcasecmp($username, 'Guest') || !strcasecmp($username, $lang_common['Guest']))
+		$errors[] = $lang_prof_reg['Username guest'];
+	else if (preg_match('%[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}%', $username) || preg_match('%((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))%', $username))
+		$errors[] = $lang_prof_reg['Username IP'];
+	else if ((strpos($username, '[') !== false || strpos($username, ']') !== false) && strpos($username, '\'') !== false && strpos($username, '"') !== false)
+		$errors[] = $lang_prof_reg['Username reserved chars'];
+	else if (preg_match('%(?:\[/?(?:b|u|s|ins|del|em|i|h|colou?r|quote|code|img|url|email|list|\*|topic|post|forum|user)\]|\[(?:img|url|quote|list)=)%i', $username))
+		$errors[] = $lang_prof_reg['Username BBCode'];
+
+	// Check username for any censored words
+	if ($pun_config['o_censoring'] == '1' && censor_words($username) != $username)
+		$errors[] = $lang_register['Username censor'];
+
+	// 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(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))
+	{
+		$busy = $db->result($result);
+		$errors[] = $lang_register['Username dupe 1'].' '.pun_htmlspecialchars($busy).'. '.$lang_register['Username dupe 2'];
+	}
+
+	// Check username for any banned usernames
+	foreach ($pun_bans as $cur_ban)
+	{
+		if ($cur_ban['username'] != '' && utf8_strtolower($username) == utf8_strtolower($cur_ban['username']))
+		{
+			$errors[] = $lang_prof_reg['Banned username'];
+			break;
+		}
+	}
+}
+
+
+//
+// Update "Users online"
+//
+function update_users_online()
+{
+	global $db, $pun_config;
+
+	$now = time();
+
+	// Fetch all online list entries that are older than "o_timeout_online"
+	$result = $db->query('SELECT user_id, ident, logged, idle FROM '.$db->prefix.'online WHERE logged<'.($now-$pun_config['o_timeout_online'])) or error('Unable to fetch old entries from online list', __FILE__, __LINE__, $db->error());
+	while ($cur_user = $db->fetch_assoc($result))
+	{
+		// If the entry is a guest, delete it
+		if ($cur_user['user_id'] == '1')
+			$db->query('DELETE FROM '.$db->prefix.'online WHERE ident=\''.$db->escape($cur_user['ident']).'\'') or error('Unable to delete from online list', __FILE__, __LINE__, $db->error());
+		else
+		{
+			// If the entry is older than "o_timeout_visit", update last_visit for the user in question, then delete him/her from the online list
+			if ($cur_user['logged'] < ($now-$pun_config['o_timeout_visit']))
+			{
+				$db->query('UPDATE '.$db->prefix.'users SET last_visit='.$cur_user['logged'].' WHERE id='.$cur_user['user_id']) or error('Unable to update user visit data', __FILE__, __LINE__, $db->error());
+				$db->query('DELETE FROM '.$db->prefix.'online WHERE user_id='.$cur_user['user_id']) or error('Unable to delete from online list', __FILE__, __LINE__, $db->error());
+			}
+			else if ($cur_user['idle'] == '0')
+				$db->query('UPDATE '.$db->prefix.'online SET idle=1 WHERE user_id='.$cur_user['user_id']) or error('Unable to insert into online list', __FILE__, __LINE__, $db->error());
+		}
+	}
+}
+
+
+//
+// Display the profile navigation menu
+//
+function generate_profile_menu($page = '')
+{
+	global $lang_profile, $pun_config, $pun_user, $id;
+
+?>
+<div id="profile" class="block2col">
+	<div class="blockmenu">
+		<h2><span><?php echo $lang_profile['Profile menu'] ?></span></h2>
+		<div class="box">
+			<div class="inbox">
+				<ul>
+					<li<?php if ($page == 'essentials') echo ' class="isactive"'; ?>><a href="profile.php?section=essentials&amp;id=<?php echo $id ?>"><?php echo $lang_profile['Section essentials'] ?></a></li>
+					<li<?php if ($page == 'personal') echo ' class="isactive"'; ?>><a href="profile.php?section=personal&amp;id=<?php echo $id ?>"><?php echo $lang_profile['Section personal'] ?></a></li>
+					<li<?php if ($page == 'messaging') echo ' class="isactive"'; ?>><a href="profile.php?section=messaging&amp;id=<?php echo $id ?>"><?php echo $lang_profile['Section messaging'] ?></a></li>
+<?php if ($pun_config['o_avatars'] == '1' || $pun_config['o_signatures'] == '1'): ?>					<li<?php if ($page == 'personality') echo ' class="isactive"'; ?>><a href="profile.php?section=personality&amp;id=<?php echo $id ?>"><?php echo $lang_profile['Section personality'] ?></a></li>
+<?php endif; ?>					<li<?php if ($page == 'display') echo ' class="isactive"'; ?>><a href="profile.php?section=display&amp;id=<?php echo $id ?>"><?php echo $lang_profile['Section display'] ?></a></li>
+					<li<?php if ($page == 'privacy') echo ' class="isactive"'; ?>><a href="profile.php?section=privacy&amp;id=<?php echo $id ?>"><?php echo $lang_profile['Section privacy'] ?></a></li>
+<?php if ($pun_user['g_id'] == PUN_ADMIN || ($pun_user['g_moderator'] == '1' && $pun_user['g_mod_ban_users'] == '1')): ?>					<li<?php if ($page == 'admin') echo ' class="isactive"'; ?>><a href="profile.php?section=admin&amp;id=<?php echo $id ?>"><?php echo $lang_profile['Section admin'] ?></a></li>
+<?php endif; ?>				</ul>
+			</div>
+		</div>
+	</div>
+<?php
+
+}
+
+
+//
+// Outputs markup to display a user's avatar
+//
+function generate_avatar_markup($user_id)
+{
+	global $pun_config;
+
+	$filetypes = array('jpg', 'gif', 'png');
+	$avatar_markup = '';
+
+	foreach ($filetypes as $cur_type)
+	{
+		$path = $pun_config['o_avatars_dir'].'/'.$user_id.'.'.$cur_type;
+
+		if (file_exists(PUN_ROOT.$path) && $img_size = getimagesize(PUN_ROOT.$path))
+		{
+			$avatar_markup = '<img src="'.pun_htmlspecialchars(get_base_url(true).'/'.$path.'?m='.filemtime(PUN_ROOT.$path)).'" '.$img_size[3].' alt="" />';
+			break;
+		}
+	}
+
+	return $avatar_markup;
+}
+
+
+//
+// Generate browser's title
+//
+function generate_page_title($page_title, $p = null)
+{
+	global $pun_config, $lang_common;
+
+	$page_title = array_reverse($page_title);
+
+	if (!is_null($p))
+		$page_title[0] .= ' ('.sprintf($lang_common['Page'], forum_number_format($p)).')';
+
+	$crumbs = implode($lang_common['Title separator'], $page_title);
+
+	return $crumbs;
+}
+
+
+//
+// Save array of tracked topics in cookie
+//
+function set_tracked_topics($tracked_topics)
+{
+	global $cookie_name, $cookie_path, $cookie_domain, $cookie_secure, $pun_config;
+
+	$cookie_data = '';
+	if (!empty($tracked_topics))
+	{
+		// Sort the arrays (latest read first)
+		arsort($tracked_topics['topics'], SORT_NUMERIC);
+		arsort($tracked_topics['forums'], SORT_NUMERIC);
+
+		// Homebrew serialization (to avoid having to run unserialize() on cookie data)
+		foreach ($tracked_topics['topics'] as $id => $timestamp)
+			$cookie_data .= 't'.$id.'='.$timestamp.';';
+		foreach ($tracked_topics['forums'] as $id => $timestamp)
+			$cookie_data .= 'f'.$id.'='.$timestamp.';';
+
+		// 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, FORUM_MAX_COOKIE_SIZE);
+			$cookie_data = substr($cookie_data, 0, strrpos($cookie_data, ';')).';';
+		}
+	}
+
+	forum_setcookie($cookie_name.'_track', $cookie_data, time() + $pun_config['o_timeout_visit']);
+	$_COOKIE[$cookie_name.'_track'] = $cookie_data; // Set it directly in $_COOKIE as well
+}
+
+
+//
+// Extract array of tracked topics from cookie
+//
+function get_tracked_topics()
+{
+	global $cookie_name;
+
+	$cookie_data = isset($_COOKIE[$cookie_name.'_track']) ? $_COOKIE[$cookie_name.'_track'] : false;
+	if (!$cookie_data)
+		return array('topics' => array(), 'forums' => array());
+
+	if (strlen($cookie_data) > FORUM_MAX_COOKIE_SIZE)
+		return array('topics' => array(), 'forums' => array());
+
+	// Unserialize data from cookie
+	$tracked_topics = array('topics' => array(), 'forums' => array());
+	$temp = explode(';', $cookie_data);
+	foreach ($temp as $t)
+	{
+		$type = substr($t, 0, 1) == 'f' ? 'forums' : 'topics';
+		$id = intval(substr($t, 1));
+		$timestamp = intval(substr($t, strpos($t, '=') + 1));
+		if ($id > 0 && $timestamp > 0)
+			$tracked_topics[$type][$id] = $timestamp;
+	}
+
+	return $tracked_topics;
+}
+
+
+//
+// Update posts, topics, last_post, last_post_id and last_poster for a forum
+//
+function update_forum($forum_id)
+{
+	global $db;
+
+	$result = $db->query('SELECT COUNT(id), SUM(num_replies) FROM '.$db->prefix.'topics WHERE forum_id='.$forum_id) or error('Unable to fetch forum topic count', __FILE__, __LINE__, $db->error());
+	list($num_topics, $num_posts) = $db->fetch_row($result);
+
+	$num_posts = $num_posts + $num_topics; // $num_posts is only the sum of all replies (we have to add the topic posts)
+
+	$result = $db->query('SELECT last_post, last_post_id, last_poster FROM '.$db->prefix.'topics WHERE forum_id='.$forum_id.' AND moved_to IS NULL ORDER BY last_post DESC LIMIT 1') or error('Unable to fetch last_post/last_post_id/last_poster', __FILE__, __LINE__, $db->error());
+	if ($db->num_rows($result)) // There are topics in the forum
+	{
+		list($last_post, $last_post_id, $last_poster) = $db->fetch_row($result);
+
+		$db->query('UPDATE '.$db->prefix.'forums SET num_topics='.$num_topics.', num_posts='.$num_posts.', last_post='.$last_post.', last_post_id='.$last_post_id.', last_poster=\''.$db->escape($last_poster).'\' WHERE id='.$forum_id) or error('Unable to update last_post/last_post_id/last_poster', __FILE__, __LINE__, $db->error());
+	}
+	else // There are no topics
+		$db->query('UPDATE '.$db->prefix.'forums SET num_topics='.$num_topics.', num_posts='.$num_posts.', last_post=NULL, last_post_id=NULL, last_poster=NULL WHERE id='.$forum_id) or error('Unable to update last_post/last_post_id/last_poster', __FILE__, __LINE__, $db->error());
+}
+
+
+//
+// Deletes any avatars owned by the specified user ID
+//
+function delete_avatar($user_id)
+{
+	global $pun_config;
+
+	$filetypes = array('jpg', 'gif', 'png');
+
+	// Delete user avatar
+	foreach ($filetypes as $cur_type)
+	{
+		if (file_exists(PUN_ROOT.$pun_config['o_avatars_dir'].'/'.$user_id.'.'.$cur_type))
+			@unlink(PUN_ROOT.$pun_config['o_avatars_dir'].'/'.$user_id.'.'.$cur_type);
+	}
+}
+
+
+//
+// Delete a topic and all of it's posts
+//
+function delete_topic($topic_id)
+{
+	global $db;
+
+	// Delete the topic and any redirect topics
+	$db->query('DELETE FROM '.$db->prefix.'topics WHERE id='.$topic_id.' OR moved_to='.$topic_id) or error('Unable to delete topic', __FILE__, __LINE__, $db->error());
+
+	// Create a list of the post IDs in this topic
+	$post_ids = '';
+	$result = $db->query('SELECT id FROM '.$db->prefix.'posts WHERE topic_id='.$topic_id) or error('Unable to fetch posts', __FILE__, __LINE__, $db->error());
+	while ($row = $db->fetch_row($result))
+		$post_ids .= ($post_ids != '') ? ','.$row[0] : $row[0];
+
+	// Make sure we have a list of post IDs
+	if ($post_ids != '')
+	{
+		strip_search_index($post_ids);
+
+		// Delete posts in topic
+		$db->query('DELETE FROM '.$db->prefix.'posts WHERE topic_id='.$topic_id) or error('Unable to delete posts', __FILE__, __LINE__, $db->error());
+	}
+
+	// Delete any subscriptions for this topic
+	$db->query('DELETE FROM '.$db->prefix.'topic_subscriptions WHERE topic_id='.$topic_id) or error('Unable to delete subscriptions', __FILE__, __LINE__, $db->error());
+}
+
+
+//
+// Delete a single post
+//
+function delete_post($post_id, $topic_id)
+{
+	global $db;
+
+	$result = $db->query('SELECT id, poster, posted FROM '.$db->prefix.'posts WHERE topic_id='.$topic_id.' ORDER BY id DESC LIMIT 2') or error('Unable to fetch post info', __FILE__, __LINE__, $db->error());
+	list($last_id, ,) = $db->fetch_row($result);
+	list($second_last_id, $second_poster, $second_posted) = $db->fetch_row($result);
+
+	// Delete the post
+	$db->query('DELETE FROM '.$db->prefix.'posts WHERE id='.$post_id) or error('Unable to delete post', __FILE__, __LINE__, $db->error());
+
+	strip_search_index($post_id);
+
+	// Count number of replies in the topic
+	$result = $db->query('SELECT COUNT(id) FROM '.$db->prefix.'posts WHERE topic_id='.$topic_id) or error('Unable to fetch post count for topic', __FILE__, __LINE__, $db->error());
+	$num_replies = $db->result($result, 0) - 1;
+
+	// If the message we deleted is the most recent in the topic (at the end of the topic)
+	if ($last_id == $post_id)
+	{
+		// If there is a $second_last_id there is more than 1 reply to the topic
+		if (!empty($second_last_id))
+			$db->query('UPDATE '.$db->prefix.'topics SET last_post='.$second_posted.', last_post_id='.$second_last_id.', last_poster=\''.$db->escape($second_poster).'\', num_replies='.$num_replies.' WHERE id='.$topic_id) or error('Unable to update topic', __FILE__, __LINE__, $db->error());
+		else
+			// We deleted the only reply, so now last_post/last_post_id/last_poster is posted/id/poster from the topic itself
+			$db->query('UPDATE '.$db->prefix.'topics SET last_post=posted, last_post_id=id, last_poster=poster, num_replies='.$num_replies.' WHERE id='.$topic_id) or error('Unable to update topic', __FILE__, __LINE__, $db->error());
+	}
+	else
+		// Otherwise we just decrement the reply counter
+		$db->query('UPDATE '.$db->prefix.'topics SET num_replies='.$num_replies.' WHERE id='.$topic_id) or error('Unable to update topic', __FILE__, __LINE__, $db->error());
+}
+
+
+//
+// Delete every .php file in the forum's cache directory
+//
+function forum_clear_cache()
+{
+	$d = dir(FORUM_CACHE_DIR);
+	while (($entry = $d->read()) !== false)
+	{
+		if (substr($entry, -4) == '.php')
+			@unlink(FORUM_CACHE_DIR.$entry);
+	}
+	$d->close();
+}
+
+
+//
+// Replace censored words in $text
+//
+function censor_words($text)
+{
+	global $db;
+	static $search_for, $replace_with;
+
+	// If not already built in a previous call, build an array of censor words and their replacement text
+	if (!isset($search_for))
+	{
+		if (file_exists(FORUM_CACHE_DIR.'cache_censoring.php'))
+			include FORUM_CACHE_DIR.'cache_censoring.php';
+
+		if (!defined('PUN_CENSOR_LOADED'))
+		{
+			if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+				require PUN_ROOT.'include/cache.php';
+
+			generate_censoring_cache();
+			require FORUM_CACHE_DIR.'cache_censoring.php';
+		}
+	}
+
+	if (!empty($search_for))
+		$text = substr(ucp_preg_replace($search_for, $replace_with, ' '.$text.' '), 1, -1);
+
+	return $text;
+}
+
+
+//
+// Determines the correct title for $user
+// $user must contain the elements 'username', 'title', 'posts', 'g_id' and 'g_user_title'
+//
+function get_title($user)
+{
+	global $db, $pun_config, $pun_bans, $lang_common;
+	static $ban_list, $pun_ranks;
+
+	// If not already built in a previous call, build an array of lowercase banned usernames
+	if (empty($ban_list))
+	{
+		$ban_list = array();
+
+		foreach ($pun_bans as $cur_ban)
+			$ban_list[] = strtolower($cur_ban['username']);
+	}
+
+	// If not already loaded in a previous call, load the cached ranks
+	if ($pun_config['o_ranks'] == '1' && !defined('PUN_RANKS_LOADED'))
+	{
+		if (file_exists(FORUM_CACHE_DIR.'cache_ranks.php'))
+			include FORUM_CACHE_DIR.'cache_ranks.php';
+
+		if (!defined('PUN_RANKS_LOADED'))
+		{
+			if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+				require PUN_ROOT.'include/cache.php';
+
+			generate_ranks_cache();
+			require FORUM_CACHE_DIR.'cache_ranks.php';
+		}
+	}
+
+	// If the user has a custom title
+	if ($user['title'] != '')
+		$user_title = pun_htmlspecialchars($user['title']);
+	// If the user is banned
+	else if (in_array(strtolower($user['username']), $ban_list))
+		$user_title = $lang_common['Banned'];
+	// If the user group has a default user title
+	else if ($user['g_user_title'] != '')
+		$user_title = pun_htmlspecialchars($user['g_user_title']);
+	// If the user is a guest
+	else if ($user['g_id'] == PUN_GUEST)
+		$user_title = $lang_common['Guest'];
+	else
+	{
+		// Are there any ranks?
+		if ($pun_config['o_ranks'] == '1' && !empty($pun_ranks))
+		{
+			foreach ($pun_ranks as $cur_rank)
+			{
+				if ($user['num_posts'] >= $cur_rank['min_posts'])
+					$user_title = pun_htmlspecialchars($cur_rank['rank']);
+			}
+		}
+
+		// If the user didn't "reach" any rank (or if ranks are disabled), we assign the default
+		if (!isset($user_title))
+			$user_title = $lang_common['Member'];
+	}
+
+	return $user_title;
+}
+
+
+//
+// Generate a string with numbered links (for multipage scripts)
+//
+function paginate($num_pages, $cur_page, $link)
+{
+	global $lang_common;
+
+	$pages = array();
+	$link_to_all = false;
+
+	// If $cur_page == -1, we link to all pages (used in viewforum.php)
+	if ($cur_page == -1)
+	{
+		$cur_page = 1;
+		$link_to_all = true;
+	}
+
+	if ($num_pages <= 1)
+		$pages = array('<strong class="item1">1</strong>');
+	else
+	{
+		// Add a previous page link
+		if ($num_pages > 1 && $cur_page > 1)
+			$pages[] = '<a rel="prev" '.(empty($pages) ? ' class="item1"' : '').' href="'.$link.'&amp;p='.($cur_page - 1).'">'.$lang_common['Previous'].'</a>';
+
+		if ($cur_page > 3)
+		{
+			$pages[] = '<a'.(empty($pages) ? ' class="item1"' : '').' href="'.$link.'&amp;p=1">1</a>';
+
+			if ($cur_page > 5)
+				$pages[] = '<span class="spacer">'.$lang_common['Spacer'].'</span>';
+		}
+
+		// Don't ask me how the following works. It just does, OK? :-)
+		for ($current = ($cur_page == 5) ? $cur_page - 3 : $cur_page - 2, $stop = ($cur_page + 4 == $num_pages) ? $cur_page + 4 : $cur_page + 3; $current < $stop; ++$current)
+		{
+			if ($current < 1 || $current > $num_pages)
+				continue;
+			else if ($current != $cur_page || $link_to_all)
+				$pages[] = '<a'.(empty($pages) ? ' class="item1"' : '').' href="'.$link.'&amp;p='.$current.'">'.forum_number_format($current).'</a>';
+			else
+				$pages[] = '<strong'.(empty($pages) ? ' class="item1"' : '').'>'.forum_number_format($current).'</strong>';
+		}
+
+		if ($cur_page <= ($num_pages-3))
+		{
+			if ($cur_page != ($num_pages-3) && $cur_page != ($num_pages-4))
+				$pages[] = '<span class="spacer">'.$lang_common['Spacer'].'</span>';
+
+			$pages[] = '<a'.(empty($pages) ? ' class="item1"' : '').' href="'.$link.'&amp;p='.$num_pages.'">'.forum_number_format($num_pages).'</a>';
+		}
+
+		// Add a next page link
+		if ($num_pages > 1 && !$link_to_all && $cur_page < $num_pages)
+			$pages[] = '<a rel="next" '.(empty($pages) ? ' class="item1"' : '').' href="'.$link.'&amp;p='.($cur_page +1).'">'.$lang_common['Next'].'</a>';
+	}
+
+	return implode(' ', $pages);
+}
+
+
+//
+// Display a message
+//
+function message($message, $no_back_link = false, $http_status = null)
+{
+	global $db, $lang_common, $pun_config, $pun_start, $tpl_main, $pun_user;
+
+	// Did we receive a custom header?
+	if(!is_null($http_status)) {
+		header('HTTP/1.1 ' . $http_status);
+	}
+
+	if (!defined('PUN_HEADER'))
+	{
+		$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_common['Info']);
+		define('PUN_ACTIVE_PAGE', 'index');
+		require PUN_ROOT.'header.php';
+	}
+
+?>
+
+<div id="msg" class="block">
+	<h2><span><?php echo $lang_common['Info'] ?></span></h2>
+	<div class="box">
+		<div class="inbox">
+			<p><?php echo $message ?></p>
+<?php if (!$no_back_link): ?>			<p><a href="javascript: history.go(-1)"><?php echo $lang_common['Go back'] ?></a></p>
+<?php endif; ?>		</div>
+	</div>
+</div>
+<?php
+
+	require PUN_ROOT.'footer.php';
+}
+
+
+//
+// Format a time string according to $time_format and time zones
+//
+function format_time($timestamp, $date_only = false, $date_format = null, $time_format = null, $time_only = false, $no_text = false)
+{
+	global $pun_config, $lang_common, $pun_user, $forum_date_formats, $forum_time_formats;
+
+	if ($timestamp == '')
+		return $lang_common['Never'];
+
+	$diff = ($pun_user['timezone'] + $pun_user['dst']) * 3600;
+	$timestamp += $diff;
+	$now = time();
+
+	if(is_null($date_format))
+		$date_format = $forum_date_formats[$pun_user['date_format']];
+
+	if(is_null($time_format))
+		$time_format = $forum_time_formats[$pun_user['time_format']];
+
+	$date = gmdate($date_format, $timestamp);
+	$today = gmdate($date_format, $now+$diff);
+	$yesterday = gmdate($date_format, $now+$diff-86400);
+
+	if(!$no_text)
+	{
+		if ($date == $today)
+			$date = $lang_common['Today'];
+		else if ($date == $yesterday)
+			$date = $lang_common['Yesterday'];
+	}
+
+	if ($date_only)
+		return $date;
+	else if ($time_only)
+		return gmdate($time_format, $timestamp);
+	else
+		return $date.' '.gmdate($time_format, $timestamp);
+}
+
+
+//
+// A wrapper for PHP's number_format function
+//
+function forum_number_format($number, $decimals = 0)
+{
+	global $lang_common;
+
+	return is_numeric($number) ? number_format($number, $decimals, $lang_common['lang_decimal_point'], $lang_common['lang_thousands_sep']) : $number;
+}
+
+
+//
+// Generate a random key of length $len
+//
+function random_key($len, $readable = false, $hash = false)
+{
+	$key = secure_random_bytes($len); 
+	if ($hash)  
+		$key = substr(bin2hex($key), 0, $len);  
+	else if ($readable)  
+		$key = substr(base64_encode($key), 0, $len);  
+
+	return $key;
+}
+
+
+//
+// Make sure that HTTP_REFERER matches base_url/script
+//
+function confirm_referrer($scripts, $error_msg = false)
+{
+	global $pun_config, $lang_common;
+
+	if (!is_array($scripts))
+		$scripts = array($scripts);
+
+	// There is no referrer
+	if (empty($_SERVER['HTTP_REFERER']))
+		message($error_msg ? $error_msg : $lang_common['Bad referrer']);
+
+	$referrer = parse_url(strtolower($_SERVER['HTTP_REFERER']));
+	// Remove www subdomain if it exists
+	if (strpos($referrer['host'], 'www.') === 0)
+		$referrer['host'] = substr($referrer['host'], 4);
+
+	$valid_paths = array();
+	foreach ($scripts as $script)
+	{
+		$valid = parse_url(strtolower(get_base_url().'/'.$script));
+		// Remove www subdomain if it exists
+		if (strpos($valid['host'], 'www.') === 0)
+			$valid['host'] = substr($valid['host'], 4);
+
+		$valid_host = $valid['host'];
+		$valid_paths[] = $valid['path'];
+	}
+
+	// Check the host and path match. Ignore the scheme, port, etc.
+	if ($referrer['host'] != $valid_host || !in_array($referrer['path'], $valid_paths, true))
+		message($error_msg ? $error_msg : $lang_common['Bad referrer']);
+}
+
+
+//
+// Generate a random password of length $len
+// Compatibility wrapper for random_key
+//
+function random_pass($len)
+{
+	return random_key($len, true);
+}
+
+
+//
+// Compute a hash of $str
+//
+function pun_hash($str)
+{
+	return sha1($str);
+}
+
+
+//
+// Try to determine the correct remote IP-address
+//
+function get_remote_address()
+{
+	$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.
+			$forwarded_for = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
+			$forwarded_for = trim($forwarded_for[0]);
+
+			if (@preg_match('%^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$%', $forwarded_for) || @preg_match('%^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$%', $forwarded_for))
+				$remote_addr = $forwarded_for;
+		}
+	}
+
+	return $remote_addr;
+}
+
+
+//
+// Calls htmlspecialchars with a few options already set
+//
+function pun_htmlspecialchars($str)
+{
+	return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
+}
+
+
+//
+// Calls htmlspecialchars_decode with a few options already set
+//
+function pun_htmlspecialchars_decode($str)
+{
+	if (function_exists('htmlspecialchars_decode'))
+		return htmlspecialchars_decode($str, ENT_QUOTES);
+
+	static $translations;
+	if (!isset($translations))
+	{
+		$translations = get_html_translation_table(HTML_SPECIALCHARS, ENT_QUOTES);
+		$translations['&#039;'] = '\''; // get_html_translation_table doesn't include &#039; which is what htmlspecialchars translates ' to, but apparently that is okay?! http://bugs.php.net/bug.php?id=25927
+		$translations = array_flip($translations);
+	}
+
+	return strtr($str, $translations);
+}
+
+
+//
+// A wrapper for utf8_strlen for compatibility
+//
+function pun_strlen($str)
+{
+	return utf8_strlen($str);
+}
+
+
+//
+// Convert \r\n and \r to \n
+//
+function pun_linebreaks($str)
+{
+	return str_replace("\r", "\n", str_replace("\r\n", "\n", $str));
+}
+
+
+//
+// A wrapper for utf8_trim for compatibility
+//
+function pun_trim($str, $charlist = false)
+{
+	return is_string($str) ? utf8_trim($str, $charlist) : '';
+}
+
+//
+// Checks if a string is in all uppercase
+//
+function is_all_uppercase($string)
+{
+	return utf8_strtoupper($string) == $string && utf8_strtolower($string) != $string;
+}
+
+
+//
+// Inserts $element into $input at $offset
+// $offset can be either a numerical offset to insert at (eg: 0 inserts at the beginning of the array)
+// or a string, which is the key that the new element should be inserted before
+// $key is optional: it's used when inserting a new key/value pair into an associative array
+//
+function array_insert(&$input, $offset, $element, $key = null)
+{
+	if (is_null($key))
+		$key = $offset;
+
+	// Determine the proper offset if we're using a string
+	if (!is_int($offset))
+		$offset = array_search($offset, array_keys($input), true);
+
+	// Out of bounds checks
+	if ($offset > count($input))
+		$offset = count($input);
+	else if ($offset < 0)
+		$offset = 0;
+
+	$input = array_merge(array_slice($input, 0, $offset), array($key => $element), array_slice($input, $offset));
+}
+
+
+//
+// Display a message when board is in maintenance mode
+//
+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('&#160; &#160; ', '&#160; ', ' &#160;');
+	$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';
+		$tpl_inc_dir = PUN_ROOT.'style/'.$pun_user['style'].'/';
+	}
+	else
+	{
+		$tpl_file = PUN_ROOT.'include/template/maintenance.tpl';
+		$tpl_inc_dir = PUN_ROOT.'include/user/';
+	}
+
+	$tpl_maint = file_get_contents($tpl_file);
+
+	// START SUBST - <pun_include "*">
+	preg_match_all('%<pun_include "([^/\\\\]*?)\.(php[45]?|inc|html?|txt)">%i', $tpl_maint, $pun_includes, PREG_SET_ORDER);
+
+	foreach ($pun_includes as $cur_include)
+	{
+		ob_start();
+
+		// Allow for overriding user includes, too.
+		if (file_exists($tpl_inc_dir.$cur_include[1].'.'.$cur_include[2]))
+			require $tpl_inc_dir.$cur_include[1].'.'.$cur_include[2];
+		else if (file_exists(PUN_ROOT.'include/user/'.$cur_include[1].'.'.$cur_include[2]))
+			require PUN_ROOT.'include/user/'.$cur_include[1].'.'.$cur_include[2];
+		else
+			error(sprintf($lang_common['Pun include error'], htmlspecialchars($cur_include[0]), basename($tpl_file)));
+
+		$tpl_temp = ob_get_contents();
+		$tpl_maint = str_replace($cur_include[0], $tpl_temp, $tpl_maint);
+		ob_end_clean();
+	}
+	// END SUBST - <pun_include "*">
+
+
+	// START SUBST - <pun_language>
+	$tpl_maint = str_replace('<pun_language>', $lang_common['lang_identifier'], $tpl_maint);
+	// END SUBST - <pun_language>
+
+
+	// START SUBST - <pun_content_direction>
+	$tpl_maint = str_replace('<pun_content_direction>', $lang_common['lang_direction'], $tpl_maint);
+	// END SUBST - <pun_content_direction>
+
+
+	// START SUBST - <pun_head>
+	ob_start();
+
+	$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_common['Maintenance']);
+
+?>
+<title><?php echo generate_page_title($page_title) ?></title>
+<link rel="stylesheet" type="text/css" href="style/<?php echo $pun_user['style'].'.css' ?>" />
+<?php
+
+	$tpl_temp = trim(ob_get_contents());
+	$tpl_maint = str_replace('<pun_head>', $tpl_temp, $tpl_maint);
+	ob_end_clean();
+	// END SUBST - <pun_head>
+
+
+	// START SUBST - <pun_maint_main>
+	ob_start();
+
+?>
+<div class="block">
+	<h2><?php echo $lang_common['Maintenance'] ?></h2>
+	<div class="box">
+		<div class="inbox">
+			<p><?php echo $message ?></p>
+		</div>
+	</div>
+</div>
+<?php
+
+	$tpl_temp = trim(ob_get_contents());
+	$tpl_maint = str_replace('<pun_maint_main>', $tpl_temp, $tpl_maint);
+	ob_end_clean();
+	// END SUBST - <pun_maint_main>
+
+
+	// End the transaction
+	$db->end_transaction();
+
+
+	// Close the db connection (and free up any result data)
+	$db->close();
+
+	exit($tpl_maint);
+}
+
+
+//
+// Display $message and redirect user to $destination_url
+//
+function redirect($destination_url, $message)
+{
+	global $db, $pun_config, $lang_common, $pun_user;
+
+	// Prefix with base_url (unless there's already a valid URI)
+	if (strpos($destination_url, 'http://') !== 0 && strpos($destination_url, 'https://') !== 0 && strpos($destination_url, '/') !== 0)
+		$destination_url = get_base_url(true).'/'.$destination_url;
+
+	// Do a little spring cleaning
+	$destination_url = preg_replace('%([\r\n])|(\%0[ad])|(;\s*data\s*:)%i', '', $destination_url);
+
+	// If the delay is 0 seconds, we might as well skip the redirect all together
+	if ($pun_config['o_redirect_delay'] == '0')
+		header('Location: '.str_replace('&amp;', '&', $destination_url));
+
+	// 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');
+
+	if (file_exists(PUN_ROOT.'style/'.$pun_user['style'].'/redirect.tpl'))
+	{
+		$tpl_file = PUN_ROOT.'style/'.$pun_user['style'].'/redirect.tpl';
+		$tpl_inc_dir = PUN_ROOT.'style/'.$pun_user['style'].'/';
+	}
+	else
+	{
+		$tpl_file = PUN_ROOT.'include/template/redirect.tpl';
+		$tpl_inc_dir = PUN_ROOT.'include/user/';
+	}
+
+	$tpl_redir = file_get_contents($tpl_file);
+
+	// START SUBST - <pun_include "*">
+	preg_match_all('%<pun_include "([^/\\\\]*?)\.(php[45]?|inc|html?|txt)">%i', $tpl_redir, $pun_includes, PREG_SET_ORDER);
+
+	foreach ($pun_includes as $cur_include)
+	{
+		ob_start();
+
+		// Allow for overriding user includes, too.
+		if (file_exists($tpl_inc_dir.$cur_include[1].'.'.$cur_include[2]))
+			require $tpl_inc_dir.$cur_include[1].'.'.$cur_include[2];
+		else if (file_exists(PUN_ROOT.'include/user/'.$cur_include[1].'.'.$cur_include[2]))
+			require PUN_ROOT.'include/user/'.$cur_include[1].'.'.$cur_include[2];
+		else
+			error(sprintf($lang_common['Pun include error'], htmlspecialchars($cur_include[0]), basename($tpl_file)));
+
+		$tpl_temp = ob_get_contents();
+		$tpl_redir = str_replace($cur_include[0], $tpl_temp, $tpl_redir);
+		ob_end_clean();
+	}
+	// END SUBST - <pun_include "*">
+
+
+	// START SUBST - <pun_language>
+	$tpl_redir = str_replace('<pun_language>', $lang_common['lang_identifier'], $tpl_redir);
+	// END SUBST - <pun_language>
+
+
+	// START SUBST - <pun_content_direction>
+	$tpl_redir = str_replace('<pun_content_direction>', $lang_common['lang_direction'], $tpl_redir);
+	// END SUBST - <pun_content_direction>
+
+
+	// START SUBST - <pun_head>
+	ob_start();
+
+	$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_common['Redirecting']);
+
+?>
+<meta http-equiv="refresh" content="<?php echo $pun_config['o_redirect_delay'] ?>;URL=<?php echo str_replace(array('<', '>', '"'), array('&lt;', '&gt;', '&quot;'), $destination_url) ?>" />
+<title><?php echo generate_page_title($page_title) ?></title>
+<link rel="stylesheet" type="text/css" href="style/<?php echo $pun_user['style'].'.css' ?>" />
+<?php
+
+	$tpl_temp = trim(ob_get_contents());
+	$tpl_redir = str_replace('<pun_head>', $tpl_temp, $tpl_redir);
+	ob_end_clean();
+	// END SUBST - <pun_head>
+
+
+	// START SUBST - <pun_redir_main>
+	ob_start();
+
+?>
+<div class="block">
+	<h2><?php echo $lang_common['Redirecting'] ?></h2>
+	<div class="box">
+		<div class="inbox">
+			<p><?php echo $message.'<br /><br /><a href="'.$destination_url.'">'.$lang_common['Click redirect'].'</a>' ?></p>
+		</div>
+	</div>
+</div>
+<?php
+
+	$tpl_temp = trim(ob_get_contents());
+	$tpl_redir = str_replace('<pun_redir_main>', $tpl_temp, $tpl_redir);
+	ob_end_clean();
+	// END SUBST - <pun_redir_main>
+
+
+	// START SUBST - <pun_footer>
+	ob_start();
+
+	// End the transaction
+	$db->end_transaction();
+
+	// Display executed queries (if enabled)
+	if (defined('PUN_SHOW_QUERIES'))
+		display_saved_queries();
+
+	$tpl_temp = trim(ob_get_contents());
+	$tpl_redir = str_replace('<pun_footer>', $tpl_temp, $tpl_redir);
+	ob_end_clean();
+	// END SUBST - <pun_footer>
+
+
+	// Close the db connection (and free up any result data)
+	$db->close();
+
+	exit($tpl_redir);
+}
+
+
+//
+// Display a simple error message
+//
+function error($message, $file = null, $line = null, $db_error = false)
+{
+	global $pun_config, $lang_common;
+
+	// Set some default settings if the script failed before $pun_config could be populated
+	if (empty($pun_config))
+	{
+		$pun_config = array(
+			'o_board_title'	=> 'FluxBB',
+			'o_gzip'		=> '0'
+		);
+	}
+
+	// Set some default translations if the script failed before $lang_common could be populated
+	if (empty($lang_common))
+	{
+		$lang_common = array(
+			'Title separator'	=> ' / ',
+			'Page'				=> 'Page %s'
+		);
+	}
+
+	// Empty all output buffers and stop buffering
+	while (@ob_end_clean());
+
+	// "Restart" output buffering if we are using ob_gzhandler (since the gzip header is already sent)
+	if ($pun_config['o_gzip'] && extension_loaded('zlib'))
+		ob_start('ob_gzhandler');
+
+	// 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');
+
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<?php $page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), 'Error') ?>
+<title><?php echo generate_page_title($page_title) ?></title>
+<style type="text/css">
+<!--
+BODY {MARGIN: 10% 20% auto 20%; font: 10px Verdana, Arial, Helvetica, sans-serif}
+#errorbox {BORDER: 1px solid #B84623}
+H2 {MARGIN: 0; COLOR: #FFFFFF; BACKGROUND-COLOR: #B84623; FONT-SIZE: 1.1em; PADDING: 5px 4px}
+#errorbox DIV {PADDING: 6px 5px; BACKGROUND-COLOR: #F1F1F1}
+-->
+</style>
+</head>
+<body>
+
+<div id="errorbox">
+	<h2>An error was encountered</h2>
+	<div>
+<?php
+
+	if (defined('PUN_DEBUG') && !is_null($file) && !is_null($line))
+	{
+		echo "\t\t".'<strong>File:</strong> '.$file.'<br />'."\n\t\t".'<strong>Line:</strong> '.$line.'<br /><br />'."\n\t\t".'<strong>FluxBB reported</strong>: '.$message."\n";
+
+		if ($db_error)
+		{
+			echo "\t\t".'<br /><br /><strong>Database reported:</strong> '.pun_htmlspecialchars($db_error['error_msg']).(($db_error['error_no']) ? ' (Errno: '.$db_error['error_no'].')' : '')."\n";
+
+			if ($db_error['error_sql'] != '')
+				echo "\t\t".'<br /><br /><strong>Failed query:</strong> '.pun_htmlspecialchars($db_error['error_sql'])."\n";
+		}
+	}
+	else
+		echo "\t\t".'Error: <strong>'.$message.'.</strong>'."\n";
+
+?>
+	</div>
+</div>
+
+</body>
+</html>
+<?php
+
+	// If a database connection was established (before this error) we close it
+	if ($db_error)
+		$GLOBALS['db']->close();
+
+	exit;
+}
+
+
+//
+// Unset any variables instantiated as a result of register_globals being enabled
+//
+function forum_unregister_globals()
+{
+	$register_globals = ini_get('register_globals');
+	if ($register_globals === '' || $register_globals === '0' || strtolower($register_globals) === 'off')
+		return;
+
+	// Prevent script.php?GLOBALS[foo]=bar
+	if (isset($_REQUEST['GLOBALS']) || isset($_FILES['GLOBALS']))
+		exit('I\'ll have a steak sandwich and... a steak sandwich.');
+
+	// Variables that shouldn't be unset
+	$no_unset = array('GLOBALS', '_GET', '_POST', '_COOKIE', '_REQUEST', '_SERVER', '_ENV', '_FILES');
+
+	// Remove elements in $GLOBALS that are present in any of the superglobals
+	$input = array_merge($_GET, $_POST, $_COOKIE, $_SERVER, $_ENV, $_FILES, isset($_SESSION) && is_array($_SESSION) ? $_SESSION : array());
+	foreach ($input as $k => $v)
+	{
+		if (!in_array($k, $no_unset) && isset($GLOBALS[$k]))
+		{
+			unset($GLOBALS[$k]);
+			unset($GLOBALS[$k]); // Double unset to circumvent the zend_hash_del_key_or_index hole in PHP <4.4.3 and <5.1.4
+		}
+	}
+}
+
+
+//
+// Removes any "bad" characters (characters which mess with the display of a page, are invisible, etc) from user input
+//
+function forum_remove_bad_characters()
+{
+	$_GET = remove_bad_characters($_GET);
+	$_POST = remove_bad_characters($_POST);
+	$_COOKIE = remove_bad_characters($_COOKIE);
+	$_REQUEST = remove_bad_characters($_REQUEST);
+}
+
+//
+// Removes any "bad" characters (characters which mess with the display of a page, are invisible, etc) from the given string
+// See: http://kb.mozillazine.org/Network.IDN.blacklist_chars
+//
+function remove_bad_characters($array)
+{
+	static $bad_utf8_chars;
+
+	if (!isset($bad_utf8_chars))
+	{
+		$bad_utf8_chars = array(
+			"\xcc\xb7"		=> '',		// COMBINING SHORT SOLIDUS OVERLAY		0337	*
+			"\xcc\xb8"		=> '',		// COMBINING LONG SOLIDUS OVERLAY		0338	*
+			"\xe1\x85\x9F"	=> '',		// HANGUL CHOSEONG FILLER				115F	*
+			"\xe1\x85\xA0"	=> '',		// HANGUL JUNGSEONG FILLER				1160	*
+			"\xe2\x80\x8b"	=> '',		// ZERO WIDTH SPACE						200B	*
+			"\xe2\x80\x8c"	=> '',		// ZERO WIDTH NON-JOINER				200C
+			"\xe2\x80\x8d"	=> '',		// ZERO WIDTH JOINER					200D
+			"\xe2\x80\x8e"	=> '',		// LEFT-TO-RIGHT MARK					200E
+			"\xe2\x80\x8f"	=> '',		// RIGHT-TO-LEFT MARK					200F
+			"\xe2\x80\xaa"	=> '',		// LEFT-TO-RIGHT EMBEDDING				202A
+			"\xe2\x80\xab"	=> '',		// RIGHT-TO-LEFT EMBEDDING				202B
+			"\xe2\x80\xac"	=> '', 		// POP DIRECTIONAL FORMATTING			202C
+			"\xe2\x80\xad"	=> '',		// LEFT-TO-RIGHT OVERRIDE				202D
+			"\xe2\x80\xae"	=> '',		// RIGHT-TO-LEFT OVERRIDE				202E
+			"\xe2\x80\xaf"	=> '',		// NARROW NO-BREAK SPACE				202F	*
+			"\xe2\x81\x9f"	=> '',		// MEDIUM MATHEMATICAL SPACE			205F	*
+			"\xe2\x81\xa0"	=> '',		// WORD JOINER							2060
+			"\xe3\x85\xa4"	=> '',		// HANGUL FILLER						3164	*
+			"\xef\xbb\xbf"	=> '',		// ZERO WIDTH NO-BREAK SPACE			FEFF
+			"\xef\xbe\xa0"	=> '',		// HALFWIDTH HANGUL FILLER				FFA0	*
+			"\xef\xbf\xb9"	=> '',		// INTERLINEAR ANNOTATION ANCHOR		FFF9	*
+			"\xef\xbf\xba"	=> '',		// INTERLINEAR ANNOTATION SEPARATOR		FFFA	*
+			"\xef\xbf\xbb"	=> '',		// INTERLINEAR ANNOTATION TERMINATOR	FFFB	*
+			"\xef\xbf\xbc"	=> '',		// OBJECT REPLACEMENT CHARACTER			FFFC	*
+			"\xef\xbf\xbd"	=> '',		// REPLACEMENT CHARACTER				FFFD	*
+			"\xe2\x80\x80"	=> ' ',		// EN QUAD								2000	*
+			"\xe2\x80\x81"	=> ' ',		// EM QUAD								2001	*
+			"\xe2\x80\x82"	=> ' ',		// EN SPACE								2002	*
+			"\xe2\x80\x83"	=> ' ',		// EM SPACE								2003	*
+			"\xe2\x80\x84"	=> ' ',		// THREE-PER-EM SPACE					2004	*
+			"\xe2\x80\x85"	=> ' ',		// FOUR-PER-EM SPACE					2005	*
+			"\xe2\x80\x86"	=> ' ',		// SIX-PER-EM SPACE						2006	*
+			"\xe2\x80\x87"	=> ' ',		// FIGURE SPACE							2007	*
+			"\xe2\x80\x88"	=> ' ',		// PUNCTUATION SPACE					2008	*
+			"\xe2\x80\x89"	=> ' ',		// THIN SPACE							2009	*
+			"\xe2\x80\x8a"	=> ' ',		// HAIR SPACE							200A	*
+			"\xE3\x80\x80"	=> ' ',		// IDEOGRAPHIC SPACE					3000	*
+		);
+	}
+
+	if (is_array($array))
+		return array_map('remove_bad_characters', $array);
+
+	// Strip out any invalid characters
+	$array = utf8_bad_strip($array);
+
+	// Remove control characters
+	$array = preg_replace('%[\x00-\x08\x0b-\x0c\x0e-\x1f]%', '', $array);
+
+	// Replace some "bad" characters
+	$array = str_replace(array_keys($bad_utf8_chars), array_values($bad_utf8_chars), $array);
+
+	return $array;
+}
+
+
+//
+// Converts the file size in bytes to a human readable file size
+//
+function file_size($size)
+{
+	global $lang_common;
+
+	$units = array('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB');
+
+	for ($i = 0; $size > 1024; $i++)
+		$size /= 1024;
+
+	return sprintf($lang_common['Size unit '.$units[$i]], round($size, 2));
+}
+
+
+//
+// Fetch a list of available styles
+//
+function forum_list_styles()
+{
+	$styles = array();
+
+	$d = dir(PUN_ROOT.'style');
+	while (($entry = $d->read()) !== false)
+	{
+		if ($entry{0} == '.')
+			continue;
+
+		if (substr($entry, -4) == '.css')
+			$styles[] = substr($entry, 0, -4);
+	}
+	$d->close();
+
+	natcasesort($styles);
+
+	return $styles;
+}
+
+
+//
+// Fetch a list of available language packs
+//
+function forum_list_langs()
+{
+	$languages = array();
+
+	$d = dir(PUN_ROOT.'lang');
+	while (($entry = $d->read()) !== false)
+	{
+		if ($entry{0} == '.')
+			continue;
+
+		if (is_dir(PUN_ROOT.'lang/'.$entry) && file_exists(PUN_ROOT.'lang/'.$entry.'/common.php'))
+			$languages[] = $entry;
+	}
+	$d->close();
+
+	natcasesort($languages);
+
+	return $languages;
+}
+
+
+//
+// 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)
+{
+	$plugins = array();
+
+	$d = dir(PUN_ROOT.'plugins');
+	while (($entry = $d->read()) !== false)
+	{
+		if ($entry{0} == '.')
+			continue;
+
+		$prefix = substr($entry, 0, strpos($entry, '_'));
+		$suffix = substr($entry, strlen($entry) - 4);
+
+		if ($suffix == '.php' && ((!$is_admin && $prefix == 'AMP') || ($is_admin && ($prefix == 'AP' || $prefix == 'AMP'))))
+			$plugins[$entry] = substr($entry, strpos($entry, '_') + 1, -4);
+	}
+	$d->close();
+
+	natcasesort($plugins);
+
+	return $plugins;
+}
+
+
+//
+// Split text into chunks ($inside contains all text inside $start and $end, and $outside contains all text outside)
+//
+function split_text($text, $start, $end, $retab = true)
+{
+	global $pun_config, $lang_common;
+
+	$result = array(0 => array(), 1 => array()); // 0 = inside, 1 = outside
+
+	// split the text into parts
+	$parts = preg_split('%'.preg_quote($start, '%').'(.*)'.preg_quote($end, '%').'%Us', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
+	$num_parts = count($parts);
+
+	// preg_split results in outside parts having even indices, inside parts having odd
+	for ($i = 0;$i < $num_parts;$i++)
+		$result[1 - ($i % 2)][] = $parts[$i];
+
+	if ($pun_config['o_indent_num_spaces'] != 8 && $retab)
+	{
+		$spaces = str_repeat(' ', $pun_config['o_indent_num_spaces']);
+		$result[1] = str_replace("\t", $spaces, $result[1]);
+	}
+
+	return $result;
+}
+
+
+//
+// Extract blocks from a text with a starting and ending string
+// This function always matches the most outer block so nesting is possible
+//
+function extract_blocks($text, $start, $end, $retab = true)
+{
+	global $pun_config;
+
+	$code = array();
+	$start_len = strlen($start);
+	$end_len = strlen($end);
+	$regex = '%(?:'.preg_quote($start, '%').'|'.preg_quote($end, '%').')%';
+	$matches = array();
+
+	if (preg_match_all($regex, $text, $matches))
+	{
+		$counter = $offset = 0;
+		$start_pos = $end_pos = false;
+
+		foreach ($matches[0] as $match)
+		{
+			if ($match == $start)
+			{
+				if ($counter == 0)
+					$start_pos = strpos($text, $start);
+				$counter++;
+			}
+			elseif ($match == $end)
+			{
+				$counter--;
+				if ($counter == 0)
+					$end_pos = strpos($text, $end, $offset + 1);
+				$offset = strpos($text, $end, $offset + 1);
+			}
+
+			if ($start_pos !== false && $end_pos !== false)
+			{
+				$code[] = substr($text, $start_pos + $start_len,
+					$end_pos - $start_pos - $start_len);
+				$text = substr_replace($text, "\1", $start_pos,
+					$end_pos - $start_pos + $end_len);
+				$start_pos = $end_pos = false;
+				$offset = 0;
+			}
+		}
+	}
+
+	if ($pun_config['o_indent_num_spaces'] != 8 && $retab)
+	{
+		$spaces = str_repeat(' ', $pun_config['o_indent_num_spaces']);
+		$text = str_replace("\t", $spaces, $text);
+	}
+
+	return array($code, $text);
+}
+
+
+//
+// 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;
+}
+
+//
+// Replace four-byte characters with a question mark
+//
+// As MySQL cannot properly handle four-byte characters with the default utf-8
+// charset up until version 5.5.3 (where a special charset has to be used), they
+// need to be replaced, by question marks in this case.
+//
+function strip_bad_multibyte_chars($str)
+{
+	$result = '';
+	$length = strlen($str);
+
+	for ($i = 0; $i < $length; $i++)
+	{
+		// Replace four-byte characters (11110www 10zzzzzz 10yyyyyy 10xxxxxx)
+		$ord = ord($str[$i]);
+		if ($ord >= 240 && $ord <= 244)
+		{
+			$result .= '?';
+			$i += 3;
+		}
+		else
+		{
+			$result .= $str[$i];
+		}
+	}
+
+	return $result;
+}
+
+//
+// Check whether a file/folder is writable.
+//
+// This function also works on Windows Server where ACLs seem to be ignored.
+//
+function forum_is_writable($path)
+{
+	if (is_dir($path))
+	{
+		$path = rtrim($path, '/').'/';
+		return forum_is_writable($path.uniqid(mt_rand()).'.tmp');
+	}
+
+	// Check temporary file for read/write capabilities
+	$rm = file_exists($path);
+	$f = @fopen($path, 'a');
+
+	if ($f === false)
+		return false;
+
+	fclose($f);
+
+	if (!$rm)
+		@unlink($path);
+
+	return true;
+}
+
+
+// DEBUG FUNCTIONS BELOW
+
+//
+// Display executed queries (if enabled)
+//
+function display_saved_queries()
+{
+	global $db, $lang_common;
+
+	// Get the queries so that we can print them out
+	$saved_queries = $db->get_saved_queries();
+
+?>
+
+<div id="debug" class="blocktable">
+	<h2><span><?php echo $lang_common['Debug table'] ?></span></h2>
+	<div class="box">
+		<div class="inbox">
+			<table cellspacing="0">
+			<thead>
+				<tr>
+					<th class="tcl" scope="col"><?php echo $lang_common['Query times'] ?></th>
+					<th class="tcr" scope="col"><?php echo $lang_common['Query'] ?></th>
+				</tr>
+			</thead>
+			<tbody>
+<?php
+
+	$query_time_total = 0.0;
+	foreach ($saved_queries as $cur_query)
+	{
+		$query_time_total += $cur_query[1];
+
+?>
+				<tr>
+					<td class="tcl"><?php echo ($cur_query[1] != 0) ? $cur_query[1] : '&#160;' ?></td>
+					<td class="tcr"><?php echo pun_htmlspecialchars($cur_query[0]) ?></td>
+				</tr>
+<?php
+
+	}
+
+?>
+				<tr>
+					<td class="tcl" colspan="2"><?php printf($lang_common['Total query time'], $query_time_total.' s') ?></td>
+				</tr>
+			</tbody>
+			</table>
+		</div>
+	</div>
+</div>
+<?php
+
+}
+
+
+//
+// Dump contents of variable(s)
+//
+function dump()
+{
+	echo '<pre>';
+
+	$num_args = func_num_args();
+
+	for ($i = 0; $i < $num_args; ++$i)
+	{
+		print_r(func_get_arg($i));
+		echo "\n\n";
+	}
+
+	echo '</pre>';
+	exit;
+}
diff --git a/include/index.html b/include/index.html
new file mode 100644
index 0000000..89337b2
--- /dev/null
+++ b/include/index.html
@@ -0,0 +1 @@
+<html><head><title>.</title></head><body>.</body></html>
diff --git a/include/parser.php b/include/parser.php
new file mode 100644
index 0000000..238932d
--- /dev/null
+++ b/include/parser.php
@@ -0,0 +1,943 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// Make sure no one attempts to run this script "directly"
+if (!defined('PUN'))
+	exit;
+
+// Global variables
+/* regular expression to match nested BBCode LIST tags
+'%
+\[list                # match opening bracket and tag name of outermost LIST tag
+(?:=([1a*]))?+        # optional attribute capture in group 1
+\]                    # closing bracket of outermost opening LIST tag
+(                     # capture contents of LIST tag in group 2
+  (?:                 # non capture group for either contents or whole nested LIST
+	[^\[]*+           # unroll the loop! consume everything up to next [ (normal *)
+	(?:               # (See "Mastering Regular Expressions" chapter 6 for details)
+	  (?!             # negative lookahead ensures we are NOT on [LIST*] or [/LIST]
+		\[list        # opening LIST tag
+		(?:=[1a*])?+  # with optional attribute
+		\]            # closing bracket of opening LIST tag
+		|             # or...
+		\[/list\]     # a closing LIST tag
+	  )               # end negative lookahead assertion (we are not on a LIST tag)
+	  \[              # match the [ which is NOT the start of LIST tag (special)
+	  [^\[]*+         # consume everything up to next [ (normal *)
+	)*+               # finish up "unrolling the loop" technique (special (normal*))*
+  |                   # or...
+	(?R)              # recursively match a whole nested LIST element
+  )*                  # as many times as necessary until deepest nested LIST tag grabbed
+)                     # end capturing contents of LIST tag into group 2
+\[/list\]             # match outermost closing LIST tag
+%iex' */
+$re_list = '%\[list(?:=([1a*]))?+\]((?:[^\[]*+(?:(?!\[list(?:=[1a*])?+\]|\[/list\])\[[^\[]*+)*+|(?R))*)\[/list\]%ie';
+
+// Here you can add additional smilies if you like (please note that you must escape single quote and backslash)
+$smilies = array(
+	':)' => 'smile.png',
+	'=)' => 'smile.png',
+	':|' => 'neutral.png',
+	'=|' => 'neutral.png',
+	':(' => 'sad.png',
+	'=(' => 'sad.png',
+	':D' => 'big_smile.png',
+	'=D' => 'big_smile.png',
+	':o' => 'yikes.png',
+	':O' => 'yikes.png',
+	';)' => 'wink.png',
+	':/' => 'hmm.png',
+	':P' => 'tongue.png',
+	':p' => 'tongue.png',
+	':lol:' => 'lol.png',
+	':mad:' => 'mad.png',
+	':rolleyes:' => 'roll.png',
+	':cool:' => 'cool.png');
+
+//
+// Make sure all BBCodes are lower case and do a little cleanup
+//
+function preparse_bbcode($text, &$errors, $is_signature = false)
+{
+	global $pun_config, $lang_common, $lang_post, $re_list;
+
+	if ($is_signature)
+	{
+		global $lang_profile;
+
+		if (preg_match('%\[/?(?:quote|code|list|h)\b[^\]]*\]%i', $text))
+			$errors[] = $lang_profile['Signature quote/code/list/h'];
+	}
+
+	// If the message contains a code tag we have to split it up (text within [code][/code] shouldn't be touched)
+	if (strpos($text, '[code]') !== false && strpos($text, '[/code]') !== false)
+		list($inside, $text) = extract_blocks($text, '[code]', '[/code]');
+
+	// Tidy up lists
+	$temp = preg_replace($re_list, 'preparse_list_tag(\'$2\', \'$1\')', $text);
+
+	// If the regex failed
+	if (is_null($temp))
+		$errors[] = $lang_common['BBCode list size error'];
+	else
+		$text = str_replace('*'."\0".']', '*]', $temp);
+
+	if ($pun_config['o_make_links'] == '1')
+		$text = do_clickable($text);
+
+	// If we split up the message before we have to concatenate it together again (code tags)
+	if (isset($inside))
+	{
+		$outside = explode("\1", $text);
+		$text = '';
+
+		$num_tokens = count($outside);
+		for ($i = 0; $i < $num_tokens; ++$i)
+		{
+			$text .= $outside[$i];
+			if (isset($inside[$i]))
+				$text .= '[code]'.$inside[$i].'[/code]';
+		}
+
+		unset($inside);
+	}
+
+	$temp_text = false;
+	if (empty($errors))
+		$temp_text = preparse_tags($text, $errors, $is_signature);
+
+	if ($temp_text !== false)
+		$text = $temp_text;
+
+	// Remove empty tags
+	while (($new_text = strip_empty_bbcode($text)) !== false)
+	{
+		if ($new_text != $text)
+		{
+			$text = $new_text;
+			if ($new_text == '')
+			{
+				$errors[] = $lang_post['Empty after strip'];
+				break;
+			}
+		}
+		else
+			break;
+	}
+
+	return pun_trim($text);
+}
+
+
+//
+// Strip empty bbcode tags from some text
+//
+function strip_empty_bbcode($text)
+{
+	// If the message contains a code tag we have to split it up (empty tags within [code][/code] are fine)
+	if (strpos($text, '[code]') !== false && strpos($text, '[/code]') !== false)
+		list($inside, $text) = extract_blocks($text, '[code]', '[/code]');
+
+	// Remove empty tags
+	while (!is_null($new_text = preg_replace('%\[(b|u|s|ins|del|em|i|h|colou?r|quote|img|url|email|list|topic|post|forum|user)(?:\=[^\]]*)?\]\s*\[/\1\]%', '', $text)))
+	{
+		if ($new_text != $text)
+			$text = $new_text;
+		else
+			break;
+	}
+
+	// If we split up the message before we have to concatenate it together again (code tags)
+	if (isset($inside))
+	{
+		$parts = explode("\1", $text);
+		$text = '';
+		foreach ($parts as $i => $part)
+		{
+			$text .= $part;
+			if (isset($inside[$i]))
+				$text .= '[code]'.$inside[$i].'[/code]';
+		}
+	}
+
+	// Remove empty code tags
+	while (!is_null($new_text = preg_replace('%\[(code)\]\s*\[/\1\]%', '', $text)))
+	{
+		if ($new_text != $text)
+			$text = $new_text;
+		else
+			break;
+	}
+
+	return $text;
+}
+
+
+//
+// Check the structure of bbcode tags and fix simple mistakes where possible
+//
+function preparse_tags($text, &$errors, $is_signature = false)
+{
+	global $lang_common, $pun_config;
+
+	// Start off by making some arrays of bbcode tags and what we need to do with each one
+
+	// List of all the tags
+	$tags = array('quote', 'code', 'b', 'i', 'u', 's', 'ins', 'del', 'em', 'color', 'colour', 'url', 'email', 'img', 'list', '*', 'h', 'topic', 'post', 'forum', 'user');
+	// List of tags that we need to check are open (You could not put b,i,u in here then illegal nesting like [b][i][/b][/i] would be allowed)
+	$tags_opened = $tags;
+	// and tags we need to check are closed (the same as above, added it just in case)
+	$tags_closed = $tags;
+	// Tags we can nest and the depth they can be nested to
+	$tags_nested = array('quote' => $pun_config['o_quote_depth'], 'list' => 5, '*' => 5);
+	// Tags to ignore the contents of completely (just code)
+	$tags_ignore = array('code');
+	// Block tags, block tags can only go within another block tag, they cannot be in a normal tag
+	$tags_block = array('quote', 'code', 'list', 'h', '*');
+	// Inline tags, we do not allow new lines in these
+	$tags_inline = array('b', 'i', 'u', 's', 'ins', 'del', 'em', 'color', 'colour', 'h', 'topic', 'post', 'forum', 'user');
+	// Tags we trim interior space
+	$tags_trim = array('img');
+	// Tags we remove quotes from the argument
+	$tags_quotes = array('url', 'email', 'img', 'topic', 'post', 'forum', 'user');
+	// Tags we limit bbcode in
+	$tags_limit_bbcode = array(
+		'*' 	=> array('b', 'i', 'u', 's', 'ins', 'del', 'em', 'color', 'colour', 'url', 'email', 'list', 'img', 'code', 'topic', 'post', 'forum', 'user'),
+		'list' 	=> array('*'),
+		'url' 	=> array('img'),
+		'email' => array('img'),
+		'topic' => array('img'),
+		'post'  => array('img'),
+		'forum' => array('img'),
+		'user'  => array('img'),
+		'img' 	=> array(),
+		'h'		=> array('b', 'i', 'u', 's', 'ins', 'del', 'em', 'color', 'colour', 'url', 'email', 'topic', 'post', 'forum', 'user'),
+	);
+	// Tags we can automatically fix bad nesting
+	$tags_fix = array('quote', 'b', 'i', 'u', 's', 'ins', 'del', 'em', 'color', 'colour', 'url', 'email', 'h', 'topic', 'post', 'forum', 'user');
+
+	$split_text = preg_split('%(\[[\*a-zA-Z0-9-/]*?(?:=.*?)?\])%', $text, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
+
+	$open_tags = array('fluxbb-bbcode');
+	$open_args = array('');
+	$opened_tag = 0;
+	$new_text = '';
+	$current_ignore = '';
+	$current_nest = '';
+	$current_depth = array();
+	$limit_bbcode = $tags;
+	$count_ignored = array();
+
+	foreach ($split_text as $current)
+	{
+		if ($current == '')
+			continue;
+
+		// Are we dealing with a tag?
+		if (substr($current, 0, 1) != '[' || substr($current, -1, 1) != ']')
+		{
+			// It's not a bbcode tag so we put it on the end and continue
+			// If we are nested too deeply don't add to the end
+			if ($current_nest)
+				continue;
+
+			$current = str_replace("\r\n", "\n", $current);
+			$current = str_replace("\r", "\n", $current);
+			if (in_array($open_tags[$opened_tag], $tags_inline) && strpos($current, "\n") !== false)
+			{
+				// Deal with new lines
+				$split_current = preg_split('%(\n\n+)%', $current, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
+				$current = '';
+
+				if (!pun_trim($split_current[0], "\n")) // The first part is a linebreak so we need to handle any open tags first
+					array_unshift($split_current, '');
+
+				for ($i = 1; $i < count($split_current); $i += 2)
+				{
+					$temp_opened = array();
+					$temp_opened_arg = array();
+					$temp = $split_current[$i - 1];
+					while (!empty($open_tags))
+					{
+						$temp_tag = array_pop($open_tags);
+						$temp_arg = array_pop($open_args);
+
+						if (in_array($temp_tag , $tags_inline))
+						{
+							array_push($temp_opened, $temp_tag);
+							array_push($temp_opened_arg, $temp_arg);
+							$temp .= '[/'.$temp_tag.']';
+						}
+						else
+						{
+							array_push($open_tags, $temp_tag);
+							array_push($open_args, $temp_arg);
+							break;
+						}
+					}
+					$current .= $temp.$split_current[$i];
+					$temp = '';
+					while (!empty($temp_opened))
+					{
+						$temp_tag = array_pop($temp_opened);
+						$temp_arg = array_pop($temp_opened_arg);
+						if (empty($temp_arg))
+							$temp .= '['.$temp_tag.']';
+						else
+							$temp .= '['.$temp_tag.'='.$temp_arg.']';
+						array_push($open_tags, $temp_tag);
+						array_push($open_args, $temp_arg);
+					}
+					$current .= $temp;
+				}
+
+				if (array_key_exists($i - 1, $split_current))
+					$current .= $split_current[$i - 1];
+			}
+
+			if (in_array($open_tags[$opened_tag], $tags_trim))
+				$new_text .= pun_trim($current);
+			else
+				$new_text .= $current;
+
+			continue;
+		}
+
+		// Get the name of the tag
+		$current_arg = '';
+		if (strpos($current, '/') === 1)
+		{
+			$current_tag = substr($current, 2, -1);
+		}
+		else if (strpos($current, '=') === false)
+		{
+			$current_tag = substr($current, 1, -1);
+		}
+		else
+		{
+			$current_tag = substr($current, 1, strpos($current, '=')-1);
+			$current_arg = substr($current, strpos($current, '=')+1, -1);
+		}
+		$current_tag = strtolower($current_tag);
+
+		// Is the tag defined?
+		if (!in_array($current_tag, $tags))
+		{
+			// It's not a bbcode tag so we put it on the end and continue
+			if (!$current_nest)
+				$new_text .= $current;
+
+			continue;
+		}
+
+		// We definitely have a bbcode tag
+
+		// Make the tag string lower case
+		if ($equalpos = strpos($current,'='))
+		{
+			// We have an argument for the tag which we don't want to make lowercase
+			if (strlen(substr($current, $equalpos)) == 2)
+			{
+				// Empty tag argument
+				$errors[] = sprintf($lang_common['BBCode error empty attribute'], $current_tag);
+				return false;
+			}
+			$current = strtolower(substr($current, 0, $equalpos)).substr($current, $equalpos);
+		}
+		else
+			$current = strtolower($current);
+
+		// This is if we are currently in a tag which escapes other bbcode such as code
+		// We keep a count of ignored bbcodes (code tags) so we can nest them, but
+		// only balanced sets of tags can be nested
+		if ($current_ignore)
+		{
+			// Increase the current ignored tags counter
+			if ('['.$current_ignore.']' == $current)
+				$count_ignored[$current_tag]++;
+
+			// Decrease the current ignored tags counter
+			if ('[/'.$current_ignore.']' == $current)
+				$count_ignored[$current_tag]--;
+
+			if ('[/'.$current_ignore.']' == $current && $count_ignored[$current_tag] == 0)
+			{
+				// We've finished the ignored section
+				$current = '[/'.$current_tag.']';
+				$current_ignore = '';
+				$count_ignored = array();
+			}
+
+			$new_text .= $current;
+
+			continue;
+		}
+
+		if ($current_nest)
+		{
+			// We are currently too deeply nested so lets see if we are closing the tag or not
+			if ($current_tag != $current_nest)
+				continue;
+
+			if (substr($current, 1, 1) == '/')
+				$current_depth[$current_nest]--;
+			else
+				$current_depth[$current_nest]++;
+
+			if ($current_depth[$current_nest] <= $tags_nested[$current_nest])
+				$current_nest = '';
+
+			continue;
+		}
+
+		// Check the current tag is allowed here
+		if (!in_array($current_tag, $limit_bbcode) && $current_tag != $open_tags[$opened_tag])
+		{
+			$errors[] = sprintf($lang_common['BBCode error invalid nesting'], $current_tag, $open_tags[$opened_tag]);
+			return false;
+		}
+
+		if (substr($current, 1, 1) == '/')
+		{
+			// This is if we are closing a tag
+			if ($opened_tag == 0 || !in_array($current_tag, $open_tags))
+			{
+				// We tried to close a tag which is not open
+				if (in_array($current_tag, $tags_opened))
+				{
+					$errors[] = sprintf($lang_common['BBCode error no opening tag'], $current_tag);
+					return false;
+				}
+			}
+			else
+			{
+				// Check nesting
+				while (true)
+				{
+					// Nesting is ok
+					if ($open_tags[$opened_tag] == $current_tag)
+					{
+						array_pop($open_tags);
+						array_pop($open_args);
+						$opened_tag--;
+						break;
+					}
+
+					// Nesting isn't ok, try to fix it
+					if (in_array($open_tags[$opened_tag], $tags_closed) && in_array($current_tag, $tags_closed))
+					{
+						if (in_array($current_tag, $open_tags))
+						{
+							$temp_opened = array();
+							$temp_opened_arg = array();
+							$temp = '';
+							while (!empty($open_tags))
+							{
+								$temp_tag = array_pop($open_tags);
+								$temp_arg = array_pop($open_args);
+
+								if (!in_array($temp_tag, $tags_fix))
+								{
+									// We couldn't fix nesting
+									$errors[] = sprintf($lang_common['BBCode error no closing tag'], array_pop($temp_opened));
+									return false;
+								}
+								array_push($temp_opened, $temp_tag);
+								array_push($temp_opened_arg, $temp_arg);
+
+								if ($temp_tag == $current_tag)
+									break;
+								else
+									$temp .= '[/'.$temp_tag.']';
+							}
+							$current = $temp.$current;
+							$temp = '';
+							array_pop($temp_opened);
+							array_pop($temp_opened_arg);
+
+							while (!empty($temp_opened))
+							{
+								$temp_tag = array_pop($temp_opened);
+								$temp_arg = array_pop($temp_opened_arg);
+								if (empty($temp_arg))
+									$temp .= '['.$temp_tag.']';
+								else
+									$temp .= '['.$temp_tag.'='.$temp_arg.']';
+								array_push($open_tags, $temp_tag);
+								array_push($open_args, $temp_arg);
+							}
+							$current .= $temp;
+							$opened_tag--;
+							break;
+						}
+						else
+						{
+							// We couldn't fix nesting
+							$errors[] = sprintf($lang_common['BBCode error no opening tag'], $current_tag);
+							return false;
+						}
+					}
+					else if (in_array($open_tags[$opened_tag], $tags_closed))
+						break;
+					else
+					{
+						array_pop($open_tags);
+						array_pop($open_args);
+						$opened_tag--;
+					}
+				}
+			}
+
+			if (in_array($current_tag, array_keys($tags_nested)))
+			{
+				if (isset($current_depth[$current_tag]))
+					$current_depth[$current_tag]--;
+			}
+
+			if (in_array($open_tags[$opened_tag], array_keys($tags_limit_bbcode)))
+				$limit_bbcode = $tags_limit_bbcode[$open_tags[$opened_tag]];
+			else
+				$limit_bbcode = $tags;
+
+			$new_text .= $current;
+
+			continue;
+		}
+		else
+		{
+			// We are opening a tag
+			if (in_array($current_tag, array_keys($tags_limit_bbcode)))
+				$limit_bbcode = $tags_limit_bbcode[$current_tag];
+			else
+				$limit_bbcode = $tags;
+
+			if (in_array($current_tag, $tags_block) && !in_array($open_tags[$opened_tag], $tags_block) && $opened_tag != 0)
+			{
+				// We tried to open a block tag within a non-block tag
+				$errors[] = sprintf($lang_common['BBCode error invalid nesting'], $current_tag, $open_tags[$opened_tag]);
+				return false;
+			}
+
+			if (in_array($current_tag, $tags_ignore))
+			{
+				// It's an ignore tag so we don't need to worry about what's inside it
+				$current_ignore = $current_tag;
+				$count_ignored[$current_tag] = 1;
+				$new_text .= $current;
+				continue;
+			}
+
+			// Deal with nested tags
+			if (in_array($current_tag, $open_tags) && !in_array($current_tag, array_keys($tags_nested)))
+			{
+				// We nested a tag we shouldn't
+				$errors[] = sprintf($lang_common['BBCode error invalid self-nesting'], $current_tag);
+				return false;
+			}
+			else if (in_array($current_tag, array_keys($tags_nested)))
+			{
+				// We are allowed to nest this tag
+
+				if (isset($current_depth[$current_tag]))
+					$current_depth[$current_tag]++;
+				else
+					$current_depth[$current_tag] = 1;
+
+				// See if we are nested too deep
+				if ($current_depth[$current_tag] > $tags_nested[$current_tag])
+				{
+					$current_nest = $current_tag;
+					continue;
+				}
+			}
+
+			// Remove quotes from arguments for certain tags
+			if (strpos($current, '=') !== false && in_array($current_tag, $tags_quotes))
+			{
+				$current = preg_replace('%\['.$current_tag.'=("|\'|)(.*?)\\1\]\s*%i', '['.$current_tag.'=$2]', $current);
+			}
+
+			if (in_array($current_tag, array_keys($tags_limit_bbcode)))
+				$limit_bbcode = $tags_limit_bbcode[$current_tag];
+
+			$open_tags[] = $current_tag;
+			$open_args[] = $current_arg;
+			$opened_tag++;
+			$new_text .= $current;
+			continue;
+		}
+	}
+
+	// Check we closed all the tags we needed to
+	foreach ($tags_closed as $check)
+	{
+		if (in_array($check, $open_tags))
+		{
+			// We left an important tag open
+			$errors[] = sprintf($lang_common['BBCode error no closing tag'], $check);
+			return false;
+		}
+	}
+
+	if ($current_ignore)
+	{
+		// We left an ignore tag open
+		$errors[] = sprintf($lang_common['BBCode error no closing tag'], $current_ignore);
+		return false;
+	}
+
+	return $new_text;
+}
+
+
+//
+// Preparse the contents of [list] bbcode
+//
+function preparse_list_tag($content, $type = '*')
+{
+	global $lang_common, $re_list;
+
+	if (strlen($type) != 1)
+		$type = '*';
+
+	if (strpos($content,'[list') !== false)
+	{
+		$content = preg_replace($re_list, 'preparse_list_tag(\'$2\', \'$1\')', $content);
+	}
+
+	$items = explode('[*]', str_replace('\"', '"', $content));
+
+	$content = '';
+	foreach ($items as $item)
+	{
+		if (pun_trim($item) != '')
+			$content .= '[*'."\0".']'.str_replace('[/*]', '', pun_trim($item)).'[/*'."\0".']'."\n";
+	}
+
+	return '[list='.$type.']'."\n".$content.'[/list]';
+}
+
+
+//
+// Truncate URL if longer than 55 characters (add http:// or ftp:// if missing)
+//
+function handle_url_tag($url, $link = '', $bbcode = false)
+{
+	$url = pun_trim($url);
+
+	// Deal with [url][img]http://example.com/test.png[/img][/url]
+	if (preg_match('%<img src=\\\\"(.*?)\\\\"%', $url, $matches))
+		return handle_url_tag($matches[1], $url, $bbcode);
+
+	$full_url = str_replace(array(' ', '\'', '`', '"'), array('%20', '', '', ''), $url);
+	if (strpos($url, 'www.') === 0) // If it starts with www, we add http://
+		$full_url = 'http://'.$full_url;
+	else if (strpos($url, 'ftp.') === 0) // Else if it starts with ftp, we add ftp://
+		$full_url = 'ftp://'.$full_url;
+	else if (strpos($url, '/') === 0) // Allow for relative URLs that start with a slash
+		$full_url = get_base_url(true).$full_url;
+	else if (!preg_match('#^([a-z0-9]{3,6})://#', $url)) // Else if it doesn't start with abcdef://, we add http://
+		$full_url = 'http://'.$full_url;
+
+	// Ok, not very pretty :-)
+	if ($bbcode)
+	{
+		if ($full_url == $link)
+			return '[url]'.$link.'[/url]';
+		else
+			return '[url='.$full_url.']'.$link.'[/url]';
+	}
+	else
+	{
+		if ($link == '' || $link == $url)
+		{
+			$url = pun_htmlspecialchars_decode($url);
+			$link = utf8_strlen($url) > 55 ? utf8_substr($url, 0 , 39).' … '.utf8_substr($url, -10) : $url;
+			$link = pun_htmlspecialchars($link);
+		}
+		else
+			$link = stripslashes($link);
+
+		return '<a href="'.$full_url.'">'.$link.'</a>';
+	}
+}
+
+
+//
+// Turns an URL from the [img] tag into an <img> tag or a <a href...> tag
+//
+function handle_img_tag($url, $is_signature = false, $alt = null)
+{
+	global $lang_common, $pun_user;
+
+	if (is_null($alt))
+		$alt = basename($url);
+
+	$img_tag = '<a href="'.$url.'">&lt;'.$lang_common['Image link'].' - '.$alt.'&gt;</a>';
+
+	if ($is_signature && $pun_user['show_img_sig'] != '0')
+		$img_tag = '<img class="sigimage" src="'.$url.'" alt="'.$alt.'" />';
+	else if (!$is_signature && $pun_user['show_img'] != '0')
+		$img_tag = '<span class="postimg"><img src="'.$url.'" alt="'.$alt.'" /></span>';
+
+	return $img_tag;
+}
+
+
+//
+// Parse the contents of [list] bbcode
+//
+function handle_list_tag($content, $type = '*')
+{
+	global $re_list;
+
+	if (strlen($type) != 1)
+		$type = '*';
+
+	if (strpos($content,'[list') !== false)
+	{
+		$content = preg_replace($re_list, 'handle_list_tag(\'$2\', \'$1\')', $content);
+	}
+
+	$content = preg_replace('#\s*\[\*\](.*?)\[/\*\]\s*#s', '<li><p>$1</p></li>', pun_trim($content));
+
+	if ($type == '*')
+		$content = '<ul>'.$content.'</ul>';
+	else
+		if ($type == 'a')
+			$content = '<ol class="alpha">'.$content.'</ol>';
+		else
+			$content = '<ol class="decimal">'.$content.'</ol>';
+
+	return '</p>'.$content.'<p>';
+}
+
+
+//
+// Convert BBCodes to their HTML equivalent
+//
+function do_bbcode($text, $is_signature = false)
+{
+	global $lang_common, $pun_user, $pun_config, $re_list;
+
+	if (strpos($text, '[quote') !== false)
+	{
+		$text = preg_replace('%\[quote\]\s*%', '</p><div class="quotebox"><blockquote><div><p>', $text);
+		$text = preg_replace('%\[quote=(&quot;|&\#039;|"|\'|)(.*?)\\1\]%se', '"</p><div class=\"quotebox\"><cite>".str_replace(array(\'[\', \'\\"\'), array(\'&#91;\', \'"\'), \'$2\')." ".$lang_common[\'wrote\']."</cite><blockquote><div><p>"', $text);
+		$text = preg_replace('%\s*\[\/quote\]%S', '</p></div></blockquote></div><p>', $text);
+	}
+
+	if (!$is_signature)
+	{
+		$pattern[] = $re_list;
+		$replace[] = 'handle_list_tag(\'$2\', \'$1\')';
+	}
+
+	$pattern[] = '%\[b\](.*?)\[/b\]%ms';
+	$pattern[] = '%\[i\](.*?)\[/i\]%ms';
+	$pattern[] = '%\[u\](.*?)\[/u\]%ms';
+	$pattern[] = '%\[s\](.*?)\[/s\]%ms';
+	$pattern[] = '%\[del\](.*?)\[/del\]%ms';
+	$pattern[] = '%\[ins\](.*?)\[/ins\]%ms';
+	$pattern[] = '%\[em\](.*?)\[/em\]%ms';
+	$pattern[] = '%\[colou?r=([a-zA-Z]{3,20}|\#[0-9a-fA-F]{6}|\#[0-9a-fA-F]{3})](.*?)\[/colou?r\]%ms';
+	$pattern[] = '%\[h\](.*?)\[/h\]%ms';
+
+	$replace[] = '<strong>$1</strong>';
+	$replace[] = '<em>$1</em>';
+	$replace[] = '<span class="bbu">$1</span>';
+	$replace[] = '<span class="bbs">$1</span>';
+	$replace[] = '<del>$1</del>';
+	$replace[] = '<ins>$1</ins>';
+	$replace[] = '<em>$1</em>';
+	$replace[] = '<span style="color: $1">$2</span>';
+	$replace[] = '</p><h5>$1</h5><p>';
+
+	if (($is_signature && $pun_config['p_sig_img_tag'] == '1') || (!$is_signature && $pun_config['p_message_img_tag'] == '1'))
+	{
+		$pattern[] = '%\[img\]((ht|f)tps?://)([^\s<"]*?)\[/img\]%e';
+		$pattern[] = '%\[img=([^\[]*?)\]((ht|f)tps?://)([^\s<"]*?)\[/img\]%e';
+		if ($is_signature)
+		{
+			$replace[] = 'handle_img_tag(\'$1$3\', true)';
+			$replace[] = 'handle_img_tag(\'$2$4\', true, \'$1\')';
+		}
+		else
+		{
+			$replace[] = 'handle_img_tag(\'$1$3\', false)';
+			$replace[] = 'handle_img_tag(\'$2$4\', false, \'$1\')';
+		}
+	}
+
+	$pattern[] = '%\[url\]([^\[]*?)\[/url\]%e';
+	$pattern[] = '%\[url=([^\[]+?)\](.*?)\[/url\]%e';
+	$pattern[] = '%\[email\]([^\[]*?)\[/email\]%';
+	$pattern[] = '%\[email=([^\[]+?)\](.*?)\[/email\]%';
+	$pattern[] = '%\[topic\]([1-9]\d*)\[/topic\]%e';
+	$pattern[] = '%\[topic=([1-9]\d*)\](.*?)\[/topic\]%e';
+	$pattern[] = '%\[post\]([1-9]\d*)\[/post\]%e';
+	$pattern[] = '%\[post=([1-9]\d*)\](.*?)\[/post\]%e';
+	$pattern[] = '%\[forum\]([1-9]\d*)\[/forum\]%e';
+	$pattern[] = '%\[forum=([1-9]\d*)\](.*?)\[/forum\]%e';
+	$pattern[] = '%\[user\]([1-9]\d*)\[/user\]%e';
+	$pattern[] = '%\[user=([1-9]\d*)\](.*?)\[/user\]%e';
+
+	$replace[] = 'handle_url_tag(\'$1\')';
+	$replace[] = 'handle_url_tag(\'$1\', \'$2\')';
+	$replace[] = '<a href="mailto:$1">$1</a>';
+	$replace[] = '<a href="mailto:$1">$2</a>';
+	$replace[] = 'handle_url_tag(\''.get_base_url(true).'/viewtopic.php?id=$1\')';
+	$replace[] = 'handle_url_tag(\''.get_base_url(true).'/viewtopic.php?id=$1\', \'$2\')';
+	$replace[] = 'handle_url_tag(\''.get_base_url(true).'/viewtopic.php?pid=$1#p$1\')';
+	$replace[] = 'handle_url_tag(\''.get_base_url(true).'/viewtopic.php?pid=$1#p$1\', \'$2\')';
+	$replace[] = 'handle_url_tag(\''.get_base_url(true).'/viewforum.php?id=$1\')';
+	$replace[] = 'handle_url_tag(\''.get_base_url(true).'/viewforum.php?id=$1\', \'$2\')';
+	$replace[] = 'handle_url_tag(\''.get_base_url(true).'/profile.php?id=$1\')';
+	$replace[] = 'handle_url_tag(\''.get_base_url(true).'/profile.php?id=$1\', \'$2\')';
+
+	// This thing takes a while! :)
+	$text = preg_replace($pattern, $replace, $text);
+
+	return $text;
+}
+
+
+//
+// Make hyperlinks clickable
+//
+function do_clickable($text)
+{
+	$text = ' '.$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);
+}
+
+
+//
+// Convert a series of smilies to images
+//
+function do_smilies($text)
+{
+	global $pun_config, $smilies;
+
+	$text = ' '.$text.' ';
+
+	foreach ($smilies as $smiley_text => $smiley_img)
+	{
+		if (strpos($text, $smiley_text) !== false)
+			$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);
+}
+
+
+//
+// Parse message text
+//
+function parse_message($text, $hide_smilies)
+{
+	global $pun_config, $lang_common, $pun_user;
+
+	if ($pun_config['o_censoring'] == '1')
+		$text = censor_words($text);
+
+	// Convert applicable characters to HTML entities
+	$text = pun_htmlspecialchars($text);
+
+	// If the message contains a code tag we have to split it up (text within [code][/code] shouldn't be touched)
+	if (strpos($text, '[code]') !== false && strpos($text, '[/code]') !== false)
+		list($inside, $text) = extract_blocks($text, '[code]', '[/code]');
+
+	if ($pun_config['p_message_bbcode'] == '1' && strpos($text, '[') !== false && strpos($text, ']') !== false)
+		$text = do_bbcode($text);
+
+	if ($pun_config['o_smilies'] == '1' && $pun_user['show_smilies'] == '1' && $hide_smilies == '0')
+		$text = do_smilies($text);
+
+	// Deal with newlines, tabs and multiple spaces
+	$pattern = array("\n", "\t", '  ', '  ');
+	$replace = array('<br />', '&#160; &#160; ', '&#160; ', ' &#160;');
+	$text = str_replace($pattern, $replace, $text);
+
+	// If we split up the message before we have to concatenate it together again (code tags)
+	if (isset($inside))
+	{
+		$parts = explode("\1", $text);
+		$text = '';
+		foreach ($parts as $i => $part)
+		{
+			$text .= $part;
+			if (isset($inside[$i]))
+			{
+				$num_lines = (substr_count($inside[$i], "\n"));
+				$text .= '</p><div class="codebox"><pre'.(($num_lines > 28) ? ' class="vscroll"' : '').'><code>'.pun_trim($inside[$i], "\n\r").'</code></pre></div><p>';
+			}
+		}
+	}
+
+	return clean_paragraphs($text);
+}
+
+
+//
+// Clean up paragraphs and line breaks
+//
+function clean_paragraphs($text)
+{
+	// Add paragraph tag around post, but make sure there are no empty paragraphs
+
+	$text = '<p>'.$text.'</p>';
+
+	// Replace any breaks next to paragraphs so our replace below catches them
+	$text = preg_replace('%(</?p>)(?:\s*?<br />){1,2}%i', '$1', $text);
+	$text = preg_replace('%(?:<br />\s*?){1,2}(</?p>)%i', '$1', $text);
+
+	// Remove any empty paragraph tags (inserted via quotes/lists/code/etc) which should be stripped
+	$text = str_replace('<p></p>', '', $text);
+
+	$text = preg_replace('%<br />\s*?<br />%i', '</p><p>', $text);
+
+	$text = str_replace('<p><br />', '<br /><p>', $text);
+	$text = str_replace('<br /></p>', '</p><br />', $text);
+	$text = str_replace('<p></p>', '<br /><br />', $text);
+
+	return $text;
+}
+
+
+//
+// Parse signature text
+//
+function parse_signature($text)
+{
+	global $pun_config, $lang_common, $pun_user;
+
+	if ($pun_config['o_censoring'] == '1')
+		$text = censor_words($text);
+
+	// Convert applicable characters to HTML entities
+	$text = pun_htmlspecialchars($text);
+
+	if ($pun_config['p_sig_bbcode'] == '1' && strpos($text, '[') !== false && strpos($text, ']') !== false)
+		$text = do_bbcode($text, true);
+
+	if ($pun_config['o_smilies_sig'] == '1' && $pun_user['show_smilies'] == '1')
+		$text = do_smilies($text);
+
+
+	// Deal with newlines, tabs and multiple spaces
+	$pattern = array("\n", "\t", '  ', '  ');
+	$replace = array('<br />', '&#160; &#160; ', '&#160; ', ' &#160;');
+	$text = str_replace($pattern, $replace, $text);
+
+	return clean_paragraphs($text);
+}
diff --git a/include/search_idx.php b/include/search_idx.php
new file mode 100644
index 0000000..550f348
--- /dev/null
+++ b/include/search_idx.php
@@ -0,0 +1,316 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// The contents of this file are very much inspired by the file functions_search.php
+// from the phpBB Group forum software phpBB2 (http://www.phpbb.com)
+
+
+// Make sure no one attempts to run this script "directly"
+if (!defined('PUN'))
+	exit;
+
+
+// Make a regex that will match CJK or Hangul characters
+define('PUN_CJK_HANGUL_REGEX', '['.
+	'\x{1100}-\x{11FF}'.		// Hangul Jamo							1100-11FF		(http://www.fileformat.info/info/unicode/block/hangul_jamo/index.htm)
+	'\x{3130}-\x{318F}'.		// Hangul Compatibility Jamo			3130-318F		(http://www.fileformat.info/info/unicode/block/hangul_compatibility_jamo/index.htm)
+	'\x{AC00}-\x{D7AF}'.		// Hangul Syllables						AC00-D7AF		(http://www.fileformat.info/info/unicode/block/hangul_syllables/index.htm)
+
+	// Hiragana
+	'\x{3040}-\x{309F}'.		// Hiragana								3040-309F		(http://www.fileformat.info/info/unicode/block/hiragana/index.htm)
+
+	// Katakana
+	'\x{30A0}-\x{30FF}'.		// Katakana								30A0-30FF		(http://www.fileformat.info/info/unicode/block/katakana/index.htm)
+	'\x{31F0}-\x{31FF}'.		// Katakana Phonetic Extensions			31F0-31FF		(http://www.fileformat.info/info/unicode/block/katakana_phonetic_extensions/index.htm)
+
+	// CJK Unified Ideographs	(http://en.wikipedia.org/wiki/CJK_Unified_Ideographs)
+	'\x{2E80}-\x{2EFF}'.		// CJK Radicals Supplement				2E80-2EFF		(http://www.fileformat.info/info/unicode/block/cjk_radicals_supplement/index.htm)
+	'\x{2F00}-\x{2FDF}'.		// Kangxi Radicals						2F00-2FDF		(http://www.fileformat.info/info/unicode/block/kangxi_radicals/index.htm)
+	'\x{2FF0}-\x{2FFF}'.		// Ideographic Description Characters	2FF0-2FFF		(http://www.fileformat.info/info/unicode/block/ideographic_description_characters/index.htm)
+	'\x{3000}-\x{303F}'.		// CJK Symbols and Punctuation			3000-303F		(http://www.fileformat.info/info/unicode/block/cjk_symbols_and_punctuation/index.htm)
+	'\x{31C0}-\x{31EF}'.		// CJK Strokes							31C0-31EF		(http://www.fileformat.info/info/unicode/block/cjk_strokes/index.htm)
+	'\x{3200}-\x{32FF}'.		// Enclosed CJK Letters and Months		3200-32FF		(http://www.fileformat.info/info/unicode/block/enclosed_cjk_letters_and_months/index.htm)
+	'\x{3400}-\x{4DBF}'.		// CJK Unified Ideographs Extension A	3400-4DBF		(http://www.fileformat.info/info/unicode/block/cjk_unified_ideographs_extension_a/index.htm)
+	'\x{4E00}-\x{9FFF}'.		// CJK Unified Ideographs				4E00-9FFF		(http://www.fileformat.info/info/unicode/block/cjk_unified_ideographs/index.htm)
+	'\x{20000}-\x{2A6DF}'.		// CJK Unified Ideographs Extension B	20000-2A6DF		(http://www.fileformat.info/info/unicode/block/cjk_unified_ideographs_extension_b/index.htm)
+']');
+
+
+//
+// "Cleans up" a text string and returns an array of unique words
+// This function depends on the current locale setting
+//
+function split_words($text, $idx)
+{
+	// Remove BBCode
+	$text = preg_replace('%\[/?(b|u|s|ins|del|em|i|h|colou?r|quote|code|img|url|email|list|topic|post|forum|user)(?:\=[^\]]*)?\]%', ' ', $text);
+
+	// Remove any apostrophes or dashes which aren't part of words
+	$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 = ucp_preg_replace('%(?![\'\-'.($idx ? '' : '\%\*').'])[^\p{L}\p{N}]+%u', ' ', $text);
+
+	// Replace multiple whitespace or dashes
+	$text = preg_replace('%(\s){2,}%u', '\1', $text);
+
+	// Fill an array with all the words
+	$words = array_unique(explode(' ', $text));
+
+	// Remove any words that should not be indexed
+	foreach ($words as $key => $value)
+	{
+		// If the word shouldn't be indexed, remove it
+		if (!validate_search_word($value, $idx))
+			unset($words[$key]);
+	}
+
+	return $words;
+}
+
+
+//
+// Checks if a word is a valid searchable word
+//
+function validate_search_word($word, $idx)
+{
+	static $stopwords;
+
+	// If the word is a keyword we don't want to index it, but we do want to be allowed to search it
+	if (is_keyword($word))
+		return !$idx;
+
+	if (!isset($stopwords))
+	{
+		if (file_exists(FORUM_CACHE_DIR.'cache_stopwords.php'))
+			include FORUM_CACHE_DIR.'cache_stopwords.php';
+
+		if (!defined('PUN_STOPWORDS_LOADED'))
+		{
+			if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
+				require PUN_ROOT.'include/cache.php';
+
+			generate_stopwords_cache();
+			require FORUM_CACHE_DIR.'cache_stopwords.php';
+		}
+	}
+
+	// If it is a stopword it isn't valid
+	if (in_array($word, $stopwords))
+		return false;
+
+	// If the word if CJK we don't want to index it, but we do want to be allowed to search it
+	if (is_cjk($word))
+		return !$idx;
+
+	// Exclude % and * when checking whether current word is valid
+	$word = str_replace(array('%', '*'), '', $word);
+
+	// Check the word is within the min/max length
+	$num_chars = pun_strlen($word);
+	return $num_chars >= PUN_SEARCH_MIN_WORD && $num_chars <= PUN_SEARCH_MAX_WORD;
+}
+
+
+//
+// Check a given word is a search keyword.
+//
+function is_keyword($word)
+{
+	return $word == 'and' || $word == 'or' || $word == 'not';
+}
+
+
+//
+// Check if a given word is CJK or Hangul.
+//
+function is_cjk($word)
+{
+	return preg_match('%^'.PUN_CJK_HANGUL_REGEX.'+$%u', $word) ? true : false;
+}
+
+
+//
+// Strip [img] [url] and [email] out of the message so we don't index their contents
+//
+function strip_bbcode($text)
+{
+	static $patterns;
+
+	if (!isset($patterns))
+	{
+		$patterns = array(
+			'%\[img=([^\]]*+)\]([^[]*+)\[/img\]%'									=>	'$2 $1',	// Keep the url and description
+			'%\[(url|email)=([^\]]*+)\]([^[]*+(?:(?!\[/\1\])\[[^[]*+)*)\[/\1\]%'	=>	'$2 $3',	// Keep the url and text
+			'%\[(img|url|email)\]([^[]*+(?:(?!\[/\1\])\[[^[]*+)*)\[/\1\]%'			=>	'$2',		// Keep the url
+			'%\[(topic|post|forum|user)\][1-9]\d*\[/\1\]%'							=>	' ',		// Do not index topic/post/forum/user ID
+		);
+	}
+
+	return preg_replace(array_keys($patterns), array_values($patterns), $text);
+}
+
+
+//
+// Updates the search index with the contents of $post_id (and $subject)
+//
+function update_search_index($mode, $post_id, $message, $subject = null)
+{
+	global $db_type, $db;
+
+	$message = utf8_strtolower($message);
+	$subject = utf8_strtolower($subject);
+
+	// Remove any bbcode that we shouldn't index
+	$message = strip_bbcode($message);
+
+	// Split old and new post/subject to obtain array of 'words'
+	$words_message = split_words($message, true);
+	$words_subject = ($subject) ? split_words($subject, true) : array();
+
+	if ($mode == 'edit')
+	{
+		$result = $db->query('SELECT w.id, w.word, m.subject_match FROM '.$db->prefix.'search_words AS w INNER JOIN '.$db->prefix.'search_matches AS m ON w.id=m.word_id WHERE m.post_id='.$post_id, true) or error('Unable to fetch search index words', __FILE__, __LINE__, $db->error());
+
+		// Declare here to stop array_keys() and array_diff() from complaining if not set
+		$cur_words['post'] = array();
+		$cur_words['subject'] = array();
+
+		while ($row = $db->fetch_row($result))
+		{
+			$match_in = ($row[2]) ? 'subject' : 'post';
+			$cur_words[$match_in][$row[1]] = $row[0];
+		}
+
+		$db->free_result($result);
+
+		$words['add']['post'] = array_diff($words_message, array_keys($cur_words['post']));
+		$words['add']['subject'] = array_diff($words_subject, array_keys($cur_words['subject']));
+		$words['del']['post'] = array_diff(array_keys($cur_words['post']), $words_message);
+		$words['del']['subject'] = array_diff(array_keys($cur_words['subject']), $words_subject);
+	}
+	else
+	{
+		$words['add']['post'] = $words_message;
+		$words['add']['subject'] = $words_subject;
+		$words['del']['post'] = array();
+		$words['del']['subject'] = array();
+	}
+
+	unset($words_message);
+	unset($words_subject);
+
+	// Get unique words from the above arrays
+	$unique_words = array_unique(array_merge($words['add']['post'], $words['add']['subject']));
+
+	if (!empty($unique_words))
+	{
+		$result = $db->query('SELECT id, word FROM '.$db->prefix.'search_words WHERE word IN(\''.implode('\',\'', array_map(array($db, 'escape'), $unique_words)).'\')', true) or error('Unable to fetch search index words', __FILE__, __LINE__, $db->error());
+
+		$word_ids = array();
+		while ($row = $db->fetch_row($result))
+			$word_ids[$row[1]] = $row[0];
+
+		$db->free_result($result);
+
+		$new_words = array_diff($unique_words, array_keys($word_ids));
+		unset($unique_words);
+
+		if (!empty($new_words))
+		{
+			switch ($db_type)
+			{
+				case 'mysql':
+				case 'mysqli':
+				case 'mysql_innodb':
+				case 'mysqli_innodb':
+					$db->query('INSERT INTO '.$db->prefix.'search_words (word) VALUES(\''.implode('\'),(\'', array_map(array($db, 'escape'), $new_words)).'\')');
+					break;
+
+				default:
+					foreach ($new_words as $word)
+						$db->query('INSERT INTO '.$db->prefix.'search_words (word) VALUES(\''.$db->escape($word).'\')');
+					break;
+			}
+		}
+
+		unset($new_words);
+	}
+
+	// Delete matches (only if editing a post)
+	foreach ($words['del'] as $match_in => $wordlist)
+	{
+		$subject_match = ($match_in == 'subject') ? 1 : 0;
+
+		if (!empty($wordlist))
+		{
+			$sql = '';
+			foreach ($wordlist as $word)
+				$sql .= (($sql != '') ? ',' : '').$cur_words[$match_in][$word];
+
+			$db->query('DELETE FROM '.$db->prefix.'search_matches WHERE word_id IN('.$sql.') AND post_id='.$post_id.' AND subject_match='.$subject_match) or error('Unable to delete search index word matches', __FILE__, __LINE__, $db->error());
+		}
+	}
+
+	// Add new matches
+	foreach ($words['add'] as $match_in => $wordlist)
+	{
+		$subject_match = ($match_in == 'subject') ? 1 : 0;
+
+		if (!empty($wordlist))
+			$db->query('INSERT INTO '.$db->prefix.'search_matches (post_id, word_id, subject_match) SELECT '.$post_id.', id, '.$subject_match.' FROM '.$db->prefix.'search_words WHERE word IN(\''.implode('\',\'', array_map(array($db, 'escape'), $wordlist)).'\')') or error('Unable to insert search index word matches', __FILE__, __LINE__, $db->error());
+	}
+
+	unset($words);
+}
+
+
+//
+// Strip search index of indexed words in $post_ids
+//
+function strip_search_index($post_ids)
+{
+	global $db_type, $db;
+
+	switch ($db_type)
+	{
+		case 'mysql':
+		case 'mysqli':
+		case 'mysql_innodb':
+		case 'mysqli_innodb':
+		{
+			$result = $db->query('SELECT word_id FROM '.$db->prefix.'search_matches WHERE post_id IN('.$post_ids.') GROUP BY word_id') or error('Unable to fetch search index word match', __FILE__, __LINE__, $db->error());
+
+			if ($db->num_rows($result))
+			{
+				$word_ids = '';
+				while ($row = $db->fetch_row($result))
+					$word_ids .= ($word_ids != '') ? ','.$row[0] : $row[0];
+
+				$result = $db->query('SELECT word_id FROM '.$db->prefix.'search_matches WHERE word_id IN('.$word_ids.') GROUP BY word_id HAVING COUNT(word_id)=1') or error('Unable to fetch search index word match', __FILE__, __LINE__, $db->error());
+
+				if ($db->num_rows($result))
+				{
+					$word_ids = '';
+					while ($row = $db->fetch_row($result))
+						$word_ids .= ($word_ids != '') ? ','.$row[0] : $row[0];
+
+					$db->query('DELETE FROM '.$db->prefix.'search_words WHERE id IN('.$word_ids.')') or error('Unable to delete search index word', __FILE__, __LINE__, $db->error());
+				}
+			}
+
+			break;
+		}
+
+		default:
+			$db->query('DELETE FROM '.$db->prefix.'search_words WHERE id IN(SELECT word_id FROM '.$db->prefix.'search_matches WHERE word_id IN(SELECT word_id FROM '.$db->prefix.'search_matches WHERE post_id IN('.$post_ids.') GROUP BY word_id) GROUP BY word_id HAVING COUNT(word_id)=1)') or error('Unable to delete from search index', __FILE__, __LINE__, $db->error());
+			break;
+	}
+
+	$db->query('DELETE FROM '.$db->prefix.'search_matches WHERE post_id IN('.$post_ids.')') or error('Unable to delete search index word match', __FILE__, __LINE__, $db->error());
+}
diff --git a/include/srand.php b/include/srand.php
new file mode 100644
index 0000000..28961ca
--- /dev/null
+++ b/include/srand.php
@@ -0,0 +1,145 @@
+<?php
+
+/*
+ * Author:
+ * George Argyros <argyros.george@gmail.com>
+ *
+ * Copyright (c) 2012, George Argyros
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *    * Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *    * Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *    * Neither the name of the <organization> nor the
+ *      names of its contributors may be used to endorse or promote products
+ *      derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GEORGE ARGYROS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *
+ *
+ * The function is providing, at least at the systems tested :), 
+ * $len bytes of entropy under any PHP installation or operating system.
+ * The execution time should be at most 10-20 ms in any system.
+ */
+function secure_random_bytes($len = 10)
+{  
+ 
+   /*
+    * Our primary choice for a cryptographic strong randomness function is
+    * openssl_random_pseudo_bytes. 
+    */
+   $SSLstr = '4'; // http://xkcd.com/221/
+   if (function_exists('openssl_random_pseudo_bytes') && 
+       (version_compare(PHP_VERSION, '5.3.4') >= 0 || 
+	substr(PHP_OS, 0, 3) !== 'WIN'))
+   {
+      $SSLstr = openssl_random_pseudo_bytes($len, $strong);
+      if ($strong)
+         return $SSLstr;
+   }
+
+   /*
+    * If mcrypt extension is available then we use it to gather entropy from 
+    * the operating system's PRNG. This is better than reading /dev/urandom 
+    * directly since it avoids reading larger blocks of data than needed. 
+    * Older versions of mcrypt_create_iv may be broken or take too much time 
+    * to finish so we only use this function with PHP 5.3 and above.
+    */
+   if (function_exists('mcrypt_create_iv') && 
+      (version_compare(PHP_VERSION, '5.3.0') >= 0 || 
+       substr(PHP_OS, 0, 3) !== 'WIN')) 
+   {
+      $str = mcrypt_create_iv($len, MCRYPT_DEV_URANDOM);
+      if ($str !== false)
+         return $str;	
+   }
+
+
+   /*
+    * No build-in crypto randomness function found. We collect any entropy 
+    * available in the PHP core PRNGs along with some filesystem info and memory
+    * stats. To make this data cryptographically strong we add data either from 
+    * /dev/urandom or if its unavailable, we gather entropy by measuring the 
+    * time needed to compute a number of SHA-1 hashes. 
+    */
+   $str = '';
+   $bits_per_round = 2; // bits of entropy collected in each clock drift round
+   $msec_per_round = 400; // expected running time of each round in microseconds
+   $hash_len = 20; // SHA-1 Hash length
+   $total = $len; // total bytes of entropy to collect
+
+   $handle = @fopen('/dev/urandom', 'rb');   
+   if ($handle && function_exists('stream_set_read_buffer'))
+      @stream_set_read_buffer($handle, 0);
+
+   do
+   {
+      $bytes = ($total > $hash_len)? $hash_len : $total;
+      $total -= $bytes;
+
+      //collect any entropy available from the PHP system and filesystem
+      $entropy = rand() . uniqid(mt_rand(), true) . $SSLstr;
+      $entropy .= implode('', @fstat(@fopen( __FILE__, 'r')));
+      $entropy .= memory_get_usage();
+      if ($handle) 
+      {
+         $entropy .= @fread($handle, $bytes);
+      }
+      else
+      {	           	
+         // Measure the time that the operations will take on average
+         for ($i = 0; $i < 3; $i ++) 
+         {
+            $c1 = get_microtime();
+            $var = sha1(mt_rand());
+            for ($j = 0; $j < 50; $j++)
+            {
+               $var = sha1($var);
+            }
+            $c2 = get_microtime();
+    	    $entropy .= $c1 . $c2;
+         }
+
+         // Based on the above measurement determine the total rounds
+         // in order to bound the total running time.	
+         $rounds = (int)($msec_per_round*50 / (int)(($c2-$c1)*1000000));
+
+         // Take the additional measurements. On average we can expect
+         // at least $bits_per_round bits of entropy from each measurement.
+         $iter = $bytes*(int)(ceil(8 / $bits_per_round));
+         for ($i = 0; $i < $iter; $i ++)
+         {
+            $c1 = time();
+            $var = sha1(mt_rand());
+            for ($j = 0; $j < $rounds; $j++)
+            {
+               $var = sha1($var);
+            }
+            $c2 = get_microtime();
+            $entropy .= $c1 . $c2;
+         }
+            
+      } 
+      // We assume sha1 is a deterministic extractor for the $entropy variable.
+      $str .= sha1($entropy, true);
+   } while ($len > strlen($str));
+   
+   if ($handle) 
+      @fclose($handle);
+   
+   return substr($str, 0, $len);
+}
\ No newline at end of file
diff --git a/include/template/admin.tpl b/include/template/admin.tpl
new file mode 100644
index 0000000..06679ee
--- /dev/null
+++ b/include/template/admin.tpl
@@ -0,0 +1,38 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<pun_language>" lang="<pun_language>" dir="<pun_content_direction>">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<pun_head>
+</head>
+
+<body>
+
+<div id="punadmin" class="pun">
+<div class="top-box"><div><!-- Top Corners --></div></div>
+<div class="punwrap">
+
+<div id="brdheader" class="block">
+	<div class="box">
+		<div id="brdtitle" class="inbox">
+			<pun_title>
+			<pun_desc>
+		</div>
+		<pun_navlinks>
+		<pun_status>
+	</div>
+</div>
+
+<pun_announcement>
+
+<div id="brdmain">
+<pun_main>
+</div>
+
+<pun_footer>
+
+</div>
+<div class="end-box"><div><!-- Bottom Corners --></div></div>
+</div>
+
+</body>
+</html>
diff --git a/include/template/help.tpl b/include/template/help.tpl
new file mode 100644
index 0000000..58fec14
--- /dev/null
+++ b/include/template/help.tpl
@@ -0,0 +1,23 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<pun_language>" lang="<pun_language>" dir="<pun_content_direction>">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<pun_head>
+</head>
+
+<body>
+
+<div id="punhelp" class="pun">
+<div class="top-box"><div><!-- Top Corners --></div></div>
+<div class="punwrap">
+
+<div id="brdmain">
+<pun_main>
+</div>
+
+</div>
+<div class="end-box"><div><!-- Bottom Corners --></div></div>
+</div>
+
+</body>
+</html>
diff --git a/include/template/index.html b/include/template/index.html
new file mode 100644
index 0000000..89337b2
--- /dev/null
+++ b/include/template/index.html
@@ -0,0 +1 @@
+<html><head><title>.</title></head><body>.</body></html>
diff --git a/include/template/main.tpl b/include/template/main.tpl
new file mode 100644
index 0000000..ea0a0b6
--- /dev/null
+++ b/include/template/main.tpl
@@ -0,0 +1,38 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<pun_language>" lang="<pun_language>" dir="<pun_content_direction>">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<pun_head>
+</head>
+
+<body>
+
+<div id="pun<pun_page>" class="pun">
+<div class="top-box"><div><!-- Top Corners --></div></div>
+<div class="punwrap">
+
+<div id="brdheader" class="block">
+	<div class="box">
+		<div id="brdtitle" class="inbox">
+			<pun_title>
+			<pun_desc>
+		</div>
+		<pun_navlinks>
+		<pun_status>
+	</div>
+</div>
+
+<pun_announcement>
+
+<div id="brdmain">
+<pun_main>
+</div>
+
+<pun_footer>
+
+</div>
+<div class="end-box"><div><!-- Bottom corners --></div></div>
+</div>
+
+</body>
+</html>
diff --git a/include/template/maintenance.tpl b/include/template/maintenance.tpl
new file mode 100644
index 0000000..56edc49
--- /dev/null
+++ b/include/template/maintenance.tpl
@@ -0,0 +1,23 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<pun_language>" lang="<pun_language>" dir="<pun_content_direction>">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<pun_head>
+</head>
+
+<body>
+
+<div id="punmaint" class="pun">
+<div class="top-box"><div><!-- Top Corners --></div></div>
+<div class="punwrap">
+
+<div id="brdmain">
+<pun_maint_main>
+</div>
+
+</div>
+<div class="end-box"><div><!-- Bottom Corners --></div></div>
+</div>
+
+</body>
+</html>
diff --git a/include/template/redirect.tpl b/include/template/redirect.tpl
new file mode 100644
index 0000000..31f5f19
--- /dev/null
+++ b/include/template/redirect.tpl
@@ -0,0 +1,25 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<pun_language>" lang="<pun_language>" dir="<pun_content_direction>">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<pun_head>
+</head>
+
+<body>
+
+<div id="punredirect" class="pun">
+<div class="top-box"><div><!-- Top Corners --></div></div>
+<div class="punwrap">
+
+<div id="brdmain">
+<pun_redir_main>
+</div>
+
+<pun_footer>
+
+</div>
+<div class="end-box"><div><!-- Bottom Corners --></div></div>
+</div>
+
+</body>
+</html>
diff --git a/include/user/index.html b/include/user/index.html
new file mode 100644
index 0000000..89337b2
--- /dev/null
+++ b/include/user/index.html
@@ -0,0 +1 @@
+<html><head><title>.</title></head><body>.</body></html>
diff --git a/include/utf8/index.html b/include/utf8/index.html
new file mode 100644
index 0000000..89337b2
--- /dev/null
+++ b/include/utf8/index.html
@@ -0,0 +1 @@
+<html><head><title>.</title></head><body>.</body></html>
diff --git a/include/utf8/mbstring/core.php b/include/utf8/mbstring/core.php
new file mode 100644
index 0000000..bea1c32
--- /dev/null
+++ b/include/utf8/mbstring/core.php
@@ -0,0 +1,144 @@
+<?php
+
+/**
+* @version $Id: core.php,v 1.5 2006/02/28 22:12:25 harryf Exp $
+* @package utf8
+* @subpackage strings
+*/
+
+// Define UTF8_CORE as required
+if (!defined('UTF8_CORE'))
+	define('UTF8_CORE', true);
+
+/**
+* Wrapper round mb_strlen
+* Assumes you have mb_internal_encoding to UTF-8 already
+* Note: this function does not count bad bytes in the string - these
+* are simply ignored
+* @param string UTF-8 string
+* @return int number of UTF-8 characters in string
+* @package utf8
+* @subpackage strings
+*/
+function utf8_strlen($str)
+{
+	return mb_strlen($str);
+}
+
+/**
+* Assumes mbstring internal encoding is set to UTF-8
+* Wrapper around mb_strpos
+* Find position of first occurrence of a string
+* @param string haystack
+* @param string needle (you should validate this with utf8_is_valid)
+* @param integer offset in characters (from left)
+* @return mixed integer position or FALSE on failure
+* @package utf8
+* @subpackage strings
+*/
+function utf8_strpos($str, $search, $offset = false)
+{
+	// Strip unvalid characters
+	$str = utf8_bad_strip($str);
+
+	if ($offset === false)
+		return mb_strpos($str, $search);
+	else
+		return mb_strpos($str, $search, $offset);
+}
+
+/**
+* Assumes mbstring internal encoding is set to UTF-8
+* Wrapper around mb_strrpos
+* Find position of last occurrence of a char in a string
+* @param string haystack
+* @param string needle (you should validate this with utf8_is_valid)
+* @param integer (optional) offset (from left)
+* @return mixed integer position or FALSE on failure
+* @package utf8
+* @subpackage strings
+*/
+function utf8_strrpos($str, $search, $offset = false)
+{
+	// Strip unvalid characters
+	$str = utf8_bad_strip($str);
+
+	if (!$offset)
+	{
+		// Emulate behaviour of strrpos rather than raising warning
+		if (empty($str))
+			return false;
+
+		return mb_strrpos($str, $search);
+	}
+	else
+	{
+		if (!is_int($offset))
+		{
+			trigger_error('utf8_strrpos expects parameter 3 to be long', E_USER_WARNING);
+			return false;
+		}
+
+		$str = mb_substr($str, $offset);
+
+		if (($pos = mb_strrpos($str, $search)) !== false)
+			return $pos + $offset;
+
+		return false;
+	}
+}
+
+/**
+* Assumes mbstring internal encoding is set to UTF-8
+* Wrapper around mb_substr
+* Return part of a string given character offset (and optionally length)
+* @param string
+* @param integer number of UTF-8 characters offset (from left)
+* @param integer (optional) length in UTF-8 characters from offset
+* @return mixed string or FALSE if failure
+* @package utf8
+* @subpackage strings
+*/
+function utf8_substr($str, $offset, $length = false)
+{
+	if ($length === false)
+		return mb_substr($str, $offset);
+	else
+		return mb_substr($str, $offset, $length);
+}
+
+/**
+* Assumes mbstring internal encoding is set to UTF-8
+* Wrapper around mb_strtolower
+* Make a string lowercase
+* Note: The concept of a characters "case" only exists is some alphabets
+* such as Latin, Greek, Cyrillic, Armenian and archaic Georgian - it does
+* not exist in the Chinese alphabet, for example. See Unicode Standard
+* Annex #21: Case Mappings
+* @param string
+* @return mixed either string in lowercase or FALSE is UTF-8 invalid
+* @package utf8
+* @subpackage strings
+*/
+function utf8_strtolower($str)
+{
+	return mb_strtolower($str);
+}
+
+/**
+* Assumes mbstring internal encoding is set to UTF-8
+* Wrapper around mb_strtoupper
+* Make a string uppercase
+* Note: The concept of a characters "case" only exists is some alphabets
+* such as Latin, Greek, Cyrillic, Armenian and archaic Georgian - it does
+* not exist in the Chinese alphabet, for example. See Unicode Standard
+* Annex #21: Case Mappings
+* @param string
+* @return mixed either string in lowercase or FALSE is UTF-8 invalid
+* @package utf8
+* @subpackage strings
+*/
+function utf8_strtoupper($str)
+{
+	return mb_strtoupper($str);
+}
diff --git a/include/utf8/mbstring/index.html b/include/utf8/mbstring/index.html
new file mode 100644
index 0000000..89337b2
--- /dev/null
+++ b/include/utf8/mbstring/index.html
@@ -0,0 +1 @@
+<html><head><title>.</title></head><body>.</body></html>
diff --git a/include/utf8/native/core.php b/include/utf8/native/core.php
new file mode 100644
index 0000000..58636f5
--- /dev/null
+++ b/include/utf8/native/core.php
@@ -0,0 +1,422 @@
+<?php
+
+/**
+* @version $Id: core.php,v 1.9 2007/08/12 01:11:33 harryf Exp $
+* @package utf8
+* @subpackage strings
+*/
+
+// Define UTF8_CORE as required
+if (!defined('UTF8_CORE'))
+	define('UTF8_CORE', true);
+
+/**
+* Unicode aware replacement for strlen(). Returns the number
+* of characters in the string (not the number of bytes), replacing
+* multibyte characters with a single byte equivalent
+* utf8_decode() converts characters that are not in ISO-8859-1
+* to '?', which, for the purpose of counting, is alright - It's
+* much faster than iconv_strlen
+* Note: this function does not count bad UTF-8 bytes in the string
+* - these are simply ignored
+* @author <chernyshevsky at hotmail dot com>
+* @link   http://www.php.net/manual/en/function.strlen.php
+* @link   http://www.php.net/manual/en/function.utf8-decode.php
+* @param string UTF-8 string
+* @return int number of UTF-8 characters in string
+* @package utf8
+* @subpackage strings
+*/
+function utf8_strlen($str)
+{
+	return strlen(utf8_decode($str));
+}
+
+/**
+* UTF-8 aware alternative to strpos
+* Find position of first occurrence of a string
+* Note: This will get alot slower if offset is used
+* Note: requires utf8_strlen amd utf8_substr to be loaded
+* @param string haystack
+* @param string needle (you should validate this with utf8_is_valid)
+* @param integer offset in characters (from left)
+* @return mixed integer position or FALSE on failure
+* @see http://www.php.net/strpos
+* @see utf8_strlen
+* @see utf8_substr
+* @package utf8
+* @subpackage strings
+*/
+function utf8_strpos($str, $needle, $offset = false)
+{
+	if ($offset === false)
+	{
+		$ar = explode($needle, $str, 2);
+
+		if (count($ar) > 1)
+			return utf8_strlen($ar[0]);
+
+		return false;
+	}
+	else
+	{
+		if (!is_int($offset))
+		{
+			trigger_error('utf8_strpos: Offset must be an integer', E_USER_ERROR);
+			return false;
+		}
+
+		$str = utf8_substr($str, $offset);
+
+		if (($pos = utf8_strpos($str, $needle)) !== false)
+			return $pos + $offset;
+
+		return false;
+	}
+}
+
+/**
+* UTF-8 aware alternative to strrpos
+* Find position of last occurrence of a char in a string
+* Note: This will get alot slower if offset is used
+* Note: requires utf8_substr and utf8_strlen to be loaded
+* @param string haystack
+* @param string needle (you should validate this with utf8_is_valid)
+* @param integer (optional) offset (from left)
+* @return mixed integer position or FALSE on failure
+* @see http://www.php.net/strrpos
+* @see utf8_substr
+* @see utf8_strlen
+* @package utf8
+* @subpackage strings
+*/
+function utf8_strrpos($str, $needle, $offset = false)
+{
+	if ($offset === false)
+	{
+		$ar = explode($needle, $str);
+
+		if (count($ar) > 1)
+		{
+			// Pop off the end of the string where the last match was made
+			array_pop($ar);
+			$str = join($needle, $ar);
+
+			return utf8_strlen($str);
+		}
+
+		return false;
+	}
+	else
+	{
+		if (!is_int($offset))
+		{
+			trigger_error('utf8_strrpos expects parameter 3 to be long', E_USER_WARNING);
+			return false;
+		}
+
+		$str = utf8_substr($str, $offset);
+
+		if (($pos = utf8_strrpos($str, $needle)) !== false)
+			return $pos + $offset;
+
+		return false;
+	}
+}
+
+/**
+* UTF-8 aware alternative to substr
+* Return part of a string given character offset (and optionally length)
+*
+* Note arguments: comparied to substr - if offset or length are
+* not integers, this version will not complain but rather massages them
+* into an integer.
+*
+* Note on returned values: substr documentation states false can be
+* returned in some cases (e.g. offset > string length)
+* mb_substr never returns false, it will return an empty string instead.
+* This adopts the mb_substr approach
+*
+* Note on implementation: PCRE only supports repetitions of less than
+* 65536, in order to accept up to MAXINT values for offset and length,
+* we'll repeat a group of 65535 characters when needed.
+*
+* Note on implementation: calculating the number of characters in the
+* string is a relatively expensive operation, so we only carry it out when
+* necessary. It isn't necessary for +ve offsets and no specified length
+*
+* @author Chris Smith<chris@jalakai.co.uk>
+* @param string
+* @param integer number of UTF-8 characters offset (from left)
+* @param integer (optional) length in UTF-8 characters from offset
+* @return mixed string or FALSE if failure
+* @package utf8
+* @subpackage strings
+*/
+function utf8_substr($str, $offset, $length = false)
+{
+	// Generates E_NOTICE for PHP4 objects, but not PHP5 objects
+	$str = (string) $str;
+	$offset = (int) $offset;
+
+	if ($length)
+		$length = (int) $length;
+
+	// Handle trivial cases
+	if ($length === 0)
+		return '';
+	if ($offset < 0 && $length < 0 && $length < $offset)
+		return '';
+
+	// Normalise negative offsets (we could use a tail
+	// anchored pattern, but they are horribly slow!)
+	if ($offset < 0)
+	{
+		// See notes
+		$strlen = utf8_strlen($str);
+		$offset = $strlen + $offset;
+
+		if ($offset < 0)
+			$offset = 0;
+	}
+
+	$Op = '';
+	$Lp = '';
+
+	// Establish a pattern for offset, a
+	// non-captured group equal in length to offset
+	if ($offset > 0)
+	{
+		$Ox = (int) ($offset / 65535);
+		$Oy = $offset % 65535;
+
+		if ($Ox)
+			$Op = '(?:.{65535}){'.$Ox.'}';
+
+		$Op = '^(?:'.$Op.'.{'.$Oy.'})';
+	}
+	else
+		$Op = '^';
+
+
+	// Establish a pattern for length
+	if (!$length)
+	{
+		// The rest of the string
+		$Lp = '(.*)$';
+	}
+	else
+	{
+		// See notes
+		if (!isset($strlen))
+			$strlen = strlen(utf8_decode($str));
+
+		// Another trivial case
+		if ($offset > $strlen)
+			return '';
+
+		if ($length > 0)
+		{
+			// Reduce any length that would go passed the end of the string
+			$length = min($strlen-$offset, $length);
+
+			$Lx = (int)( $length / 65535 );
+			$Ly = $length % 65535;
+
+			// Negative length requires a captured group of length characters
+			if ($Lx) $Lp = '(?:.{65535}){'.$Lx.'}';
+			$Lp = '('.$Lp.'.{'.$Ly.'})';
+		}
+		else if ($length < 0)
+		{
+
+			if ($length < ($offset - $strlen))
+				return '';
+
+			$Lx = (int)((-$length)/65535);
+			$Ly = (-$length)%65535;
+
+			// Negative length requires ... capture everything except a group of
+			// -length characters anchored at the tail-end of the string
+			if ($Lx)
+				$Lp = '(?:.{65535}){'.$Lx.'}';
+
+			$Lp = '(.*)(?:'.$Lp.'.{'.$Ly.'})$';
+		}
+	}
+
+	if (!preg_match('#'.$Op.$Lp.'#us', $str, $match))
+		return '';
+
+	return $match[1];
+}
+
+/**
+* UTF-8 aware alternative to strtolower
+* Make a string lowercase
+* Note: The concept of a characters "case" only exists is some alphabets
+* such as Latin, Greek, Cyrillic, Armenian and archaic Georgian - it does
+* not exist in the Chinese alphabet, for example. See Unicode Standard
+* Annex #21: Case Mappings
+* Note: requires utf8_to_unicode and utf8_from_unicode
+* @author Andreas Gohr <andi@splitbrain.org>
+* @param string
+* @return mixed either string in lowercase or FALSE is UTF-8 invalid
+* @see http://www.php.net/strtolower
+* @see utf8_to_unicode
+* @see utf8_from_unicode
+* @see http://www.unicode.org/reports/tr21/tr21-5.html
+* @see http://dev.splitbrain.org/view/darcs/dokuwiki/inc/utf8.php
+* @package utf8
+* @subpackage strings
+*/
+function utf8_strtolower($string)
+{
+	static $UTF8_UPPER_TO_LOWER = false;
+
+	if (!$UTF8_UPPER_TO_LOWER)
+	{
+		$UTF8_UPPER_TO_LOWER = array(
+			0x0041=>0x0061, 0x03A6=>0x03C6, 0x0162=>0x0163, 0x00C5=>0x00E5, 0x0042=>0x0062,
+			0x0139=>0x013A, 0x00C1=>0x00E1, 0x0141=>0x0142, 0x038E=>0x03CD, 0x0100=>0x0101,
+			0x0490=>0x0491, 0x0394=>0x03B4, 0x015A=>0x015B, 0x0044=>0x0064, 0x0393=>0x03B3,
+			0x00D4=>0x00F4, 0x042A=>0x044A, 0x0419=>0x0439, 0x0112=>0x0113, 0x041C=>0x043C,
+			0x015E=>0x015F, 0x0143=>0x0144, 0x00CE=>0x00EE, 0x040E=>0x045E, 0x042F=>0x044F,
+			0x039A=>0x03BA, 0x0154=>0x0155, 0x0049=>0x0069, 0x0053=>0x0073, 0x1E1E=>0x1E1F,
+			0x0134=>0x0135, 0x0427=>0x0447, 0x03A0=>0x03C0, 0x0418=>0x0438, 0x00D3=>0x00F3,
+			0x0420=>0x0440, 0x0404=>0x0454, 0x0415=>0x0435, 0x0429=>0x0449, 0x014A=>0x014B,
+			0x0411=>0x0431, 0x0409=>0x0459, 0x1E02=>0x1E03, 0x00D6=>0x00F6, 0x00D9=>0x00F9,
+			0x004E=>0x006E, 0x0401=>0x0451, 0x03A4=>0x03C4, 0x0423=>0x0443, 0x015C=>0x015D,
+			0x0403=>0x0453, 0x03A8=>0x03C8, 0x0158=>0x0159, 0x0047=>0x0067, 0x00C4=>0x00E4,
+			0x0386=>0x03AC, 0x0389=>0x03AE, 0x0166=>0x0167, 0x039E=>0x03BE, 0x0164=>0x0165,
+			0x0116=>0x0117, 0x0108=>0x0109, 0x0056=>0x0076, 0x00DE=>0x00FE, 0x0156=>0x0157,
+			0x00DA=>0x00FA, 0x1E60=>0x1E61, 0x1E82=>0x1E83, 0x00C2=>0x00E2, 0x0118=>0x0119,
+			0x0145=>0x0146, 0x0050=>0x0070, 0x0150=>0x0151, 0x042E=>0x044E, 0x0128=>0x0129,
+			0x03A7=>0x03C7, 0x013D=>0x013E, 0x0422=>0x0442, 0x005A=>0x007A, 0x0428=>0x0448,
+			0x03A1=>0x03C1, 0x1E80=>0x1E81, 0x016C=>0x016D, 0x00D5=>0x00F5, 0x0055=>0x0075,
+			0x0176=>0x0177, 0x00DC=>0x00FC, 0x1E56=>0x1E57, 0x03A3=>0x03C3, 0x041A=>0x043A,
+			0x004D=>0x006D, 0x016A=>0x016B, 0x0170=>0x0171, 0x0424=>0x0444, 0x00CC=>0x00EC,
+			0x0168=>0x0169, 0x039F=>0x03BF, 0x004B=>0x006B, 0x00D2=>0x00F2, 0x00C0=>0x00E0,
+			0x0414=>0x0434, 0x03A9=>0x03C9, 0x1E6A=>0x1E6B, 0x00C3=>0x00E3, 0x042D=>0x044D,
+			0x0416=>0x0436, 0x01A0=>0x01A1, 0x010C=>0x010D, 0x011C=>0x011D, 0x00D0=>0x00F0,
+			0x013B=>0x013C, 0x040F=>0x045F, 0x040A=>0x045A, 0x00C8=>0x00E8, 0x03A5=>0x03C5,
+			0x0046=>0x0066, 0x00DD=>0x00FD, 0x0043=>0x0063, 0x021A=>0x021B, 0x00CA=>0x00EA,
+			0x0399=>0x03B9, 0x0179=>0x017A, 0x00CF=>0x00EF, 0x01AF=>0x01B0, 0x0045=>0x0065,
+			0x039B=>0x03BB, 0x0398=>0x03B8, 0x039C=>0x03BC, 0x040C=>0x045C, 0x041F=>0x043F,
+			0x042C=>0x044C, 0x00DE=>0x00FE, 0x00D0=>0x00F0, 0x1EF2=>0x1EF3, 0x0048=>0x0068,
+			0x00CB=>0x00EB, 0x0110=>0x0111, 0x0413=>0x0433, 0x012E=>0x012F, 0x00C6=>0x00E6,
+			0x0058=>0x0078, 0x0160=>0x0161, 0x016E=>0x016F, 0x0391=>0x03B1, 0x0407=>0x0457,
+			0x0172=>0x0173, 0x0178=>0x00FF, 0x004F=>0x006F, 0x041B=>0x043B, 0x0395=>0x03B5,
+			0x0425=>0x0445, 0x0120=>0x0121, 0x017D=>0x017E, 0x017B=>0x017C, 0x0396=>0x03B6,
+			0x0392=>0x03B2, 0x0388=>0x03AD, 0x1E84=>0x1E85, 0x0174=>0x0175, 0x0051=>0x0071,
+			0x0417=>0x0437, 0x1E0A=>0x1E0B, 0x0147=>0x0148, 0x0104=>0x0105, 0x0408=>0x0458,
+			0x014C=>0x014D, 0x00CD=>0x00ED, 0x0059=>0x0079, 0x010A=>0x010B, 0x038F=>0x03CE,
+			0x0052=>0x0072, 0x0410=>0x0430, 0x0405=>0x0455, 0x0402=>0x0452, 0x0126=>0x0127,
+			0x0136=>0x0137, 0x012A=>0x012B, 0x038A=>0x03AF, 0x042B=>0x044B, 0x004C=>0x006C,
+			0x0397=>0x03B7, 0x0124=>0x0125, 0x0218=>0x0219, 0x00DB=>0x00FB, 0x011E=>0x011F,
+			0x041E=>0x043E, 0x1E40=>0x1E41, 0x039D=>0x03BD, 0x0106=>0x0107, 0x03AB=>0x03CB,
+			0x0426=>0x0446, 0x00DE=>0x00FE, 0x00C7=>0x00E7, 0x03AA=>0x03CA, 0x0421=>0x0441,
+			0x0412=>0x0432, 0x010E=>0x010F, 0x00D8=>0x00F8, 0x0057=>0x0077, 0x011A=>0x011B,
+			0x0054=>0x0074, 0x004A=>0x006A, 0x040B=>0x045B, 0x0406=>0x0456, 0x0102=>0x0103,
+			0x039B=>0x03BB, 0x00D1=>0x00F1, 0x041D=>0x043D, 0x038C=>0x03CC, 0x00C9=>0x00E9,
+			0x00D0=>0x00F0, 0x0407=>0x0457, 0x0122=>0x0123);
+	}
+
+	$uni = utf8_to_unicode($string);
+
+	if (!$uni)
+		return false;
+
+	$cnt = count($uni);
+
+	for ($i=0; $i < $cnt; $i++)
+		if (isset($UTF8_UPPER_TO_LOWER[$uni[$i]]))
+			$uni[$i] = $UTF8_UPPER_TO_LOWER[$uni[$i]];
+
+	return utf8_from_unicode($uni);
+}
+
+/**
+* UTF-8 aware alternative to strtoupper
+* Make a string uppercase
+* Note: The concept of a characters "case" only exists is some alphabets
+* such as Latin, Greek, Cyrillic, Armenian and archaic Georgian - it does
+* not exist in the Chinese alphabet, for example. See Unicode Standard
+* Annex #21: Case Mappings
+* Note: requires utf8_to_unicode and utf8_from_unicode
+* @author Andreas Gohr <andi@splitbrain.org>
+* @param string
+* @return mixed either string in lowercase or FALSE is UTF-8 invalid
+* @see http://www.php.net/strtoupper
+* @see utf8_to_unicode
+* @see utf8_from_unicode
+* @see http://www.unicode.org/reports/tr21/tr21-5.html
+* @see http://dev.splitbrain.org/view/darcs/dokuwiki/inc/utf8.php
+* @package utf8
+* @subpackage strings
+*/
+function utf8_strtoupper($string)
+{
+	static $UTF8_LOWER_TO_UPPER = false;
+
+	if (!$UTF8_LOWER_TO_UPPER)
+	{
+		$UTF8_LOWER_TO_UPPER = array(
+			0x0061=>0x0041, 0x03C6=>0x03A6, 0x0163=>0x0162, 0x00E5=>0x00C5, 0x0062=>0x0042,
+			0x013A=>0x0139, 0x00E1=>0x00C1, 0x0142=>0x0141, 0x03CD=>0x038E, 0x0101=>0x0100,
+			0x0491=>0x0490, 0x03B4=>0x0394, 0x015B=>0x015A, 0x0064=>0x0044, 0x03B3=>0x0393,
+			0x00F4=>0x00D4, 0x044A=>0x042A, 0x0439=>0x0419, 0x0113=>0x0112, 0x043C=>0x041C,
+			0x015F=>0x015E, 0x0144=>0x0143, 0x00EE=>0x00CE, 0x045E=>0x040E, 0x044F=>0x042F,
+			0x03BA=>0x039A, 0x0155=>0x0154, 0x0069=>0x0049, 0x0073=>0x0053, 0x1E1F=>0x1E1E,
+			0x0135=>0x0134, 0x0447=>0x0427, 0x03C0=>0x03A0, 0x0438=>0x0418, 0x00F3=>0x00D3,
+			0x0440=>0x0420, 0x0454=>0x0404, 0x0435=>0x0415, 0x0449=>0x0429, 0x014B=>0x014A,
+			0x0431=>0x0411, 0x0459=>0x0409, 0x1E03=>0x1E02, 0x00F6=>0x00D6, 0x00F9=>0x00D9,
+			0x006E=>0x004E, 0x0451=>0x0401, 0x03C4=>0x03A4, 0x0443=>0x0423, 0x015D=>0x015C,
+			0x0453=>0x0403, 0x03C8=>0x03A8, 0x0159=>0x0158, 0x0067=>0x0047, 0x00E4=>0x00C4,
+			0x03AC=>0x0386, 0x03AE=>0x0389, 0x0167=>0x0166, 0x03BE=>0x039E, 0x0165=>0x0164,
+			0x0117=>0x0116, 0x0109=>0x0108, 0x0076=>0x0056, 0x00FE=>0x00DE, 0x0157=>0x0156,
+			0x00FA=>0x00DA, 0x1E61=>0x1E60, 0x1E83=>0x1E82, 0x00E2=>0x00C2, 0x0119=>0x0118,
+			0x0146=>0x0145, 0x0070=>0x0050, 0x0151=>0x0150, 0x044E=>0x042E, 0x0129=>0x0128,
+			0x03C7=>0x03A7, 0x013E=>0x013D, 0x0442=>0x0422, 0x007A=>0x005A, 0x0448=>0x0428,
+			0x03C1=>0x03A1, 0x1E81=>0x1E80, 0x016D=>0x016C, 0x00F5=>0x00D5, 0x0075=>0x0055,
+			0x0177=>0x0176, 0x00FC=>0x00DC, 0x1E57=>0x1E56, 0x03C3=>0x03A3, 0x043A=>0x041A,
+			0x006D=>0x004D, 0x016B=>0x016A, 0x0171=>0x0170, 0x0444=>0x0424, 0x00EC=>0x00CC,
+			0x0169=>0x0168, 0x03BF=>0x039F, 0x006B=>0x004B, 0x00F2=>0x00D2, 0x00E0=>0x00C0,
+			0x0434=>0x0414, 0x03C9=>0x03A9, 0x1E6B=>0x1E6A, 0x00E3=>0x00C3, 0x044D=>0x042D,
+			0x0436=>0x0416, 0x01A1=>0x01A0, 0x010D=>0x010C, 0x011D=>0x011C, 0x00F0=>0x00D0,
+			0x013C=>0x013B, 0x045F=>0x040F, 0x045A=>0x040A, 0x00E8=>0x00C8, 0x03C5=>0x03A5,
+			0x0066=>0x0046, 0x00FD=>0x00DD, 0x0063=>0x0043, 0x021B=>0x021A, 0x00EA=>0x00CA,
+			0x03B9=>0x0399, 0x017A=>0x0179, 0x00EF=>0x00CF, 0x01B0=>0x01AF, 0x0065=>0x0045,
+			0x03BB=>0x039B, 0x03B8=>0x0398, 0x03BC=>0x039C, 0x045C=>0x040C, 0x043F=>0x041F,
+			0x044C=>0x042C, 0x00FE=>0x00DE, 0x00F0=>0x00D0, 0x1EF3=>0x1EF2, 0x0068=>0x0048,
+			0x00EB=>0x00CB, 0x0111=>0x0110, 0x0433=>0x0413, 0x012F=>0x012E, 0x00E6=>0x00C6,
+			0x0078=>0x0058, 0x0161=>0x0160, 0x016F=>0x016E, 0x03B1=>0x0391, 0x0457=>0x0407,
+			0x0173=>0x0172, 0x00FF=>0x0178, 0x006F=>0x004F, 0x043B=>0x041B, 0x03B5=>0x0395,
+			0x0445=>0x0425, 0x0121=>0x0120, 0x017E=>0x017D, 0x017C=>0x017B, 0x03B6=>0x0396,
+			0x03B2=>0x0392, 0x03AD=>0x0388, 0x1E85=>0x1E84, 0x0175=>0x0174, 0x0071=>0x0051,
+			0x0437=>0x0417, 0x1E0B=>0x1E0A, 0x0148=>0x0147, 0x0105=>0x0104, 0x0458=>0x0408,
+			0x014D=>0x014C, 0x00ED=>0x00CD, 0x0079=>0x0059, 0x010B=>0x010A, 0x03CE=>0x038F,
+			0x0072=>0x0052, 0x0430=>0x0410, 0x0455=>0x0405, 0x0452=>0x0402, 0x0127=>0x0126,
+			0x0137=>0x0136, 0x012B=>0x012A, 0x03AF=>0x038A, 0x044B=>0x042B, 0x006C=>0x004C,
+			0x03B7=>0x0397, 0x0125=>0x0124, 0x0219=>0x0218, 0x00FB=>0x00DB, 0x011F=>0x011E,
+			0x043E=>0x041E, 0x1E41=>0x1E40, 0x03BD=>0x039D, 0x0107=>0x0106, 0x03CB=>0x03AB,
+			0x0446=>0x0426, 0x00FE=>0x00DE, 0x00E7=>0x00C7, 0x03CA=>0x03AA, 0x0441=>0x0421,
+			0x0432=>0x0412, 0x010F=>0x010E, 0x00F8=>0x00D8, 0x0077=>0x0057, 0x011B=>0x011A,
+			0x0074=>0x0054, 0x006A=>0x004A, 0x045B=>0x040B, 0x0456=>0x0406, 0x0103=>0x0102,
+			0x03BB=>0x039B, 0x00F1=>0x00D1, 0x043D=>0x041D, 0x03CC=>0x038C, 0x00E9=>0x00C9,
+			0x00F0=>0x00D0, 0x0457=>0x0407, 0x0123=>0x0122);
+	}
+
+	$uni = utf8_to_unicode($string);
+
+	if (!$uni)
+		return false;
+
+	$cnt = count($uni);
+
+	for ($i=0; $i < $cnt; $i++)
+		if(isset($UTF8_LOWER_TO_UPPER[$uni[$i]]))
+			$uni[$i] = $UTF8_LOWER_TO_UPPER[$uni[$i]];
+
+	return utf8_from_unicode($uni);
+}
diff --git a/include/utf8/native/index.html b/include/utf8/native/index.html
new file mode 100644
index 0000000..89337b2
--- /dev/null
+++ b/include/utf8/native/index.html
@@ -0,0 +1 @@
+<html><head><title>.</title></head><body>.</body></html>
diff --git a/include/utf8/ord.php b/include/utf8/ord.php
new file mode 100644
index 0000000..a333f96
--- /dev/null
+++ b/include/utf8/ord.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+* @version $Id: ord.php,v 1.4 2006/09/11 15:22:54 harryf Exp $
+* @package utf8
+* @subpackage strings
+*/
+
+/**
+* UTF-8 aware alternative to ord
+* Returns the unicode ordinal for a character
+* @param string UTF-8 encoded character
+* @return int unicode ordinal for the character
+* @see http://www.php.net/ord
+* @see http://www.php.net/manual/en/function.ord.php#46267
+*/
+function utf8_ord($chr)
+{
+	$ord0 = ord($chr);
+
+	if ($ord0 >= 0 && $ord0 <= 127)
+		return $ord0;
+
+	if (!isset($chr{1}))
+	{
+		trigger_error('Short sequence - at least 2 bytes expected, only 1 seen');
+		return false;
+	}
+
+	$ord1 = ord($chr{1});
+	if ($ord0 >= 192 && $ord0 <= 223)
+		return ($ord0 - 192) * 64 + ($ord1 - 128);
+
+	if (!isset($chr{2}))
+	{
+		trigger_error('Short sequence - at least 3 bytes expected, only 2 seen');
+		return false;
+	}
+
+	$ord2 = ord($chr{2});
+	if ($ord0 >= 224 && $ord0 <= 239)
+		return ($ord0-224)*4096 + ($ord1-128)*64 + ($ord2-128);
+
+	if (!isset($chr{3}))
+	{
+		trigger_error('Short sequence - at least 4 bytes expected, only 3 seen');
+		return false;
+	}
+
+	$ord3 = ord($chr{3});
+	if ($ord0>=240 && $ord0<=247)
+		return ($ord0-240)*262144 + ($ord1-128)*4096 + ($ord2-128)*64 + ($ord3-128);
+
+	if (!isset($chr{4}))
+	{
+		trigger_error('Short sequence - at least 5 bytes expected, only 4 seen');
+		return false;
+	}
+
+	$ord4 = ord($chr{4});
+	if ($ord0>=248 && $ord0<=251)
+		return ($ord0-248)*16777216 + ($ord1-128)*262144 + ($ord2-128)*4096 + ($ord3-128)*64 + ($ord4-128);
+
+	if (!isset($chr{5}))
+	{
+		trigger_error('Short sequence - at least 6 bytes expected, only 5 seen');
+		return false;
+	}
+
+	if ($ord0>=252 && $ord0<=253)
+		return ($ord0-252) * 1073741824 + ($ord1-128)*16777216 + ($ord2-128)*262144 + ($ord3-128)*4096  + ($ord4-128)*64 + (ord($c{5})-128);
+
+	if ($ord0 >= 254 && $ord0 <= 255)
+	{
+		trigger_error('Invalid UTF-8 with surrogate ordinal '.$ord0);
+		return false;
+	}
+}
diff --git a/include/utf8/str_ireplace.php b/include/utf8/str_ireplace.php
new file mode 100644
index 0000000..7257b0a
--- /dev/null
+++ b/include/utf8/str_ireplace.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+* @version $Id: str_ireplace.php,v 1.2 2007/08/12 01:20:46 harryf Exp $
+* @package utf8
+* @subpackage strings
+*/
+
+/**
+* UTF-8 aware alternative to str_ireplace
+* Case-insensitive version of str_replace
+* Note: requires utf8_strtolower
+* Note: it's not fast and gets slower if $search / $replace is array
+* Notes: it's based on the assumption that the lower and uppercase
+* versions of a UTF-8 character will have the same length in bytes
+* which is currently true given the hash table to strtolower
+* @param string
+* @return string
+* @see http://www.php.net/str_ireplace
+* @see utf8_strtolower
+* @package utf8
+* @subpackage strings
+*/
+function utf8_ireplace($search, $replace, $str, $count=null)
+{
+	if (!is_array($search))
+	{
+		$slen = strlen($search);
+
+		if ($slen == 0)
+			return $str;
+
+		$lendif = strlen($replace) - strlen($search);
+		$search = utf8_strtolower($search);
+
+		$search = preg_quote($search);
+		$lstr = utf8_strtolower($str);
+		$i = 0;
+		$matched = 0;
+
+		while (preg_match('/(.*)'.$search.'/Us', $lstr, $matches))
+		{
+			if ($i === $count)
+				break;
+
+			$mlen = strlen($matches[0]);
+			$lstr = substr($lstr, $mlen);
+			$str = substr_replace($str, $replace, $matched+strlen($matches[1]), $slen);
+			$matched += $mlen + $lendif;
+			$i++;
+		}
+
+		return $str;
+	}
+	else
+	{
+		foreach (array_keys($search) as $k)
+		{
+			if (is_array($replace))
+			{
+				if (array_key_exists($k, $replace))
+					$str = utf8_ireplace($search[$k], $replace[$k], $str, $count);
+				else
+					$str = utf8_ireplace($search[$k], '', $str, $count);
+			}
+			else
+				$str = utf8_ireplace($search[$k], $replace, $str, $count);
+		}
+
+		return $str;
+	}
+}
diff --git a/include/utf8/str_pad.php b/include/utf8/str_pad.php
new file mode 100644
index 0000000..93a559a
--- /dev/null
+++ b/include/utf8/str_pad.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+* @version $Id: str_pad.php,v 1.1 2006/09/03 09:25:13 harryf Exp $
+* @package utf8
+* @subpackage strings
+*/
+
+/**
+* Replacement for str_pad. $padStr may contain multi-byte characters.
+*
+* @author Oliver Saunders <oliver (a) osinternetservices.com>
+* @param string $input
+* @param int $length
+* @param string $padStr
+* @param int $type ( same constants as str_pad )
+* @return string
+* @see http://www.php.net/str_pad
+* @see utf8_substr
+* @package utf8
+* @subpackage strings
+*/
+function utf8_str_pad($input, $length, $padStr=' ', $type=STR_PAD_RIGHT)
+{
+	$inputLen = utf8_strlen($input);
+	if ($length <= $inputLen)
+		return $input;
+
+	$padStrLen = utf8_strlen($padStr);
+	$padLen = $length - $inputLen;
+
+	if ($type == STR_PAD_RIGHT)
+	{
+		$repeatTimes = ceil($padLen / $padStrLen);
+		return utf8_substr($input.str_repeat($padStr, $repeatTimes), 0, $length);
+	}
+
+	if ($type == STR_PAD_LEFT)
+	{
+		$repeatTimes = ceil($padLen / $padStrLen);
+		return utf8_substr(str_repeat($padStr, $repeatTimes), 0, floor($padLen)).$input;
+	}
+
+	if ($type == STR_PAD_BOTH)
+	{
+		$padLen /= 2;
+		$padAmountLeft = floor($padLen);
+		$padAmountRight = ceil($padLen);
+		$repeatTimesLeft = ceil($padAmountLeft / $padStrLen);
+		$repeatTimesRight = ceil($padAmountRight / $padStrLen);
+
+		$paddingLeft = utf8_substr(str_repeat($padStr, $repeatTimesLeft), 0, $padAmountLeft);
+		$paddingRight = utf8_substr(str_repeat($padStr, $repeatTimesRight), 0, $padAmountLeft);
+
+		return $paddingLeft.$input.$paddingRight;
+	}
+
+	trigger_error('utf8_str_pad: Unknown padding type ('.$type.')', E_USER_ERROR);
+}
diff --git a/include/utf8/str_split.php b/include/utf8/str_split.php
new file mode 100644
index 0000000..15bc215
--- /dev/null
+++ b/include/utf8/str_split.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+* @version $Id: str_split.php,v 1.1 2006/02/25 13:50:17 harryf Exp $
+* @package utf8
+* @subpackage strings
+*/
+
+/**
+* UTF-8 aware alternative to str_split
+* Convert a string to an array
+* Note: requires utf8_strlen to be loaded
+* @param string UTF-8 encoded
+* @param int number to characters to split string by
+* @return string characters in string reverses
+* @see http://www.php.net/str_split
+* @see utf8_strlen
+* @package utf8
+* @subpackage strings
+*/
+function utf8_str_split($str, $split_len=1)
+{
+	if (!preg_match('/^[0-9]+$/',$split_len) || $split_len < 1)
+		return false;
+
+	$len = utf8_strlen($str);
+	if ($len <= $split_len)
+		return array($str);
+
+	preg_match_all('/.{'.$split_len.'}|[^\x00]{1,'.$split_len.'}$/us', $str, $ar);
+
+	return $ar[0];
+}
diff --git a/include/utf8/strcasecmp.php b/include/utf8/strcasecmp.php
new file mode 100644
index 0000000..423f443
--- /dev/null
+++ b/include/utf8/strcasecmp.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+* @version $Id: strcasecmp.php,v 1.1 2006/02/25 13:50:17 harryf Exp $
+* @package utf8
+* @subpackage strings
+*/
+
+/**
+* UTF-8 aware alternative to strcasecmp
+* A case insensivite string comparison
+* Note: requires utf8_strtolower
+* @param string
+* @param string
+* @return int
+* @see http://www.php.net/strcasecmp
+* @see utf8_strtolower
+* @package utf8
+* @subpackage strings
+*/
+function utf8_strcasecmp($strX, $strY)
+{
+	$strX = utf8_strtolower($strX);
+	$strY = utf8_strtolower($strY);
+
+	return strcmp($strX, $strY);
+}
diff --git a/include/utf8/strcspn.php b/include/utf8/strcspn.php
new file mode 100644
index 0000000..b05e327
--- /dev/null
+++ b/include/utf8/strcspn.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+* @version $Id: strcspn.php,v 1.1 2006/02/25 13:50:17 harryf Exp $
+* @package utf8
+* @subpackage strings
+*/
+
+/**
+* UTF-8 aware alternative to strcspn
+* Find length of initial segment not matching mask
+* Note: requires utf8_strlen and utf8_substr (if start, length are used)
+* @param string
+* @return int
+* @see http://www.php.net/strcspn
+* @see utf8_strlen
+* @package utf8
+* @subpackage strings
+*/
+function utf8_strcspn($str, $mask, $start=null, $length=null)
+{
+	if (empty($mask) || strlen($mask) == 0)
+		return null;
+
+	$mask = preg_replace('!([\\\\\\-\\]\\[/^])!','\\\${1}', $mask);
+
+	if (!is_null($start) || !is_null($length))
+		$str = utf8_substr($str, $start, $length);
+
+	preg_match('/^[^'.$mask.']+/u', $str, $matches);
+
+	if (isset($matches[0]))
+		return utf8_strlen($matches[0]);
+
+	return 0;
+}
diff --git a/include/utf8/stristr.php b/include/utf8/stristr.php
new file mode 100644
index 0000000..fb9e6a5
--- /dev/null
+++ b/include/utf8/stristr.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+* @version $Id: stristr.php,v 1.1 2006/02/25 13:50:17 harryf Exp $
+* @package utf8
+* @subpackage strings
+*/
+
+/**
+* UTF-8 aware alternative to stristr
+* Find first occurrence of a string using case insensitive comparison
+* Note: requires utf8_strtolower
+* @param string
+* @param string
+* @return int
+* @see http://www.php.net/strcasecmp
+* @see utf8_strtolower
+* @package utf8
+* @subpackage strings
+*/
+function utf8_stristr($str, $search)
+{
+	if (strlen($search) == 0)
+		return $str;
+
+	$lstr = utf8_strtolower($str);
+	$lsearch = utf8_strtolower($search);
+	preg_match('/^(.*)'.preg_quote($lsearch).'/Us', $lstr, $matches);
+
+	if (count($matches) == 2)
+		return substr($str, strlen($matches[1]));
+
+	return false;
+}
diff --git a/include/utf8/strrev.php b/include/utf8/strrev.php
new file mode 100644
index 0000000..ae9c32b
--- /dev/null
+++ b/include/utf8/strrev.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+* @version $Id: strrev.php,v 1.1 2006/02/25 13:50:17 harryf Exp $
+* @package utf8
+* @subpackage strings
+*/
+
+/**
+* UTF-8 aware alternative to strrev
+* Reverse a string
+* @param string UTF-8 encoded
+* @return string characters in string reverses
+* @see http://www.php.net/strrev
+* @package utf8
+* @subpackage strings
+*/
+function utf8_strrev($str)
+{
+	preg_match_all('/./us', $str, $ar);
+	return implode(array_reverse($ar[0]));
+}
diff --git a/include/utf8/strspn.php b/include/utf8/strspn.php
new file mode 100644
index 0000000..49d300a
--- /dev/null
+++ b/include/utf8/strspn.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+* @version $Id: strspn.php,v 1.1 2006/02/25 13:50:17 harryf Exp $
+* @package utf8
+* @subpackage strings
+*/
+
+/**
+* UTF-8 aware alternative to strspn
+* Find length of initial segment matching mask
+* Note: requires utf8_strlen and utf8_substr (if start, length are used)
+* @param string
+* @return int
+* @see http://www.php.net/strspn
+* @package utf8
+* @subpackage strings
+*/
+function utf8_strspn($str, $mask, $start=null, $length=null)
+{
+	$mask = preg_replace('!([\\\\\\-\\]\\[/^])!', '\\\${1}', $mask);
+
+	if (!is_null($start)|| !is_null($length))
+		$str = utf8_substr($str, $start, $length);
+
+	preg_match('/^['.$mask.']+/u', $str, $matches);
+
+	if (isset($matches[0]))
+		return utf8_strlen($matches[0]);
+
+	return 0;
+}
diff --git a/include/utf8/substr_replace.php b/include/utf8/substr_replace.php
new file mode 100644
index 0000000..20a43b5
--- /dev/null
+++ b/include/utf8/substr_replace.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+* @version $Id: substr_replace.php,v 1.1 2006/02/25 13:50:17 harryf Exp $
+* @package utf8
+* @subpackage strings
+*/
+
+/**
+* UTF-8 aware substr_replace.
+* Note: requires utf8_substr to be loaded
+* @see http://www.php.net/substr_replace
+* @see utf8_strlen
+* @see utf8_substr
+*/
+function utf8_substr_replace($str, $repl, $start , $length=null)
+{
+	preg_match_all('/./us', $str, $ar);
+	preg_match_all('/./us', $repl, $rar);
+
+	if(is_null($length))
+		$length = utf8_strlen($str);
+
+	array_splice($ar[0], $start, $length, $rar[0]);
+
+	return implode($ar[0]);
+}
diff --git a/include/utf8/trim.php b/include/utf8/trim.php
new file mode 100644
index 0000000..3d22840
--- /dev/null
+++ b/include/utf8/trim.php
@@ -0,0 +1,74 @@
+<?php
+
+/**
+* @version $Id: trim.php,v 1.1 2006/02/25 13:50:17 harryf Exp $
+* @package utf8
+* @subpackage strings
+*/
+
+/**
+* UTF-8 aware replacement for ltrim()
+* Note: you only need to use this if you are supplying the charlist
+* optional arg and it contains UTF-8 characters. Otherwise ltrim will
+* work normally on a UTF-8 string
+* @author Andreas Gohr <andi@splitbrain.org>
+* @see http://www.php.net/ltrim
+* @see http://dev.splitbrain.org/view/darcs/dokuwiki/inc/utf8.php
+* @return string
+* @package utf8
+* @subpackage strings
+*/
+function utf8_ltrim( $str, $charlist=false)
+{
+	if($charlist === false)
+		return ltrim($str);
+
+	// Quote charlist for use in a characterclass
+	$charlist = preg_replace('!([\\\\\\-\\]\\[/^])!', '\\\${1}', $charlist);
+
+	return preg_replace('/^['.$charlist.']+/u', '', $str);
+}
+
+/**
+* UTF-8 aware replacement for rtrim()
+* Note: you only need to use this if you are supplying the charlist
+* optional arg and it contains UTF-8 characters. Otherwise rtrim will
+* work normally on a UTF-8 string
+* @author Andreas Gohr <andi@splitbrain.org>
+* @see http://www.php.net/rtrim
+* @see http://dev.splitbrain.org/view/darcs/dokuwiki/inc/utf8.php
+* @return string
+* @package utf8
+* @subpackage strings
+*/
+function utf8_rtrim($str, $charlist=false)
+{
+	if($charlist === false)
+		return rtrim($str);
+
+	// Quote charlist for use in a characterclass
+	$charlist = preg_replace('!([\\\\\\-\\]\\[/^])!', '\\\${1}', $charlist);
+
+	return preg_replace('/['.$charlist.']+$/u', '', $str);
+}
+
+//---------------------------------------------------------------
+/**
+* UTF-8 aware replacement for trim()
+* Note: you only need to use this if you are supplying the charlist
+* optional arg and it contains UTF-8 characters. Otherwise trim will
+* work normally on a UTF-8 string
+* @author Andreas Gohr <andi@splitbrain.org>
+* @see http://www.php.net/trim
+* @see http://dev.splitbrain.org/view/darcs/dokuwiki/inc/utf8.php
+* @return string
+* @package utf8
+* @subpackage strings
+*/
+function utf8_trim( $str, $charlist=false)
+{
+	if($charlist === false)
+		return trim($str);
+
+	return utf8_ltrim(utf8_rtrim($str, $charlist), $charlist);
+}
diff --git a/include/utf8/ucfirst.php b/include/utf8/ucfirst.php
new file mode 100644
index 0000000..efee55d
--- /dev/null
+++ b/include/utf8/ucfirst.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+* @version $Id: ucfirst.php,v 1.1 2006/02/25 13:50:17 harryf Exp $
+* @package utf8
+* @subpackage strings
+*/
+
+/**
+* UTF-8 aware alternative to ucfirst
+* Make a string's first character uppercase
+* Note: requires utf8_strtoupper
+* @param string
+* @return string with first character as upper case (if applicable)
+* @see http://www.php.net/ucfirst
+* @see utf8_strtoupper
+* @package utf8
+* @subpackage strings
+*/
+function utf8_ucfirst($str)
+{
+	switch (utf8_strlen($str))
+	{
+		case 0:
+			return '';
+			break;
+		case 1:
+			return utf8_strtoupper($str);
+			break;
+		default:
+			preg_match('/^(.{1})(.*)$/us', $str, $matches);
+			return utf8_strtoupper($matches[1]).$matches[2];
+			break;
+	}
+}
diff --git a/include/utf8/ucwords.php b/include/utf8/ucwords.php
new file mode 100644
index 0000000..e985cee
--- /dev/null
+++ b/include/utf8/ucwords.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+* @version $Id: ucwords.php,v 1.1 2006/02/25 13:50:17 harryf Exp $
+* @package utf8
+* @subpackage strings
+*/
+
+/**
+* UTF-8 aware alternative to ucwords
+* Uppercase the first character of each word in a string
+* Note: requires utf8_substr_replace and utf8_strtoupper
+* @param string
+* @return string with first char of each word uppercase
+* @see http://www.php.net/ucwords
+* @package utf8
+* @subpackage strings
+*/
+function utf8_ucwords($str)
+{
+	// Note: [\x0c\x09\x0b\x0a\x0d\x20] matches;
+	// Form feeds, horizontal tabs, vertical tabs, linefeeds and carriage returns
+	// This corresponds to the definition of a "word" defined at http://www.php.net/ucwords
+	$pattern = '/(^|([\x0c\x09\x0b\x0a\x0d\x20]+))([^\x0c\x09\x0b\x0a\x0d\x20]{1})[^\x0c\x09\x0b\x0a\x0d\x20]*/u';
+
+	return preg_replace_callback($pattern, 'utf8_ucwords_callback', $str);
+}
+
+/**
+* Callback function for preg_replace_callback call in utf8_ucwords
+* You don't need to call this yourself
+* @param array of matches corresponding to a single word
+* @return string with first char of the word in uppercase
+* @see utf8_ucwords
+* @see utf8_strtoupper
+* @package utf8
+* @subpackage strings
+*/
+function utf8_ucwords_callback($matches)
+{
+	$leadingws = $matches[2];
+	$ucfirst = utf8_strtoupper($matches[3]);
+	$ucword = utf8_substr_replace(ltrim($matches[0]), $ucfirst, 0, 1);
+
+	return $leadingws.$ucword;
+}
diff --git a/include/utf8/utf8.php b/include/utf8/utf8.php
new file mode 100644
index 0000000..661b2d7
--- /dev/null
+++ b/include/utf8/utf8.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+* This is the dynamic loader for the library. It checks whether you have
+* the mbstring extension available and includes relevant files
+* on that basis, falling back to the native (as in written in PHP) version
+* if mbstring is unavailabe.
+*
+* It's probably easiest to use this, if you don't want to understand
+* the dependencies involved, in conjunction with PHP versions etc. At
+* the same time, you might get better performance by managing loading
+* yourself. The smartest way to do this, bearing in mind performance,
+* is probably to "load on demand" - i.e. just before you use these
+* functions in your code, load the version you need.
+*
+* It makes sure the the following functions are available;
+* utf8_strlen, utf8_strpos, utf8_strrpos, utf8_substr,
+* utf8_strtolower, utf8_strtoupper
+* Other functions in the ./native directory depend on these
+* six functions being available
+* @package utf8
+*/
+
+// Check whether PCRE has been compiled with UTF-8 support
+$UTF8_ar = array();
+if (preg_match('/^.{1}$/u', "ñ", $UTF8_ar) != 1)
+	trigger_error('PCRE is not compiled with UTF-8 support', E_USER_ERROR);
+
+unset($UTF8_ar);
+
+// Put the current directory in this constant
+if (!defined('UTF8'))
+	define('UTF8', dirname(__FILE__));
+
+if (extension_loaded('mbstring') && !defined('UTF8_USE_MBSTRING') && !defined('UTF8_USE_NATIVE'))
+	define('UTF8_USE_MBSTRING', true);
+else if (!defined('UTF8_USE_NATIVE'))
+	define('UTF8_USE_NATIVE', true);
+
+// utf8_strpos() and utf8_strrpos() need utf8_bad_strip() to strip invalid
+// characters. Mbstring doesn't do this while the Native implementation does.
+require UTF8.'/utils/bad.php';
+
+if (defined('UTF8_USE_MBSTRING'))
+{
+	/**
+	* If string overloading is active, it will break many of the
+	* native implementations. mbstring.func_overload must be set
+	* to 0, 1 or 4 in php.ini (string overloading disabled).
+	* Also need to check we have the correct internal mbstring
+	* encoding
+	*/
+	if (ini_get('mbstring.func_overload') & MB_OVERLOAD_STRING)
+		trigger_error('String functions are overloaded by mbstring', E_USER_ERROR);
+
+	mb_language('uni');
+	mb_internal_encoding('UTF-8');
+
+	if (!defined('UTF8_CORE'))
+		require UTF8.'/mbstring/core.php';
+}
+elseif (defined('UTF8_USE_NATIVE'))
+{
+	if (!defined('UTF8_CORE'))
+	{
+		require UTF8.'/utils/unicode.php';
+		require UTF8.'/native/core.php';
+	}
+}
+
+// Load the native implementation of utf8_trim
+require UTF8.'/trim.php';
diff --git a/include/utf8/utils/ascii.php b/include/utf8/utils/ascii.php
new file mode 100644
index 0000000..af75b92
--- /dev/null
+++ b/include/utf8/utils/ascii.php
@@ -0,0 +1,221 @@
+<?php
+
+/**
+* Tools to help with ASCII in UTF-8
+* @version $Id: ascii.php,v 1.5 2006/10/16 20:38:12 harryf Exp $
+* @package utf8
+* @subpackage ascii
+*/
+
+/**
+* Tests whether a string contains only 7bit ASCII bytes.
+* You might use this to conditionally check whether a string
+* needs handling as UTF-8 or not, potentially offering performance
+* benefits by using the native PHP equivalent if it's just ASCII e.g.;
+*
+* <code>
+* if ( utf8_is_ascii($someString) ) {
+*     // It's just ASCII - use the native PHP version
+*     $someString = strtolower($someString);
+* } else {
+*     $someString = utf8_strtolower($someString);
+* }
+* </code>
+*
+* @param string
+* @return boolean TRUE if it's all ASCII
+* @package utf8
+* @subpackage ascii
+* @see utf8_is_ascii_ctrl
+*/
+function utf8_is_ascii($str)
+{
+	// Search for any bytes which are outside the ASCII range...
+	return (preg_match('/(?:[^\x00-\x7F])/', $str) !== 1);
+}
+
+/**
+* Tests whether a string contains only 7bit ASCII bytes with device
+* control codes omitted. The device control codes can be found on the
+* second table here: http://www.w3schools.com/tags/ref_ascii.asp
+*
+* @param string
+* @return boolean TRUE if it's all ASCII without device control codes
+* @package utf8
+* @subpackage ascii
+* @see utf8_is_ascii
+*/
+function utf8_is_ascii_ctrl($str)
+{
+	// Search for any bytes which are outside the ASCII range, or are device control codes
+	if (strlen($str) > 0)
+		return (preg_match('/[^\x09\x0A\x0D\x20-\x7E]/', $str) !== 1);
+
+	return false;
+}
+
+/**
+* Strip out all non-7bit ASCII bytes
+* If you need to transmit a string to system which you know can only
+* support 7bit ASCII, you could use this function.
+* @param string
+* @return string with non ASCII bytes removed
+* @package utf8
+* @subpackage ascii
+* @see utf8_strip_non_ascii_ctrl
+*/
+function utf8_strip_non_ascii($str)
+{
+	ob_start();
+
+	while (preg_match('/^([\x00-\x7F]+)|([^\x00-\x7F]+)/S', $str, $matches))
+	{
+		if (!isset($matches[2]))
+			echo $matches[0];
+
+		$str = substr($str, strlen($matches[0]));
+	}
+
+	$result = ob_get_contents();
+	ob_end_clean();
+
+	return $result;
+}
+
+/**
+* Strip out device control codes in the ASCII range
+* which are not permitted in XML. Note that this leaves
+* multi-byte characters untouched - it only removes device
+* control codes
+* @see http://hsivonen.iki.fi/producing-xml/#controlchar
+* @param string
+* @return string control codes removed
+*/
+function utf8_strip_ascii_ctrl($str)
+{
+	ob_start();
+
+	while (preg_match('/^([^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+)|([\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+)/S', $str, $matches))
+	{
+		if (!isset($matches[2]))
+			echo $matches[0];
+
+		$str = substr($str, strlen($matches[0]));
+	}
+
+	$result = ob_get_contents();
+	ob_end_clean();
+
+	return $result;
+}
+
+/**
+* Strip out all non 7bit ASCII bytes and ASCII device control codes.
+* For a list of ASCII device control codes see the 2nd table here:
+* http://www.w3schools.com/tags/ref_ascii.asp
+*
+* @param string
+* @return boolean TRUE if it's all ASCII
+* @package utf8
+* @subpackage ascii
+*/
+function utf8_strip_non_ascii_ctrl($str)
+{
+	ob_start();
+
+	while (preg_match( '/^([\x09\x0A\x0D\x20-\x7E]+)|([^\x09\x0A\x0D\x20-\x7E]+)/S', $str, $matches))
+	{
+		if (!isset($matches[2]))
+			echo $matches[0];
+
+		$str = substr($str, strlen($matches[0]));
+	}
+
+	$result = ob_get_contents();
+	ob_end_clean();
+
+	return $result;
+}
+
+/**
+* Replace accented UTF-8 characters by unaccented ASCII-7 "equivalents".
+* The purpose of this function is to replace characters commonly found in Latin
+* alphabets with something more or less equivalent from the ASCII range. This can
+* be useful for converting a UTF-8 to something ready for a filename, for example.
+* Following the use of this function, you would probably also pass the string
+* through utf8_strip_non_ascii to clean out any other non-ASCII chars
+* Use the optional parameter to just deaccent lower ($case = -1) or upper ($case = 1)
+* letters. Default is to deaccent both cases ($case = 0)
+*
+* For a more complete implementation of transliteration, see the utf8_to_ascii package
+* available from the phputf8 project downloads:
+* http://prdownloads.sourceforge.net/phputf8
+*
+* @param string UTF-8 string
+* @param int (optional) -1 lowercase only, +1 uppercase only, 1 both cases
+* @param string UTF-8 with accented characters replaced by ASCII chars
+* @return string accented chars replaced with ascii equivalents
+* @author Andreas Gohr <andi@splitbrain.org>
+* @package utf8
+* @subpackage ascii
+*/
+function utf8_accents_to_ascii($str, $case=0)
+{
+	static $UTF8_LOWER_ACCENTS = null;
+	static $UTF8_UPPER_ACCENTS = null;
+
+	if($case <= 0)
+	{
+
+		if (is_null($UTF8_LOWER_ACCENTS))
+		{
+			$UTF8_LOWER_ACCENTS = array(
+				'à' => 'a', 'ô' => 'o', 'ď' => 'd', 'ḟ' => 'f', 'ë' => 'e', 'š' => 's', 'ơ' => 'o',
+				'ß' => 'ss', 'ă' => 'a', 'ř' => 'r', 'ț' => 't', 'ň' => 'n', 'ā' => 'a', 'ķ' => 'k',
+				'ŝ' => 's', 'ỳ' => 'y', 'ņ' => 'n', 'ĺ' => 'l', 'ħ' => 'h', 'ṗ' => 'p', 'ó' => 'o',
+				'ú' => 'u', 'ě' => 'e', 'é' => 'e', 'ç' => 'c', 'ẁ' => 'w', 'ċ' => 'c', 'õ' => 'o',
+				'ṡ' => 's', 'ø' => 'o', 'ģ' => 'g', 'ŧ' => 't', 'ș' => 's', 'ė' => 'e', 'ĉ' => 'c',
+				'ś' => 's', 'î' => 'i', 'ű' => 'u', 'ć' => 'c', 'ę' => 'e', 'ŵ' => 'w', 'ṫ' => 't',
+				'ū' => 'u', 'č' => 'c', 'ö' => 'oe', 'è' => 'e', 'ŷ' => 'y', 'ą' => 'a', 'ł' => 'l',
+				'ų' => 'u', 'ů' => 'u', 'ş' => 's', 'ğ' => 'g', 'ļ' => 'l', 'ƒ' => 'f', 'ž' => 'z',
+				'ẃ' => 'w', 'ḃ' => 'b', 'å' => 'a', 'ì' => 'i', 'ï' => 'i', 'ḋ' => 'd', 'ť' => 't',
+				'ŗ' => 'r', 'ä' => 'ae', 'í' => 'i', 'ŕ' => 'r', 'ê' => 'e', 'ü' => 'ue', 'ò' => 'o',
+				'ē' => 'e', 'ñ' => 'n', 'ń' => 'n', 'ĥ' => 'h', 'ĝ' => 'g', 'đ' => 'd', 'ĵ' => 'j',
+				'ÿ' => 'y', 'ũ' => 'u', 'ŭ' => 'u', 'ư' => 'u', 'ţ' => 't', 'ý' => 'y', 'ő' => 'o',
+				'â' => 'a', 'ľ' => 'l', 'ẅ' => 'w', 'ż' => 'z', 'ī' => 'i', 'ã' => 'a', 'ġ' => 'g',
+				'ṁ' => 'm', 'ō' => 'o', 'ĩ' => 'i', 'ù' => 'u', 'į' => 'i', 'ź' => 'z', 'á' => 'a',
+				'û' => 'u', 'þ' => 'th', 'ð' => 'dh', 'æ' => 'ae', 'µ' => 'u', 'ĕ' => 'e',
+				);
+		}
+
+		$str = str_replace(array_keys($UTF8_LOWER_ACCENTS), array_values($UTF8_LOWER_ACCENTS), $str);
+	}
+
+	if($case >= 0)
+	{
+		if (is_null($UTF8_UPPER_ACCENTS))
+		{
+			$UTF8_UPPER_ACCENTS = array(
+				'À' => 'A', 'Ô' => 'O', 'Ď' => 'D', 'Ḟ' => 'F', 'Ë' => 'E', 'Š' => 'S', 'Ơ' => 'O',
+				'Ă' => 'A', 'Ř' => 'R', 'Ț' => 'T', 'Ň' => 'N', 'Ā' => 'A', 'Ķ' => 'K',
+				'Ŝ' => 'S', 'Ỳ' => 'Y', 'Ņ' => 'N', 'Ĺ' => 'L', 'Ħ' => 'H', 'Ṗ' => 'P', 'Ó' => 'O',
+				'Ú' => 'U', 'Ě' => 'E', 'É' => 'E', 'Ç' => 'C', 'Ẁ' => 'W', 'Ċ' => 'C', 'Õ' => 'O',
+				'Ṡ' => 'S', 'Ø' => 'O', 'Ģ' => 'G', 'Ŧ' => 'T', 'Ș' => 'S', 'Ė' => 'E', 'Ĉ' => 'C',
+				'Ś' => 'S', 'Î' => 'I', 'Ű' => 'U', 'Ć' => 'C', 'Ę' => 'E', 'Ŵ' => 'W', 'Ṫ' => 'T',
+				'Ū' => 'U', 'Č' => 'C', 'Ö' => 'Oe', 'È' => 'E', 'Ŷ' => 'Y', 'Ą' => 'A', 'Ł' => 'L',
+				'Ų' => 'U', 'Ů' => 'U', 'Ş' => 'S', 'Ğ' => 'G', 'Ļ' => 'L', 'Ƒ' => 'F', 'Ž' => 'Z',
+				'Ẃ' => 'W', 'Ḃ' => 'B', 'Å' => 'A', 'Ì' => 'I', 'Ï' => 'I', 'Ḋ' => 'D', 'Ť' => 'T',
+				'Ŗ' => 'R', 'Ä' => 'Ae', 'Í' => 'I', 'Ŕ' => 'R', 'Ê' => 'E', 'Ü' => 'Ue', 'Ò' => 'O',
+				'Ē' => 'E', 'Ñ' => 'N', 'Ń' => 'N', 'Ĥ' => 'H', 'Ĝ' => 'G', 'Đ' => 'D', 'Ĵ' => 'J',
+				'Ÿ' => 'Y', 'Ũ' => 'U', 'Ŭ' => 'U', 'Ư' => 'U', 'Ţ' => 'T', 'Ý' => 'Y', 'Ő' => 'O',
+				'Â' => 'A', 'Ľ' => 'L', 'Ẅ' => 'W', 'Ż' => 'Z', 'Ī' => 'I', 'Ã' => 'A', 'Ġ' => 'G',
+				'Ṁ' => 'M', 'Ō' => 'O', 'Ĩ' => 'I', 'Ù' => 'U', 'Į' => 'I', 'Ź' => 'Z', 'Á' => 'A',
+				'Û' => 'U', 'Þ' => 'Th', 'Ð' => 'Dh', 'Æ' => 'Ae', 'Ĕ' => 'E',
+				);
+		}
+
+		$str = str_replace(array_keys($UTF8_UPPER_ACCENTS), array_values($UTF8_UPPER_ACCENTS),  $str);
+	}
+
+	return $str;
+}
diff --git a/include/utf8/utils/bad.php b/include/utf8/utils/bad.php
new file mode 100644
index 0000000..2704294
--- /dev/null
+++ b/include/utf8/utils/bad.php
@@ -0,0 +1,430 @@
+<?php
+
+/**
+* @version $Id: bad.php,v 1.2 2006/02/26 13:20:44 harryf Exp $
+* Tools for locating / replacing bad bytes in UTF-8 strings
+* The Original Code is Mozilla Communicator client code.
+* The Initial Developer of the Original Code is
+* Netscape Communications Corporation.
+* Portions created by the Initial Developer are Copyright (C) 1998
+* the Initial Developer. All Rights Reserved.
+* Ported to PHP by Henri Sivonen (http://hsivonen.iki.fi)
+* Slight modifications to fit with phputf8 library by Harry Fuecks (hfuecks gmail com)
+* @see http://lxr.mozilla.org/seamonkey/source/intl/uconv/src/nsUTF8ToUnicode.cpp
+* @see http://lxr.mozilla.org/seamonkey/source/intl/uconv/src/nsUnicodeToUTF8.cpp
+* @see http://hsivonen.iki.fi/php-utf8/
+* @package utf8
+* @subpackage bad
+* @see utf8_is_valid
+*/
+
+/**
+* Locates the first bad byte in a UTF-8 string returning it's
+* byte index in the string
+* PCRE Pattern to locate bad bytes in a UTF-8 string
+* Comes from W3 FAQ: Multilingual Forms
+* Note: modified to include full ASCII range including control chars
+* @see http://www.w3.org/International/questions/qa-forms-utf-8
+* @param string
+* @return mixed integer byte index or FALSE if no bad found
+* @package utf8
+* @subpackage bad
+*/
+function utf8_bad_find($str)
+{
+	$UTF8_BAD =
+		'([\x00-\x7F]'.                          # ASCII (including control chars)
+		'|[\xC2-\xDF][\x80-\xBF]'.               # Non-overlong 2-byte
+		'|\xE0[\xA0-\xBF][\x80-\xBF]'.           # Excluding overlongs
+		'|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}'.    # Straight 3-byte
+		'|\xED[\x80-\x9F][\x80-\xBF]'.           # Excluding surrogates
+		'|\xF0[\x90-\xBF][\x80-\xBF]{2}'.        # Planes 1-3
+		'|[\xF1-\xF3][\x80-\xBF]{3}'.            # Planes 4-15
+		'|\xF4[\x80-\x8F][\x80-\xBF]{2}'.        # Plane 16
+		'|(.{1}))';                              # Invalid byte
+	$pos = 0;
+	$badList = array();
+
+	while (preg_match('/'.$UTF8_BAD.'/S', $str, $matches))
+	{
+		$bytes = strlen($matches[0]);
+
+		if (isset($matches[2]))
+			return $pos;
+
+		$pos += $bytes;
+		$str = substr($str,$bytes);
+	}
+
+	return false;
+}
+
+/**
+* Locates all bad bytes in a UTF-8 string and returns a list of their
+* byte index in the string
+* PCRE Pattern to locate bad bytes in a UTF-8 string
+* Comes from W3 FAQ: Multilingual Forms
+* Note: modified to include full ASCII range including control chars
+* @see http://www.w3.org/International/questions/qa-forms-utf-8
+* @param string
+* @return mixed array of integers or FALSE if no bad found
+* @package utf8
+* @subpackage bad
+*/
+function utf8_bad_findall($str)
+{
+	$UTF8_BAD =
+		'([\x00-\x7F]'.                          # ASCII (including control chars)
+		'|[\xC2-\xDF][\x80-\xBF]'.               # Non-overlong 2-byte
+		'|\xE0[\xA0-\xBF][\x80-\xBF]'.           # Excluding overlongs
+		'|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}'.    # Straight 3-byte
+		'|\xED[\x80-\x9F][\x80-\xBF]'.           # Excluding surrogates
+		'|\xF0[\x90-\xBF][\x80-\xBF]{2}'.        # Planes 1-3
+		'|[\xF1-\xF3][\x80-\xBF]{3}'.            # Planes 4-15
+		'|\xF4[\x80-\x8F][\x80-\xBF]{2}'.        # Plane 16
+		'|(.{1}))';                              # Invalid byte
+	$pos = 0;
+	$badList = array();
+
+	while (preg_match('/'.$UTF8_BAD.'/S', $str, $matches))
+	{
+		$bytes = strlen($matches[0]);
+
+		if (isset($matches[2]))
+			$badList[] = $pos;
+
+		$pos += $bytes;
+		$str = substr($str,$bytes);
+	}
+
+	if (count($badList) > 0)
+		return $badList;
+
+	return false;
+}
+
+/**
+* Strips out any bad bytes from a UTF-8 string and returns the rest
+* PCRE Pattern to locate bad bytes in a UTF-8 string
+* Comes from W3 FAQ: Multilingual Forms
+* Note: modified to include full ASCII range including control chars
+* @see http://www.w3.org/International/questions/qa-forms-utf-8
+* @param string
+* @return string
+* @package utf8
+* @subpackage bad
+*/
+function utf8_bad_strip($original)
+{
+	return utf8_bad_replace($original, '');
+}
+
+/**
+* Replace bad bytes with an alternative character - ASCII character
+* recommended is replacement char
+* PCRE Pattern to locate bad bytes in a UTF-8 string
+* Comes from W3 FAQ: Multilingual Forms
+* Note: modified to include full ASCII range including control chars
+* @see http://www.w3.org/International/questions/qa-forms-utf-8
+* @param string to search
+* @param string to replace bad bytes with (defaults to '?') - use ASCII
+* @return string
+* @package utf8
+* @subpackage bad
+*/
+function utf8_bad_replace($original, $replace = '?') {
+	$result = '';
+
+	$strlen = strlen($original);
+	for ($i = 0; $i < $strlen;) {
+		$char = $original[$i++];
+		$byte = ord($char);
+
+		if ($byte < 0x80) $bytes = 0; // 1-bytes (00000000 - 01111111)
+		else if ($byte < 0xC0) { // 1-bytes (10000000 - 10111111)
+			$result .= $replace;
+			continue;
+		}
+		else if ($byte < 0xE0) $bytes = 1; // 2-bytes (11000000 - 11011111)
+		else if ($byte < 0xF0) $bytes = 2; // 3-bytes (11100000 - 11101111)
+		else if ($byte < 0xF8) $bytes = 3; // 4-bytes (11110000 - 11110111)
+		else if ($byte < 0xFC) $bytes = 4; // 5-bytes (11111000 - 11111011)
+		else if ($byte < 0xFE) $bytes = 5; // 6-bytes (11111100 - 11111101)
+		else { // Otherwise it's something invalid
+			$result .= $replace;
+			continue;
+		}
+
+		// Check our input actually has enough data
+		if ($i + $bytes > $strlen) {
+			$result .= $replace;
+			continue;
+		}
+
+		// If we've got this far then we have a multiple-byte character
+		for ($j = 0; $j < $bytes; $j++) {
+			$byte = $original[$i + $j];
+
+			$char .= $byte;
+			$byte = ord($byte);
+
+			// Every following byte must be 10000000 - 10111111
+			if ($byte < 0x80 || $byte > 0xBF) {
+				$result .= $replace;
+				continue 2;
+			}
+		}
+
+		$i += $bytes;
+		$result .= $char;
+	}
+
+	return $result;
+}
+
+/**
+* Return code from utf8_bad_identify() when a five octet sequence is detected.
+* Note: 5 octets sequences are valid UTF-8 but are not supported by Unicode so
+* do not represent a useful character
+* @see utf8_bad_identify
+* @package utf8
+* @subpackage bad
+*/
+define('UTF8_BAD_5OCTET', 1);
+
+/**
+* Return code from utf8_bad_identify() when a six octet sequence is detected.
+* Note: 6 octets sequences are valid UTF-8 but are not supported by Unicode so
+* do not represent a useful character
+* @see utf8_bad_identify
+* @package utf8
+* @subpackage bad
+*/
+define('UTF8_BAD_6OCTET', 2);
+
+/**
+* Return code from utf8_bad_identify().
+* Invalid octet for use as start of multi-byte UTF-8 sequence
+* @see utf8_bad_identify
+* @package utf8
+* @subpackage bad
+*/
+define('UTF8_BAD_SEQID', 3);
+
+/**
+* Return code from utf8_bad_identify().
+* From Unicode 3.1, non-shortest form is illegal
+* @see utf8_bad_identify
+* @package utf8
+* @subpackage bad
+*/
+define('UTF8_BAD_NONSHORT', 4);
+
+/**
+* Return code from utf8_bad_identify().
+* From Unicode 3.2, surrogate characters are illegal
+* @see utf8_bad_identify
+* @package utf8
+* @subpackage bad
+*/
+define('UTF8_BAD_SURROGATE', 5);
+
+/**
+* Return code from utf8_bad_identify().
+* Codepoints outside the Unicode range are illegal
+* @see utf8_bad_identify
+* @package utf8
+* @subpackage bad
+*/
+define('UTF8_BAD_UNIOUTRANGE', 6);
+
+/**
+* Return code from utf8_bad_identify().
+* Incomplete multi-octet sequence
+* Note: this is kind of a "catch-all"
+* @see utf8_bad_identify
+* @package utf8
+* @subpackage bad
+*/
+define('UTF8_BAD_SEQINCOMPLETE', 7);
+
+/**
+* Reports on the type of bad byte found in a UTF-8 string. Returns a
+* status code on the first bad byte found
+* @author <hsivonen@iki.fi>
+* @param string UTF-8 encoded string
+* @return mixed integer constant describing problem or FALSE if valid UTF-8
+* @see utf8_bad_explain
+* @see http://hsivonen.iki.fi/php-utf8/
+* @package utf8
+* @subpackage bad
+*/
+function utf8_bad_identify($str, &$i)
+{
+	$mState = 0;	// Cached expected number of octets after the current octet
+					// until the beginning of the next UTF8 character sequence
+	$mUcs4  = 0;	// Cached Unicode character
+	$mBytes = 1;	// Cached expected number of octets in the current sequence
+
+	$len = strlen($str);
+
+	for($i=0; $i < $len; $i++)
+	{
+		$in = ord($str{$i});
+
+		if ( $mState == 0)
+		{
+			// When mState is zero we expect either a US-ASCII character or a multi-octet sequence.
+			if (0 == (0x80 & ($in)))
+			{
+				// US-ASCII, pass straight through.
+				$mBytes = 1;
+			}
+			else if (0xC0 == (0xE0 & ($in)))
+			{
+				// First octet of 2 octet sequence
+				$mUcs4 = ($in);
+				$mUcs4 = ($mUcs4 & 0x1F) << 6;
+				$mState = 1;
+				$mBytes = 2;
+			}
+			else if (0xE0 == (0xF0 & ($in)))
+			{
+				// First octet of 3 octet sequence
+				$mUcs4 = ($in);
+				$mUcs4 = ($mUcs4 & 0x0F) << 12;
+				$mState = 2;
+				$mBytes = 3;
+			}
+			else if (0xF0 == (0xF8 & ($in)))
+			{
+				// First octet of 4 octet sequence
+				$mUcs4 = ($in);
+				$mUcs4 = ($mUcs4 & 0x07) << 18;
+				$mState = 3;
+				$mBytes = 4;
+			}
+			else if (0xF8 == (0xFC & ($in)))
+			{
+				/* First octet of 5 octet sequence.
+				*
+				* This is illegal because the encoded codepoint must be either
+				* (a) not the shortest form or
+				* (b) outside the Unicode range of 0-0x10FFFF.
+				*/
+				return UTF8_BAD_5OCTET;
+			}
+			else if (0xFC == (0xFE & ($in)))
+			{
+				// First octet of 6 octet sequence, see comments for 5 octet sequence.
+				return UTF8_BAD_6OCTET;
+			}
+			else
+			{
+				// Current octet is neither in the US-ASCII range nor a legal first
+				// octet of a multi-octet sequence.
+				return UTF8_BAD_SEQID;
+			}
+		}
+		else
+		{
+			// When mState is non-zero, we expect a continuation of the multi-octet sequence
+			if (0x80 == (0xC0 & ($in)))
+			{
+				// Legal continuation.
+				$shift = ($mState - 1) * 6;
+				$tmp = $in;
+				$tmp = ($tmp & 0x0000003F) << $shift;
+				$mUcs4 |= $tmp;
+
+				/**
+				* End of the multi-octet sequence. mUcs4 now contains the final
+				* Unicode codepoint to be output
+				*/
+				if (0 == --$mState)
+				{
+					// From Unicode 3.1, non-shortest form is illegal
+					if (((2 == $mBytes) && ($mUcs4 < 0x0080)) ||
+					((3 == $mBytes) && ($mUcs4 < 0x0800)) ||
+					((4 == $mBytes) && ($mUcs4 < 0x10000)) )
+						return UTF8_BAD_NONSHORT;
+					else if (($mUcs4 & 0xFFFFF800) == 0xD800) // From Unicode 3.2, surrogate characters are illegal
+						return UTF8_BAD_SURROGATE;
+					else if ($mUcs4 > 0x10FFFF) // Codepoints outside the Unicode range are illegal
+						return UTF8_BAD_UNIOUTRANGE;
+
+					// Initialize UTF8 cache
+					$mState = 0;
+					$mUcs4  = 0;
+					$mBytes = 1;
+				}
+
+			}
+			else
+			{
+				// ((0xC0 & (*in) != 0x80) && (mState != 0))
+				// Incomplete multi-octet sequence.
+				$i--;
+				return UTF8_BAD_SEQINCOMPLETE;
+			}
+		}
+	}
+
+	// Incomplete multi-octet sequence
+	if ($mState != 0)
+	{
+		$i--;
+		return UTF8_BAD_SEQINCOMPLETE;
+	}
+
+	// No bad octets found
+	$i = null;
+	return false;
+}
+
+/**
+* Takes a return code from utf8_bad_identify() are returns a message
+* (in English) explaining what the problem is.
+* @param int return code from utf8_bad_identify
+* @return mixed string message or FALSE if return code unknown
+* @see utf8_bad_identify
+* @package utf8
+* @subpackage bad
+*/
+function utf8_bad_explain($code)
+{
+	switch ($code)
+	{
+		case UTF8_BAD_5OCTET:
+		return 'Five octet sequences are valid UTF-8 but are not supported by Unicode';
+		break;
+
+		case UTF8_BAD_6OCTET:
+		return 'Six octet sequences are valid UTF-8 but are not supported by Unicode';
+		break;
+
+		case UTF8_BAD_SEQID:
+		return 'Invalid octet for use as start of multi-byte UTF-8 sequence';
+		break;
+
+		case UTF8_BAD_NONSHORT:
+		return 'From Unicode 3.1, non-shortest form is illegal';
+		break;
+
+		case UTF8_BAD_SURROGATE:
+		return 'From Unicode 3.2, surrogate characters are illegal';
+		break;
+
+		case UTF8_BAD_UNIOUTRANGE:
+		return 'Codepoints outside the Unicode range are illegal';
+		break;
+
+		case UTF8_BAD_SEQINCOMPLETE:
+		return 'Incomplete multi-octet sequence';
+		break;
+	}
+
+	trigger_error('Unknown error code: '.$code, E_USER_WARNING);
+
+	return false;
+}
diff --git a/include/utf8/utils/index.html b/include/utf8/utils/index.html
new file mode 100644
index 0000000..89337b2
--- /dev/null
+++ b/include/utf8/utils/index.html
@@ -0,0 +1 @@
+<html><head><title>.</title></head><body>.</body></html>
diff --git a/include/utf8/utils/patterns.php b/include/utf8/utils/patterns.php
new file mode 100644
index 0000000..5a85a4f
--- /dev/null
+++ b/include/utf8/utils/patterns.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+* PCRE Regular expressions for UTF-8. Note this file is not actually used by
+* the rest of the library but these regular expressions can be useful to have
+* available.
+* @version $Id: patterns.php,v 1.1 2006/02/25 14:20:02 harryf Exp $
+* @see http://www.w3.org/International/questions/qa-forms-utf-8
+* @package utf8
+* @subpackage patterns
+*/
+
+/**
+* PCRE Pattern to check a UTF-8 string is valid
+* Comes from W3 FAQ: Multilingual Forms
+* Note: modified to include full ASCII range including control chars
+* @see http://www.w3.org/International/questions/qa-forms-utf-8
+* @package utf8
+* @subpackage patterns
+*/
+$UTF8_VALID = '^('.
+	'[\x00-\x7F]'.                          # ASCII (including control chars)
+	'|[\xC2-\xDF][\x80-\xBF]'.              # Non-overlong 2-byte
+	'|\xE0[\xA0-\xBF][\x80-\xBF]'.          # Excluding overlongs
+	'|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}'.   # Straight 3-byte
+	'|\xED[\x80-\x9F][\x80-\xBF]'.          # Excluding surrogates
+	'|\xF0[\x90-\xBF][\x80-\xBF]{2}'.       # Planes 1-3
+	'|[\xF1-\xF3][\x80-\xBF]{3}'.           # Planes 4-15
+	'|\xF4[\x80-\x8F][\x80-\xBF]{2}'.       # Plane 16
+	')*$';
+
+/**
+* PCRE Pattern to match single UTF-8 characters
+* Comes from W3 FAQ: Multilingual Forms
+* Note: modified to include full ASCII range including control chars
+* @see http://www.w3.org/International/questions/qa-forms-utf-8
+* @package utf8
+* @subpackage patterns
+*/
+$UTF8_MATCH =
+	'([\x00-\x7F])'.                          # ASCII (including control chars)
+	'|([\xC2-\xDF][\x80-\xBF])'.              # Non-overlong 2-byte
+	'|(\xE0[\xA0-\xBF][\x80-\xBF])'.          # Excluding overlongs
+	'|([\xE1-\xEC\xEE\xEF][\x80-\xBF]{2})'.   # Straight 3-byte
+	'|(\xED[\x80-\x9F][\x80-\xBF])'.          # Excluding surrogates
+	'|(\xF0[\x90-\xBF][\x80-\xBF]{2})'.       # Planes 1-3
+	'|([\xF1-\xF3][\x80-\xBF]{3})'.           # Planes 4-15
+	'|(\xF4[\x80-\x8F][\x80-\xBF]{2})';       # Plane 16
+
+/**
+* PCRE Pattern to locate bad bytes in a UTF-8 string
+* Comes from W3 FAQ: Multilingual Forms
+* Note: modified to include full ASCII range including control chars
+* @see http://www.w3.org/International/questions/qa-forms-utf-8
+* @package utf8
+* @subpackage patterns
+*/
+$UTF8_BAD =
+	'([\x00-\x7F]'.                          # ASCII (including control chars)
+	'|[\xC2-\xDF][\x80-\xBF]'.               # Non-overlong 2-byte
+	'|\xE0[\xA0-\xBF][\x80-\xBF]'.           # Excluding overlongs
+	'|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}'.    # Straight 3-byte
+	'|\xED[\x80-\x9F][\x80-\xBF]'.           # Excluding surrogates
+	'|\xF0[\x90-\xBF][\x80-\xBF]{2}'.        # Planes 1-3
+	'|[\xF1-\xF3][\x80-\xBF]{3}'.            # Planes 4-15
+	'|\xF4[\x80-\x8F][\x80-\xBF]{2}'.        # Plane 16
+	'|(.{1}))';                              # Invalid byte
diff --git a/include/utf8/utils/position.php b/include/utf8/utils/position.php
new file mode 100644
index 0000000..7c62d10
--- /dev/null
+++ b/include/utf8/utils/position.php
@@ -0,0 +1,171 @@
+<?php
+
+/**
+* Locate a byte index given a UTF-8 character index
+* @version $Id: position.php,v 1.1 2006/10/01 00:01:31 harryf Exp $
+* @package utf8
+* @subpackage position
+*/
+
+/**
+* Given a string and a character index in the string, in
+* terms of the UTF-8 character position, returns the byte
+* index of that character. Can be useful when you want to
+* PHP's native string functions but we warned, locating
+* the byte can be expensive
+* Takes variable number of parameters - first must be
+* the search string then 1 to n UTF-8 character positions
+* to obtain byte indexes for - it is more efficient to search
+* the string for multiple characters at once, than make
+* repeated calls to this function
+*
+* @author Chris Smith<chris@jalakai.co.uk>
+* @param string string to locate index in
+* @param int (n times)
+* @return mixed - int if only one input int, array if more
+* @return boolean TRUE if it's all ASCII
+* @package utf8
+* @subpackage position
+*/
+function utf8_byte_position()
+{
+	$args = func_get_args();
+	$str =& array_shift($args);
+
+	if (!is_string($str))
+		return false;
+
+	$result = array();
+	$prev = array(0, 0); // Trivial byte index, character offset pair
+	$i = utf8_locate_next_chr($str, 300); // Use a short piece of str to estimate bytes per character. $i (& $j) -> byte indexes into $str
+	$c = strlen(utf8_decode(substr($str, 0, $i))); // $c -> character offset into $str
+
+	// Deal with arguments from lowest to highest
+	sort($args);
+
+	foreach ($args as $offset)
+	{
+		// Sanity checks FIXME
+
+		// 0 is an easy check
+		if ($offset == 0)
+		{
+			$result[] = 0; continue;
+		}
+
+		// Ensure no endless looping
+		$safety_valve = 50;
+
+		do
+		{
+			if (($c - $prev[1]) == 0)
+			{
+				// Hack: gone past end of string
+				$error = 0;
+				$i = strlen($str);
+				break;
+			}
+
+			$j = $i + (int)(($offset-$c) * ($i - $prev[0]) / ($c - $prev[1]));
+			$j = utf8_locate_next_chr($str, $j); // Correct to utf8 character boundary
+			$prev = array($i,$c); // Save the index, offset for use next iteration
+
+			if ($j > $i)
+				$c += strlen(utf8_decode(substr($str, $i, $j-$i))); // Determine new character offset
+			else
+				$c -= strlen(utf8_decode(substr($str, $j, $i-$j))); // Ditto
+
+			$error = abs($c-$offset);
+			$i = $j; // Ready for next time around
+		}
+		while (($error > 7) && --$safety_valve); // From 7 it is faster to iterate over the string
+
+		if ($error && $error <= 7)
+		{
+			if ($c < $offset)
+			{
+				// Move up
+				while ($error--)
+					$i = utf8_locate_next_chr($str, ++$i);
+			}
+			else
+			{
+				// Move down
+				while ($error--)
+					$i = utf8_locate_current_chr($str, --$i);
+			}
+
+			// Ready for next arg
+			$c = $offset;
+		}
+
+		$result[] = $i;
+	}
+
+	if (count($result) == 1)
+		return $result[0];
+
+	return $result;
+}
+
+/**
+* Given a string and any byte index, returns the byte index
+* of the start of the current UTF-8 character, relative to supplied
+* position. If the current character begins at the same place as the
+* supplied byte index, that byte index will be returned. Otherwise
+* this function will step backwards, looking for the index where
+* curent UTF-8 character begins
+* @author Chris Smith<chris@jalakai.co.uk>
+* @param string
+* @param int byte index in the string
+* @return int byte index of start of next UTF-8 character
+* @package utf8
+* @subpackage position
+*/
+function utf8_locate_current_chr( &$str, $idx )
+{
+	if ($idx <= 0)
+		return 0;
+
+	$limit = strlen($str);
+	if ($idx >= $limit)
+		return $limit;
+
+	// Binary value for any byte after the first in a multi-byte UTF-8 character
+	// will be like 10xxxxxx so & 0xC0 can be used to detect this kind
+	// of byte - assuming well formed UTF-8
+	while ($idx && ((ord($str[$idx]) & 0xC0) == 0x80))
+		$idx--;
+
+	return $idx;
+}
+
+/**
+* Given a string and any byte index, returns the byte index
+* of the start of the next UTF-8 character, relative to supplied
+* position. If the next character begins at the same place as the
+* supplied byte index, that byte index will be returned.
+* @author Chris Smith<chris@jalakai.co.uk>
+* @param string
+* @param int byte index in the string
+* @return int byte index of start of next UTF-8 character
+* @package utf8
+* @subpackage position
+*/
+function utf8_locate_next_chr(&$str, $idx)
+{
+	if ($idx <= 0)
+		return 0;
+
+	$limit = strlen($str);
+	if ($idx >= $limit)
+		return $limit;
+
+	// Binary value for any byte after the first in a multi-byte UTF-8 character
+	// will be like 10xxxxxx so & 0xC0 can be used to detect this kind
+	// of byte - assuming well formed UTF-8
+	while (($idx < $limit) && ((ord($str[$idx]) & 0xC0) == 0x80))
+		$idx++;
+
+	return $idx;
+}
diff --git a/include/utf8/utils/specials.php b/include/utf8/utils/specials.php
new file mode 100644
index 0000000..69219dc
--- /dev/null
+++ b/include/utf8/utils/specials.php
@@ -0,0 +1,131 @@
+<?php
+
+/**
+* Utilities for processing "special" characters in UTF-8. "Special" largely means anything which would
+* be regarded as a non-word character, like ASCII control characters and punctuation. This has a "Roman"
+* bias - it would be unaware of modern Chinese "punctuation" characters for example.
+* Note: requires utils/unicode.php to be loaded
+* @version $Id: specials.php,v 1.2 2006/10/16 21:13:59 harryf Exp $
+* @package utf8
+* @subpackage utils
+* @see utf8_is_valid
+*/
+
+/**
+* Used internally. Builds a PCRE pattern from the $UTF8_SPECIAL_CHARS
+* array defined in this file
+* The $UTF8_SPECIAL_CHARS should contain all special characters (non-letter/non-digit)
+* defined in the various local charsets - it's not a complete list of
+* non-alphanum characters in UTF-8. It's not perfect but should match most
+* cases of special chars.
+* This function adds the control chars 0x00 to 0x19 to the array of
+* special chars (they are not included in $UTF8_SPECIAL_CHARS)
+* @package utf8
+* @subpackage utils
+* @return string
+* @see utf8_from_unicode
+* @see utf8_is_word_chars
+* @see utf8_strip_specials
+*/
+function utf8_specials_pattern()
+{
+	static $pattern = null;
+
+	if (!$pattern)
+	{
+		$UTF8_SPECIAL_CHARS = array(
+			0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, 0x0020, 0x0021, 0x0022, 0x0023,
+			0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c,
+			0x002f,         0x003b, 0x003c, 0x003d, 0x003e, 0x003f, 0x0040, 0x005b,
+			0x005c, 0x005d, 0x005e,         0x0060, 0x007b, 0x007c, 0x007d, 0x007e,
+			0x007f, 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088,
+			0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, 0x0090, 0x0091, 0x0092,
+			0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009a, 0x009b, 0x009c,
+			0x009d, 0x009e, 0x009f, 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6,
+			0x00a7, 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, 0x00b0,
+			0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba,
+			0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, 0x00d7, 0x00f7, 0x02c7, 0x02d8, 0x02d9,
+			0x02da, 0x02db, 0x02dc, 0x02dd, 0x0300, 0x0301, 0x0303, 0x0309, 0x0323, 0x0384,
+			0x0385, 0x0387, 0x03b2, 0x03c6, 0x03d1, 0x03d2, 0x03d5, 0x03d6, 0x05b0, 0x05b1,
+			0x05b2, 0x05b3, 0x05b4, 0x05b5, 0x05b6, 0x05b7, 0x05b8, 0x05b9, 0x05bb, 0x05bc,
+			0x05bd, 0x05be, 0x05bf, 0x05c0, 0x05c1, 0x05c2, 0x05c3, 0x05f3, 0x05f4, 0x060c,
+			0x061b, 0x061f, 0x0640, 0x064b, 0x064c, 0x064d, 0x064e, 0x064f, 0x0650, 0x0651,
+			0x0652, 0x066a, 0x0e3f, 0x200c, 0x200d, 0x200e, 0x200f, 0x2013, 0x2014, 0x2015,
+			0x2017, 0x2018, 0x2019, 0x201a, 0x201c, 0x201d, 0x201e, 0x2020, 0x2021, 0x2022,
+			0x2026, 0x2030, 0x2032, 0x2033, 0x2039, 0x203a, 0x2044, 0x20a7, 0x20aa, 0x20ab,
+			0x20ac, 0x2116, 0x2118, 0x2122, 0x2126, 0x2135, 0x2190, 0x2191, 0x2192, 0x2193,
+			0x2194, 0x2195, 0x21b5, 0x21d0, 0x21d1, 0x21d2, 0x21d3, 0x21d4, 0x2200, 0x2202,
+			0x2203, 0x2205, 0x2206, 0x2207, 0x2208, 0x2209, 0x220b, 0x220f, 0x2211, 0x2212,
+			0x2215, 0x2217, 0x2219, 0x221a, 0x221d, 0x221e, 0x2220, 0x2227, 0x2228, 0x2229,
+			0x222a, 0x222b, 0x2234, 0x223c, 0x2245, 0x2248, 0x2260, 0x2261, 0x2264, 0x2265,
+			0x2282, 0x2283, 0x2284, 0x2286, 0x2287, 0x2295, 0x2297, 0x22a5, 0x22c5, 0x2310,
+			0x2320, 0x2321, 0x2329, 0x232a, 0x2469, 0x2500, 0x2502, 0x250c, 0x2510, 0x2514,
+			0x2518, 0x251c, 0x2524, 0x252c, 0x2534, 0x253c, 0x2550, 0x2551, 0x2552, 0x2553,
+			0x2554, 0x2555, 0x2556, 0x2557, 0x2558, 0x2559, 0x255a, 0x255b, 0x255c, 0x255d,
+			0x255e, 0x255f, 0x2560, 0x2561, 0x2562, 0x2563, 0x2564, 0x2565, 0x2566, 0x2567,
+			0x2568, 0x2569, 0x256a, 0x256b, 0x256c, 0x2580, 0x2584, 0x2588, 0x258c, 0x2590,
+			0x2591, 0x2592, 0x2593, 0x25a0, 0x25b2, 0x25bc, 0x25c6, 0x25ca, 0x25cf, 0x25d7,
+			0x2605, 0x260e, 0x261b, 0x261e, 0x2660, 0x2663, 0x2665, 0x2666, 0x2701, 0x2702,
+			0x2703, 0x2704, 0x2706, 0x2707, 0x2708, 0x2709, 0x270c, 0x270d, 0x270e, 0x270f,
+			0x2710, 0x2711, 0x2712, 0x2713, 0x2714, 0x2715, 0x2716, 0x2717, 0x2718, 0x2719,
+			0x271a, 0x271b, 0x271c, 0x271d, 0x271e, 0x271f, 0x2720, 0x2721, 0x2722, 0x2723,
+			0x2724, 0x2725, 0x2726, 0x2727, 0x2729, 0x272a, 0x272b, 0x272c, 0x272d, 0x272e,
+			0x272f, 0x2730, 0x2731, 0x2732, 0x2733, 0x2734, 0x2735, 0x2736, 0x2737, 0x2738,
+			0x2739, 0x273a, 0x273b, 0x273c, 0x273d, 0x273e, 0x273f, 0x2740, 0x2741, 0x2742,
+			0x2743, 0x2744, 0x2745, 0x2746, 0x2747, 0x2748, 0x2749, 0x274a, 0x274b, 0x274d,
+			0x274f, 0x2750, 0x2751, 0x2752, 0x2756, 0x2758, 0x2759, 0x275a, 0x275b, 0x275c,
+			0x275d, 0x275e, 0x2761, 0x2762, 0x2763, 0x2764, 0x2765, 0x2766, 0x2767, 0x277f,
+			0x2789, 0x2793, 0x2794, 0x2798, 0x2799, 0x279a, 0x279b, 0x279c, 0x279d, 0x279e,
+			0x279f, 0x27a0, 0x27a1, 0x27a2, 0x27a3, 0x27a4, 0x27a5, 0x27a6, 0x27a7, 0x27a8,
+			0x27a9, 0x27aa, 0x27ab, 0x27ac, 0x27ad, 0x27ae, 0x27af, 0x27b1, 0x27b2, 0x27b3,
+			0x27b4, 0x27b5, 0x27b6, 0x27b7, 0x27b8, 0x27b9, 0x27ba, 0x27bb, 0x27bc, 0x27bd,
+			0x27be, 0xf6d9, 0xf6da, 0xf6db, 0xf8d7, 0xf8d8, 0xf8d9, 0xf8da, 0xf8db, 0xf8dc,
+			0xf8dd, 0xf8de, 0xf8df, 0xf8e0, 0xf8e1, 0xf8e2, 0xf8e3, 0xf8e4, 0xf8e5, 0xf8e6,
+			0xf8e7, 0xf8e8, 0xf8e9, 0xf8ea, 0xf8eb, 0xf8ec, 0xf8ed, 0xf8ee, 0xf8ef, 0xf8f0,
+			0xf8f1, 0xf8f2, 0xf8f3, 0xf8f4, 0xf8f5, 0xf8f6, 0xf8f7, 0xf8f8, 0xf8f9, 0xf8fa,
+			0xf8fb, 0xf8fc, 0xf8fd, 0xf8fe, 0xfe7c, 0xfe7d);
+
+		$pattern = preg_quote(utf8_from_unicode($UTF8_SPECIAL_CHARS), '/');
+		$pattern = '/[\x00-\x19'.$pattern.']/u';
+	}
+
+	return $pattern;
+}
+
+/**
+* Checks a string for whether it contains only word characters. This
+* is logically equivalent to the \w PCRE meta character. Note that
+* this is not a 100% guarantee that the string only contains alpha /
+* numeric characters but just that common non-alphanumeric are not
+* in the string, including ASCII device control characters.
+* @package utf8
+* @subpackage utils
+* @param string to check
+* @return boolean TRUE if the string only contains word characters
+* @see utf8_specials_pattern
+*/
+function utf8_is_word_chars($str)
+{
+	return !(bool) preg_match(utf8_specials_pattern(), $str);
+}
+
+/**
+* Removes special characters (nonalphanumeric) from a UTF-8 string
+*
+* This can be useful as a helper for sanitizing a string for use as
+* something like a file name or a unique identifier. Be warned though
+* it does not handle all possible non-alphanumeric characters and is
+* not intended is some kind of security / injection filter.
+*
+* @package utf8
+* @subpackage utils
+* @author Andreas Gohr <andi@splitbrain.org>
+* @param string $string The UTF8 string to strip of special chars
+* @param string (optional) $repl   Replace special with this string
+* @return string with common non-alphanumeric characters removed
+* @see utf8_specials_pattern
+*/
+function utf8_strip_specials($string, $repl='')
+{
+	return preg_replace(utf8_specials_pattern(), $repl, $string);
+}
diff --git a/include/utf8/utils/unicode.php b/include/utf8/utils/unicode.php
new file mode 100644
index 0000000..f0e86cb
--- /dev/null
+++ b/include/utf8/utils/unicode.php
@@ -0,0 +1,241 @@
+<?php
+
+/**
+* @version $Id: unicode.php,v 1.2 2006/02/26 13:20:44 harryf Exp $
+* Tools for conversion between UTF-8 and unicode
+* The Original Code is Mozilla Communicator client code.
+* The Initial Developer of the Original Code is
+* Netscape Communications Corporation.
+* Portions created by the Initial Developer are Copyright (C) 1998
+* the Initial Developer. All Rights Reserved.
+* Ported to PHP by Henri Sivonen (http://hsivonen.iki.fi)
+* Slight modifications to fit with phputf8 library by Harry Fuecks (hfuecks gmail com)
+* @see http://lxr.mozilla.org/seamonkey/source/intl/uconv/src/nsUTF8ToUnicode.cpp
+* @see http://lxr.mozilla.org/seamonkey/source/intl/uconv/src/nsUnicodeToUTF8.cpp
+* @see http://hsivonen.iki.fi/php-utf8/
+* @package utf8
+* @subpackage unicode
+*/
+
+/**
+* Takes an UTF-8 string and returns an array of ints representing the
+* Unicode characters. Astral planes are supported ie. the ints in the
+* output can be > 0xFFFF. Occurrances of the BOM are ignored. Surrogates
+* are not allowed.
+* Returns false if the input string isn't a valid UTF-8 octet sequence
+* and raises a PHP error at level E_USER_WARNING
+* Note: this function has been modified slightly in this library to
+* trigger errors on encountering bad bytes
+* @author <hsivonen@iki.fi>
+* @param string UTF-8 encoded string
+* @return mixed array of unicode code points or FALSE if UTF-8 invalid
+* @see utf8_from_unicode
+* @see http://hsivonen.iki.fi/php-utf8/
+* @package utf8
+* @subpackage unicode
+*/
+function utf8_to_unicode($str)
+{
+	$mState = 0;	// Cached expected number of octets after the current octet
+					// until the beginning of the next UTF8 character sequence
+	$mUcs4  = 0;	// Cached Unicode character
+	$mBytes = 1;	// Cached expected number of octets in the current sequence
+
+	$out = array();
+	$len = strlen($str);
+
+	for($i = 0; $i < $len; $i++)
+	{
+		$in = ord($str[$i]);
+
+		if ($mState == 0)
+		{
+			// When mState is zero we expect either a US-ASCII character or a multi-octet sequence.
+			if (0 == (0x80 & ($in)))
+			{
+				// US-ASCII, pass straight through.
+				$out[] = $in;
+				$mBytes = 1;
+			}
+			else if (0xC0 == (0xE0 & ($in)))
+			{
+				// First octet of 2 octet sequence
+				$mUcs4 = ($in);
+				$mUcs4 = ($mUcs4 & 0x1F) << 6;
+				$mState = 1;
+				$mBytes = 2;
+			}
+			else if (0xE0 == (0xF0 & ($in)))
+			{
+				// First octet of 3 octet sequence
+				$mUcs4 = ($in);
+				$mUcs4 = ($mUcs4 & 0x0F) << 12;
+				$mState = 2;
+				$mBytes = 3;
+			}
+			else if (0xF0 == (0xF8 & ($in)))
+			{
+				// First octet of 4 octet sequence
+				$mUcs4 = ($in);
+				$mUcs4 = ($mUcs4 & 0x07) << 18;
+				$mState = 3;
+				$mBytes = 4;
+			}
+			else if (0xF8 == (0xFC & ($in)))
+			{
+				/* First octet of 5 octet sequence.
+				*
+				* This is illegal because the encoded codepoint must be either
+				* (a) not the shortest form or
+				* (b) outside the Unicode range of 0-0x10FFFF.
+				* Rather than trying to resynchronize, we will carry on until the end
+				* of the sequence and let the later error handling code catch it.
+				*/
+				$mUcs4 = ($in);
+				$mUcs4 = ($mUcs4 & 0x03) << 24;
+				$mState = 4;
+				$mBytes = 5;
+			}
+			else if (0xFC == (0xFE & ($in)))
+			{
+				// First octet of 6 octet sequence, see comments for 5 octet sequence.
+				$mUcs4 = ($in);
+				$mUcs4 = ($mUcs4 & 1) << 30;
+				$mState = 5;
+				$mBytes = 6;
+			}
+			else
+			{
+				// Current octet is neither in the US-ASCII range nor a legal first octet of a multi-octet sequence
+				trigger_error('utf8_to_unicode: Illegal sequence identifier in UTF-8 at byte '.$i, E_USER_WARNING);
+				return false;
+			}
+		}
+		else
+		{
+			// When mState is non-zero, we expect a continuation of the multi-octet sequence
+			if (0x80 == (0xC0 & ($in)))
+			{
+				// Legal continuation.
+				$shift = ($mState - 1) * 6;
+				$tmp = $in;
+				$tmp = ($tmp & 0x0000003F) << $shift;
+				$mUcs4 |= $tmp;
+
+				/**
+				* End of the multi-octet sequence. mUcs4 now contains the final
+				* Unicode codepoint to be output
+				*/
+				if (0 == --$mState)
+				{
+					/*
+					* Check for illegal sequences and codepoints.
+					*/
+					// From Unicode 3.1, non-shortest form is illegal
+					if (((2 == $mBytes) && ($mUcs4 < 0x0080)) || ((3 == $mBytes) && ($mUcs4 < 0x0800)) ||
+					   ((4 == $mBytes) && ($mUcs4 < 0x10000)) || (4 < $mBytes) ||
+					   // From Unicode 3.2, surrogate characters are illegal
+					   (($mUcs4 & 0xFFFFF800) == 0xD800) ||
+					   // Codepoints outside the Unicode range are illegal
+					   ($mUcs4 > 0x10FFFF))
+					{
+						trigger_error('utf8_to_unicode: Illegal sequence or codepoint in UTF-8 at byte '.$i, E_USER_WARNING);
+						return false;
+					}
+
+					// BOM is legal but we don't want to output it
+					if (0xFEFF != $mUcs4)
+						$out[] = $mUcs4;
+
+					// Initialize UTF8 cache
+					$mState = 0;
+					$mUcs4  = 0;
+					$mBytes = 1;
+				}
+
+			}
+			else
+			{
+				/* ((0xC0 & (*in) != 0x80) && (mState != 0))
+				Incomplete multi-octet sequence. */
+				trigger_error('utf8_to_unicode: Incomplete multi-octet sequence in UTF-8 at byte '.$i, E_USER_WARNING);
+				return false;
+			}
+		}
+	}
+
+	return $out;
+}
+
+/**
+* Takes an array of ints representing the Unicode characters and returns
+* a UTF-8 string. Astral planes are supported ie. the ints in the
+* input can be > 0xFFFF. Occurrances of the BOM are ignored. Surrogates
+* are not allowed.
+* Returns false if the input array contains ints that represent
+* surrogates or are outside the Unicode range
+* and raises a PHP error at level E_USER_WARNING
+* Note: this function has been modified slightly in this library to use
+* output buffering to concatenate the UTF-8 string (faster) as well as
+* reference the array by it's keys
+* @param array of unicode code points representing a string
+* @return mixed UTF-8 string or FALSE if array contains invalid code points
+* @author <hsivonen@iki.fi>
+* @see utf8_to_unicode
+* @see http://hsivonen.iki.fi/php-utf8/
+* @package utf8
+* @subpackage unicode
+*/
+function utf8_from_unicode($arr)
+{
+	ob_start();
+
+	foreach (array_keys($arr) as $k)
+	{
+		if ( ($arr[$k] >= 0) && ($arr[$k] <= 0x007f) ) // ASCII range (including control chars)
+		{
+			echo chr($arr[$k]);
+		}
+		else if ($arr[$k] <= 0x07ff) //2 byte sequence
+		{
+			echo chr(0xc0 | ($arr[$k] >> 6));
+			echo chr(0x80 | ($arr[$k] & 0x003f));
+		}
+		else if($arr[$k] == 0xFEFF) // Byte order mark (skip)
+		{
+			// Nop -- zap the BOM
+		}
+		else if ($arr[$k] >= 0xD800 && $arr[$k] <= 0xDFFF) // Test for illegal surrogates
+		{
+			// Found a surrogate
+			trigger_error('utf8_from_unicode: Illegal surrogate at index: '.$k.', value: '.$arr[$k], E_USER_WARNING);
+
+			return false;
+		}
+		else if ($arr[$k] <= 0xffff) // 3 byte sequence
+		{
+			echo chr(0xe0 | ($arr[$k] >> 12));
+			echo chr(0x80 | (($arr[$k] >> 6) & 0x003f));
+			echo chr(0x80 | ($arr[$k] & 0x003f));
+		}
+		else if ($arr[$k] <= 0x10ffff) // 4 byte sequence
+		{
+			echo chr(0xf0 | ($arr[$k] >> 18));
+			echo chr(0x80 | (($arr[$k] >> 12) & 0x3f));
+			echo chr(0x80 | (($arr[$k] >> 6) & 0x3f));
+			echo chr(0x80 | ($arr[$k] & 0x3f));
+		}
+		else
+		{
+			trigger_error('utf8_from_unicode: Codepoint out of Unicode range at index: '.$k.', value: '.$arr[$k], E_USER_WARNING);
+
+			// Out of range
+			return false;
+		}
+	}
+
+	$result = ob_get_contents();
+	ob_end_clean();
+
+	return $result;
+}
diff --git a/include/utf8/utils/validation.php b/include/utf8/utils/validation.php
new file mode 100644
index 0000000..90dce8e
--- /dev/null
+++ b/include/utf8/utils/validation.php
@@ -0,0 +1,186 @@
+<?php
+
+/**
+* @version $Id: validation.php,v 1.2 2006/02/26 13:20:44 harryf Exp $
+* Tools for validing a UTF-8 string is well formed.
+* The Original Code is Mozilla Communicator client code.
+* The Initial Developer of the Original Code is
+* Netscape Communications Corporation.
+* Portions created by the Initial Developer are Copyright (C) 1998
+* the Initial Developer. All Rights Reserved.
+* Ported to PHP by Henri Sivonen (http://hsivonen.iki.fi)
+* Slight modifications to fit with phputf8 library by Harry Fuecks (hfuecks gmail com)
+* @see http://lxr.mozilla.org/seamonkey/source/intl/uconv/src/nsUTF8ToUnicode.cpp
+* @see http://lxr.mozilla.org/seamonkey/source/intl/uconv/src/nsUnicodeToUTF8.cpp
+* @see http://hsivonen.iki.fi/php-utf8/
+* @package utf8
+* @subpackage validation
+*/
+
+/**
+* Tests a string as to whether it's valid UTF-8 and supported by the
+* Unicode standard
+* Note: this function has been modified to simple return true or false
+* @author <hsivonen@iki.fi>
+* @param string UTF-8 encoded string
+* @return boolean true if valid
+* @see http://hsivonen.iki.fi/php-utf8/
+* @see utf8_compliant
+* @package utf8
+* @subpackage validation
+*/
+function utf8_is_valid($str)
+{
+	$mState = 0;	// Cached expected number of octets after the current octet
+					// until the beginning of the next UTF8 character sequence
+	$mUcs4  = 0;	// Cached Unicode character
+	$mBytes = 1;	// Cached expected number of octets in the current sequence
+
+	$len = strlen($str);
+
+	for($i = 0; $i < $len; $i++)
+	{
+		$in = ord($str{$i});
+
+		if ( $mState == 0)
+		{
+			// When mState is zero we expect either a US-ASCII character or a multi-octet sequence.
+			if (0 == (0x80 & ($in)))
+			{
+				$mBytes = 1; // US-ASCII, pass straight through
+			}
+			else if (0xC0 == (0xE0 & ($in)))
+			{
+				// First octet of 2 octet sequence
+				$mUcs4 = ($in);
+				$mUcs4 = ($mUcs4 & 0x1F) << 6;
+				$mState = 1;
+				$mBytes = 2;
+			}
+			else if (0xE0 == (0xF0 & ($in)))
+			{
+				// First octet of 3 octet sequence
+				$mUcs4 = ($in);
+				$mUcs4 = ($mUcs4 & 0x0F) << 12;
+				$mState = 2;
+				$mBytes = 3;
+			}
+			else if (0xF0 == (0xF8 & ($in)))
+			{
+				// First octet of 4 octet sequence
+				$mUcs4 = ($in);
+				$mUcs4 = ($mUcs4 & 0x07) << 18;
+				$mState = 3;
+				$mBytes = 4;
+			}
+			else if (0xF8 == (0xFC & ($in)))
+			{
+				/* First octet of 5 octet sequence.
+				*
+				* This is illegal because the encoded codepoint must be either
+				* (a) not the shortest form or
+				* (b) outside the Unicode range of 0-0x10FFFF.
+				* Rather than trying to resynchronize, we will carry on until the end
+				* of the sequence and let the later error handling code catch it.
+				*/
+				$mUcs4 = ($in);
+				$mUcs4 = ($mUcs4 & 0x03) << 24;
+				$mState = 4;
+				$mBytes = 5;
+			}
+			else if (0xFC == (0xFE & ($in)))
+			{
+				// First octet of 6 octet sequence, see comments for 5 octet sequence.
+				$mUcs4 = ($in);
+				$mUcs4 = ($mUcs4 & 1) << 30;
+				$mState = 5;
+				$mBytes = 6;
+			}
+			else
+			{
+				// Current octet is neither in the US-ASCII range nor a legal first octet of a multi-octet sequence.
+				return false;
+			}
+		}
+		else
+		{
+			// When mState is non-zero, we expect a continuation of the multi-octet sequence
+			if (0x80 == (0xC0 & ($in)))
+			{
+				// Legal continuation.
+				$shift = ($mState - 1) * 6;
+				$tmp = $in;
+				$tmp = ($tmp & 0x0000003F) << $shift;
+				$mUcs4 |= $tmp;
+
+				/**
+				* End of the multi-octet sequence. mUcs4 now contains the final
+				* Unicode codepoint to be output
+				*/
+				if (0 == --$mState)
+				{
+					/*
+					* Check for illegal sequences and codepoints.
+					*/
+					// From Unicode 3.1, non-shortest form is illegal
+					if (((2 == $mBytes) && ($mUcs4 < 0x0080)) || ((3 == $mBytes) && ($mUcs4 < 0x0800)) ||
+					((4 == $mBytes) && ($mUcs4 < 0x10000)) || (4 < $mBytes) ||
+					// From Unicode 3.2, surrogate characters are illegal
+					(($mUcs4 & 0xFFFFF800) == 0xD800) ||
+					// Codepoints outside the Unicode range are illegal
+					($mUcs4 > 0x10FFFF))
+					{
+						return FALSE;
+					}
+
+					// Initialize UTF8 cache
+					$mState = 0;
+					$mUcs4  = 0;
+					$mBytes = 1;
+				}
+			}
+			else
+			{
+				/**
+				*((0xC0 & (*in) != 0x80) && (mState != 0))
+				* Incomplete multi-octet sequence.
+				*/
+
+				return false;
+			}
+		}
+	}
+
+	return true;
+}
+
+/**
+* Tests whether a string complies as UTF-8. This will be much
+* faster than utf8_is_valid, but will pass five and six octet
+* UTF-8 sequences, which are not supported by Unicode and
+* so cannot be displayed correctly in a browser. In other words
+* it is not as strict as utf8_is_valid but it's faster. If you use
+* is to validate user input, you place yourself at the risk that
+* attackers will be able to inject 5 and 6 byte sequences (which
+* may or may not be a significant risk, depending on what you are
+* are doing)
+* Note: Does not pass five and six octet UTF-8 sequences anymore in
+*       in the unit tests.
+* @see utf8_is_valid
+* @see http://www.php.net/manual/en/reference.pcre.pattern.modifiers.php#54805
+* @param string UTF-8 string to check
+* @return boolean TRUE if string is valid UTF-8
+* @package utf8
+* @subpackage validation
+*/
+function utf8_compliant($str)
+{
+	if (strlen($str) == 0)
+		return true;
+
+	// If even just the first character can be matched, when the /u
+	// modifier is used, then it's valid UTF-8. If the UTF-8 is somehow
+	// invalid, nothing at all will match, even if the string contains
+	// some valid sequences
+	return (preg_match('/^.{1}/us', $str, $ar) == 1);
+}
diff --git a/index.php b/index.php
new file mode 100644
index 0000000..0f257ac
--- /dev/null
+++ b/index.php
@@ -0,0 +1,265 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+define('PUN_ROOT', dirname(__FILE__).'/');
+require PUN_ROOT.'include/common.php';
+
+
+if ($pun_user['g_read_board'] == '0')
+	message($lang_common['No view'], false, '403 Forbidden');
+
+
+// Load the index.php language file
+require PUN_ROOT.'lang/'.$pun_user['language'].'/index.php';
+
+// Get list of forums and topics with new posts since last visit
+if (!$pun_user['is_guest'])
+{
+	$result = $db->query('SELECT t.forum_id, t.id, t.last_post FROM '.$db->prefix.'topics AS t INNER JOIN '.$db->prefix.'forums AS f ON f.id=t.forum_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 t.last_post>'.$pun_user['last_visit'].' AND t.moved_to IS NULL') or error('Unable to fetch new topics', __FILE__, __LINE__, $db->error());
+
+	$new_topics = array();
+	while ($cur_topic = $db->fetch_assoc($result))
+		$new_topics[$cur_topic['forum_id']][$cur_topic['id']] = $cur_topic['last_post'];
+
+	$tracked_topics = get_tracked_topics();
+}
+
+if ($pun_config['o_feed_type'] == '1')
+	$page_head = array('feed' => '<link rel="alternate" type="application/rss+xml" href="extern.php?action=feed&amp;type=rss" title="'.$lang_common['RSS active topics feed'].'" />');
+else if ($pun_config['o_feed_type'] == '2')
+	$page_head = array('feed' => '<link rel="alternate" type="application/atom+xml" href="extern.php?action=feed&amp;type=atom" title="'.$lang_common['Atom active topics feed'].'" />');
+
+$forum_actions = array();
+
+// Display a "mark all as read" link
+if (!$pun_user['is_guest'])
+	$forum_actions[] = '<a href="misc.php?action=markread">'.$lang_common['Mark all as read'].'</a>';
+
+$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']));
+define('PUN_ALLOW_INDEX', 1);
+define('PUN_ACTIVE_PAGE', 'index');
+require PUN_ROOT.'header.php';
+
+// Print the categories and forums
+$result = $db->query('SELECT c.id AS cid, c.cat_name, f.id AS fid, f.forum_name, f.forum_desc, f.redirect_url, f.moderators, f.num_topics, f.num_posts, f.last_post, f.last_post_id, f.last_poster 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 ORDER BY c.disp_position, c.id, f.disp_position', true) or error('Unable to fetch category/forum list', __FILE__, __LINE__, $db->error());
+
+$cur_category = 0;
+$cat_count = 0;
+$forum_count = 0;
+while ($cur_forum = $db->fetch_assoc($result))
+{
+	$moderators = '';
+
+	if ($cur_forum['cid'] != $cur_category) // A new category since last iteration?
+	{
+		if ($cur_category != 0)
+			echo "\t\t\t".'</tbody>'."\n\t\t\t".'</table>'."\n\t\t".'</div>'."\n\t".'</div>'."\n".'</div>'."\n\n";
+
+		++$cat_count;
+		$forum_count = 0;
+
+?>
+<div id="idx<?php echo $cat_count ?>" class="blocktable">
+	<h2><span><?php echo pun_htmlspecialchars($cur_forum['cat_name']) ?></span></h2>
+	<div class="box">
+		<div class="inbox">
+			<table cellspacing="0">
+			<thead>
+				<tr>
+					<th class="tcl" scope="col"><?php echo $lang_common['Forum'] ?></th>
+					<th class="tc2" scope="col"><?php echo $lang_index['Topics'] ?></th>
+					<th class="tc3" scope="col"><?php echo $lang_common['Posts'] ?></th>
+					<th class="tcr" scope="col"><?php echo $lang_common['Last post'] ?></th>
+				</tr>
+			</thead>
+			<tbody>
+<?php
+
+		$cur_category = $cur_forum['cid'];
+	}
+
+	++$forum_count;
+	$item_status = ($forum_count % 2 == 0) ? 'roweven' : 'rowodd';
+	$forum_field_new = '';
+	$icon_type = 'icon';
+
+	// Are there new posts since our last visit?
+	if (!$pun_user['is_guest'] && $cur_forum['last_post'] > $pun_user['last_visit'] && (empty($tracked_topics['forums'][$cur_forum['fid']]) || $cur_forum['last_post'] > $tracked_topics['forums'][$cur_forum['fid']]))
+	{
+		// There are new posts in this forum, but have we read all of them already?
+		foreach ($new_topics[$cur_forum['fid']] as $check_topic_id => $check_last_post)
+		{
+			if ((empty($tracked_topics['topics'][$check_topic_id]) || $tracked_topics['topics'][$check_topic_id] < $check_last_post) && (empty($tracked_topics['forums'][$cur_forum['fid']]) || $tracked_topics['forums'][$cur_forum['fid']] < $check_last_post))
+			{
+				$item_status .= ' inew';
+				$forum_field_new = '<span class="newtext">[ <a href="search.php?action=show_new&amp;fid='.$cur_forum['fid'].'">'.$lang_common['New posts'].'</a> ]</span>';
+				$icon_type = 'icon icon-new';
+
+				break;
+			}
+		}
+	}
+
+	// Is this a redirect forum?
+	if ($cur_forum['redirect_url'] != '')
+	{
+		$forum_field = '<h3><span class="redirtext">'.$lang_index['Link to'].'</span> <a href="'.pun_htmlspecialchars($cur_forum['redirect_url']).'" title="'.$lang_index['Link to'].' '.pun_htmlspecialchars($cur_forum['redirect_url']).'">'.pun_htmlspecialchars($cur_forum['forum_name']).'</a></h3>';
+		$num_topics = $num_posts = '-';
+		$item_status .= ' iredirect';
+		$icon_type = 'icon';
+	}
+	else
+	{
+		$forum_field = '<h3><a href="viewforum.php?id='.$cur_forum['fid'].'">'.pun_htmlspecialchars($cur_forum['forum_name']).'</a>'.(!empty($forum_field_new) ? ' '.$forum_field_new : '').'</h3>';
+		$num_topics = $cur_forum['num_topics'];
+		$num_posts = $cur_forum['num_posts'];
+	}
+
+	if ($cur_forum['forum_desc'] != '')
+		$forum_field .= "\n\t\t\t\t\t\t\t\t".'<div class="forumdesc">'.$cur_forum['forum_desc'].'</div>';
+
+	// If there is a last_post/last_poster
+	if ($cur_forum['last_post'] != '')
+		$last_post = '<a href="viewtopic.php?pid='.$cur_forum['last_post_id'].'#p'.$cur_forum['last_post_id'].'">'.format_time($cur_forum['last_post']).'</a> <span class="byuser">'.$lang_common['by'].' '.pun_htmlspecialchars($cur_forum['last_poster']).'</span>';
+	else if ($cur_forum['redirect_url'] != '')
+		$last_post = '- - -';
+	else
+		$last_post = $lang_common['Never'];
+
+	if ($cur_forum['moderators'] != '')
+	{
+		$mods_array = unserialize($cur_forum['moderators']);
+		$moderators = array();
+
+		foreach ($mods_array as $mod_username => $mod_id)
+		{
+			if ($pun_user['g_view_users'] == '1')
+				$moderators[] = '<a href="profile.php?id='.$mod_id.'">'.pun_htmlspecialchars($mod_username).'</a>';
+			else
+				$moderators[] = pun_htmlspecialchars($mod_username);
+		}
+
+		$moderators = "\t\t\t\t\t\t\t\t".'<p class="modlist">(<em>'.$lang_common['Moderated by'].'</em> '.implode(', ', $moderators).')</p>'."\n";
+	}
+
+?>
+				<tr class="<?php echo $item_status ?>">
+					<td class="tcl">
+						<div class="<?php echo $icon_type ?>"><div class="nosize"><?php echo forum_number_format($forum_count) ?></div></div>
+						<div class="tclcon">
+							<div>
+								<?php echo $forum_field."\n".$moderators ?>
+							</div>
+						</div>
+					</td>
+					<td class="tc2"><?php echo forum_number_format($num_topics) ?></td>
+					<td class="tc3"><?php echo forum_number_format($num_posts) ?></td>
+					<td class="tcr"><?php echo $last_post ?></td>
+				</tr>
+<?php
+
+}
+
+// Did we output any categories and forums?
+if ($cur_category > 0)
+	echo "\t\t\t".'</tbody>'."\n\t\t\t".'</table>'."\n\t\t".'</div>'."\n\t".'</div>'."\n".'</div>'."\n\n";
+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
+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';
+
+	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);
+
+if ($pun_user['g_view_users'] == '1')
+	$stats['newest_user'] = '<a href="profile.php?id='.$stats['last_user']['id'].'">'.pun_htmlspecialchars($stats['last_user']['username']).'</a>';
+else
+	$stats['newest_user'] = pun_htmlspecialchars($stats['last_user']['username']);
+
+if (!empty($forum_actions))
+{
+
+?>
+<div class="linksb">
+	<div class="inbox crumbsplus">
+		<p class="subscribelink clearb"><?php echo implode(' - ', $forum_actions); ?></p>
+	</div>
+</div>
+<?php
+
+}
+
+?>
+<div id="brdstats" class="block">
+	<h2><span><?php echo $lang_index['Board info'] ?></span></h2>
+	<div class="box">
+		<div class="inbox">
+			<dl class="conr">
+				<dt><strong><?php echo $lang_index['Board stats'] ?></strong></dt>
+				<dd><span><?php printf($lang_index['No of users'], '<strong>'.forum_number_format($stats['total_users']).'</strong>') ?></span></dd>
+				<dd><span><?php printf($lang_index['No of topics'], '<strong>'.forum_number_format($stats['total_topics']).'</strong>') ?></span></dd>
+				<dd><span><?php printf($lang_index['No of posts'], '<strong>'.forum_number_format($stats['total_posts']).'</strong>') ?></span></dd>
+			</dl>
+			<dl class="conl">
+				<dt><strong><?php echo $lang_index['User info'] ?></strong></dt>
+				<dd><span><?php printf($lang_index['Newest user'], $stats['newest_user']) ?></span></dd>
+<?php
+
+if ($pun_config['o_users_online'] == '1')
+{
+	// Fetch users online info and generate strings for output
+	$num_guests = 0;
+	$users = array();
+	$result = $db->query('SELECT user_id, ident FROM '.$db->prefix.'online WHERE idle=0 ORDER BY ident', true) or error('Unable to fetch online list', __FILE__, __LINE__, $db->error());
+
+	while ($pun_user_online = $db->fetch_assoc($result))
+	{
+		if ($pun_user_online['user_id'] > 1)
+		{
+			if ($pun_user['g_view_users'] == '1')
+				$users[] = "\n\t\t\t\t".'<dd><a href="profile.php?id='.$pun_user_online['user_id'].'">'.pun_htmlspecialchars($pun_user_online['ident']).'</a>';
+			else
+				$users[] = "\n\t\t\t\t".'<dd>'.pun_htmlspecialchars($pun_user_online['ident']);
+		}
+		else
+			++$num_guests;
+	}
+
+	$num_users = count($users);
+	echo "\t\t\t\t".'<dd><span>'.sprintf($lang_index['Users online'], '<strong>'.forum_number_format($num_users).'</strong>').'</span></dd>'."\n\t\t\t\t".'<dd><span>'.sprintf($lang_index['Guests online'], '<strong>'.forum_number_format($num_guests).'</strong>').'</span></dd>'."\n\t\t\t".'</dl>'."\n";
+
+
+	if ($num_users > 0)
+		echo "\t\t\t".'<dl id="onlinelist" class="clearb">'."\n\t\t\t\t".'<dt><strong>'.$lang_index['Online'].' </strong></dt>'."\t\t\t\t".implode(',</dd> ', $users).'</dd>'."\n\t\t\t".'</dl>'."\n";
+	else
+		echo "\t\t\t".'<div class="clearer"></div>'."\n";
+
+}
+else
+	echo "\t\t\t".'</dl>'."\n\t\t\t".'<div class="clearer"></div>'."\n";
+
+
+?>
+		</div>
+	</div>
+</div>
+<?php
+
+$footer_style = 'index';
+require PUN_ROOT.'footer.php';
diff --git a/install.php b/install.php
new file mode 100644
index 0000000..1fb2543
--- /dev/null
+++ b/install.php
@@ -0,0 +1,1822 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+// The FluxBB version this script installs
+define('FORUM_VERSION', '1.4.12');
+
+define('FORUM_DB_REVISION', 15);
+define('FORUM_SI_REVISION', 2);
+define('FORUM_PARSER_REVISION', 2);
+
+define('MIN_PHP_VERSION', '4.4.0');
+define('MIN_MYSQL_VERSION', '4.1.2');
+define('MIN_PGSQL_VERSION', '7.0.0');
+define('PUN_SEARCH_MIN_WORD', 3);
+define('PUN_SEARCH_MAX_WORD', 20);
+
+
+define('PUN_ROOT', dirname(__FILE__).'/');
+
+// Load the functions script
+require PUN_ROOT.'include/functions.php';
+
+// Load UTF-8 functions
+require PUN_ROOT.'include/utf8/utf8.php';
+
+// Strip out "bad" UTF-8 characters
+forum_remove_bad_characters();
+
+// Reverse the effect of register_globals
+forum_unregister_globals();
+
+// Disable error reporting for uninitialized variables
+error_reporting(E_ALL);
+
+// Force POSIX locale (to prevent functions such as strtolower() from messing up UTF-8 strings)
+setlocale(LC_CTYPE, 'C');
+
+// Turn off magic_quotes_runtime
+if (get_magic_quotes_runtime())
+	set_magic_quotes_runtime(0);
+
+// Strip slashes from GET/POST/COOKIE (if magic_quotes_gpc is enabled)
+if (get_magic_quotes_gpc())
+{
+	function stripslashes_array($array)
+	{
+		return is_array($array) ? array_map('stripslashes_array', $array) : stripslashes($array);
+	}
+
+	$_GET = stripslashes_array($_GET);
+	$_POST = stripslashes_array($_POST);
+	$_COOKIE = stripslashes_array($_COOKIE);
+	$_REQUEST = stripslashes_array($_REQUEST);
+}
+
+// Turn off PHP time limit
+@set_time_limit(0);
+
+
+// If we've been passed a default language, use it
+$install_lang = isset($_REQUEST['install_lang']) ? pun_trim($_REQUEST['install_lang']) : 'English';
+
+// If such a language pack doesn't exist, or isn't up-to-date enough to translate this page, default to English
+if (!file_exists(PUN_ROOT.'lang/'.$install_lang.'/install.php'))
+	$install_lang = 'English';
+
+require PUN_ROOT.'lang/'.$install_lang.'/install.php';
+
+if (file_exists(PUN_ROOT.'config.php'))
+{
+	// Check to see whether FluxBB is already installed
+	include PUN_ROOT.'config.php';
+
+	// If we have the 1.3-legacy constant defined, define the proper 1.4 constant so we don't get an incorrect "need to install" message
+	if (defined('FORUM'))
+		define('PUN', FORUM);
+
+	// If PUN is defined, config.php is probably valid and thus the software is installed
+	if (defined('PUN'))
+		exit($lang_install['Already installed']);
+}
+
+// Define PUN because email.php requires it
+define('PUN', 1);
+
+// If the cache directory is not specified, we use the default setting
+if (!defined('FORUM_CACHE_DIR'))
+	define('FORUM_CACHE_DIR', PUN_ROOT.'cache/');
+
+// Make sure we are running at least MIN_PHP_VERSION
+if (!function_exists('version_compare') || version_compare(PHP_VERSION, MIN_PHP_VERSION, '<'))
+	exit(sprintf($lang_install['You are running error'], 'PHP', PHP_VERSION, FORUM_VERSION, MIN_PHP_VERSION));
+
+
+//
+// Generate output to be used for config.php
+//
+function generate_config_file()
+{
+	global $db_type, $db_host, $db_name, $db_username, $db_password, $db_prefix, $cookie_name, $cookie_seed;
+
+	return '<?php'."\n\n".'$db_type = \''.$db_type."';\n".'$db_host = \''.$db_host."';\n".'$db_name = \''.addslashes($db_name)."';\n".'$db_username = \''.addslashes($db_username)."';\n".'$db_password = \''.addslashes($db_password)."';\n".'$db_prefix = \''.addslashes($db_prefix)."';\n".'$p_connect = false;'."\n\n".'$cookie_name = '."'".$cookie_name."';\n".'$cookie_domain = '."'';\n".'$cookie_path = '."'/';\n".'$cookie_secure = 0;'."\n".'$cookie_seed = \''.random_key(16, false, true)."';\n\ndefine('PUN', 1);\n";
+}
+
+
+if (isset($_POST['generate_config']))
+{
+	header('Content-Type: text/x-delimtext; name="config.php"');
+	header('Content-disposition: attachment; filename=config.php');
+
+	$db_type = $_POST['db_type'];
+	$db_host = $_POST['db_host'];
+	$db_name = $_POST['db_name'];
+	$db_username = $_POST['db_username'];
+	$db_password = $_POST['db_password'];
+	$db_prefix = $_POST['db_prefix'];
+	$cookie_name = $_POST['cookie_name'];
+	$cookie_seed = $_POST['cookie_seed'];
+
+	echo generate_config_file();
+	exit;
+}
+
+
+if (!isset($_POST['form_sent']))
+{
+	// Make an educated guess regarding base_url
+	$base_url  = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://';	// protocol
+	$base_url .= preg_replace('%:(80|443)$%', '', $_SERVER['HTTP_HOST']);							// host[:port]
+	$base_url .= str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME']));							// path
+
+	if (substr($base_url, -1) == '/')
+		$base_url = substr($base_url, 0, -1);
+
+	$db_type = $db_name = $db_username = $db_prefix = $username = $email = '';
+	$db_host = 'localhost';
+	$title = $lang_install['My FluxBB Forum'];
+	$description = '<p><span>'.$lang_install['Description'].'</span></p>';
+	$default_lang = $install_lang;
+	$default_style = 'Air';
+}
+else
+{
+	$db_type = $_POST['req_db_type'];
+	$db_host = pun_trim($_POST['req_db_host']);
+	$db_name = pun_trim($_POST['req_db_name']);
+	$db_username = pun_trim($_POST['db_username']);
+	$db_password = pun_trim($_POST['db_password']);
+	$db_prefix = pun_trim($_POST['db_prefix']);
+	$username = pun_trim($_POST['req_username']);
+	$email = strtolower(pun_trim($_POST['req_email']));
+	$password1 = pun_trim($_POST['req_password1']);
+	$password2 = pun_trim($_POST['req_password2']);
+	$title = pun_trim($_POST['req_title']);
+	$description = pun_trim($_POST['desc']);
+	$base_url = pun_trim($_POST['req_base_url']);
+	$default_lang = pun_trim($_POST['req_default_lang']);
+	$default_style = pun_trim($_POST['req_default_style']);
+	$alerts = array();
+
+	// Make sure base_url doesn't end with a slash
+	if (substr($base_url, -1) == '/')
+		$base_url = substr($base_url, 0, -1);
+
+	// Validate username and passwords
+	if (pun_strlen($username) < 2)
+		$alerts[] = $lang_install['Username 1'];
+	else if (pun_strlen($username) > 25) // This usually doesn't happen since the form element only accepts 25 characters
+		$alerts[] = $lang_install['Username 2'];
+	else if (!strcasecmp($username, 'Guest'))
+		$alerts[] = $lang_install['Username 3'];
+	else if (preg_match('%[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}%', $username) || preg_match('%((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))%', $username))
+		$alerts[] = $lang_install['Username 4'];
+	else if ((strpos($username, '[') !== false || strpos($username, ']') !== false) && strpos($username, '\'') !== false && strpos($username, '"') !== false)
+		$alerts[] = $lang_install['Username 5'];
+	else if (preg_match('%(?:\[/?(?:b|u|i|h|colou?r|quote|code|img|url|email|list)\]|\[(?:code|quote|list)=)%i', $username))
+		$alerts[] = $lang_install['Username 6'];
+
+	if (pun_strlen($password1) < 4)
+		$alerts[] = $lang_install['Short password'];
+	else if ($password1 != $password2)
+		$alerts[] = $lang_install['Passwords not match'];
+
+	// Validate email
+	require PUN_ROOT.'include/email.php';
+
+	if (!is_valid_email($email))
+		$alerts[] = $lang_install['Wrong email'];
+
+	if ($title == '')
+		$alerts[] = $lang_install['No board title'];
+
+	$languages = forum_list_langs();
+	if (!in_array($default_lang, $languages))
+		$alerts[] = $lang_install['Error default language'];
+
+	$styles = forum_list_styles();
+	if (!in_array($default_style, $styles))
+		$alerts[] = $lang_install['Error default style'];
+}
+
+// Check if the cache directory is writable
+if (!forum_is_writable(FORUM_CACHE_DIR))
+	$alerts[] = sprintf($lang_install['Alert cache'], FORUM_CACHE_DIR);
+
+// Check if default avatar directory is writable
+if (!forum_is_writable(PUN_ROOT.'img/avatars/'))
+	$alerts[] = sprintf($lang_install['Alert avatar'], PUN_ROOT.'img/avatars/');
+
+if (!isset($_POST['form_sent']) || !empty($alerts))
+{
+	// Determine available database extensions
+	$dual_mysql = false;
+	$db_extensions = array();
+	$mysql_innodb = false;
+	if (function_exists('mysqli_connect'))
+	{
+		$db_extensions[] = array('mysqli', 'MySQL Improved');
+		$db_extensions[] = array('mysqli_innodb', 'MySQL Improved (InnoDB)');
+		$mysql_innodb = true;
+	}
+	if (function_exists('mysql_connect'))
+	{
+		$db_extensions[] = array('mysql', 'MySQL Standard');
+		$db_extensions[] = array('mysql_innodb', 'MySQL Standard (InnoDB)');
+		$mysql_innodb = true;
+
+		if (count($db_extensions) > 2)
+			$dual_mysql = true;
+	}
+	if (function_exists('sqlite_open'))
+		$db_extensions[] = array('sqlite', 'SQLite');
+	if (function_exists('pg_connect'))
+		$db_extensions[] = array('pgsql', 'PostgreSQL');
+
+	if (empty($db_extensions))
+		error($lang_install['No DB extensions']);
+
+	// Fetch a list of installed languages
+	$languages = forum_list_langs();
+
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<title><?php echo $lang_install['FluxBB Installation'] ?></title>
+<link rel="stylesheet" type="text/css" href="style/<?php echo $default_style ?>.css" />
+<script type="text/javascript">
+/* <![CDATA[ */
+function process_form(the_form)
+{
+	var required_fields = {
+		"req_db_type": "<?php echo $lang_install['Database type'] ?>",
+		"req_db_host": "<?php echo $lang_install['Database server hostname'] ?>",
+		"req_db_name": "<?php echo $lang_install['Database name'] ?>",
+		"req_username": "<?php echo $lang_install['Administrator username'] ?>",
+		"req_password1": "<?php echo $lang_install['Administrator password 1'] ?>",
+		"req_password2": "<?php echo $lang_install['Administrator password 2'] ?>",
+		"req_email": "<?php echo $lang_install['Administrator email'] ?>",
+		"req_title": "<?php echo $lang_install['Board title'] ?>",
+		"req_base_url": "<?php echo $lang_install['Base URL'] ?>"
+	};
+	if (document.all || document.getElementById)
+	{
+		for (var i = 0; i < the_form.length; ++i)
+		{
+			var elem = the_form.elements[i];
+			if (elem.name && required_fields[elem.name] && !elem.value && elem.type && (/^(?:text(?:area)?|password|file)$/i.test(elem.type)))
+			{
+				alert('"' + required_fields[elem.name] + '" <?php echo $lang_install['Required field'] ?>');
+				elem.focus();
+				return false;
+			}
+		}
+	}
+	return true;
+}
+/* ]]> */
+</script>
+</head>
+<body onload="document.getElementById('install').req_db_type.focus();document.getElementById('install').start.disabled=false;" onunload="">
+
+<div id="puninstall" class="pun">
+<div class="top-box"><div><!-- Top Corners --></div></div>
+<div class="punwrap">
+
+<div id="brdheader" class="block">
+	<div class="box">
+		<div id="brdtitle" class="inbox">
+			<h1><span><?php echo $lang_install['FluxBB Installation'] ?></span></h1>
+			<div id="brddesc"><p><?php echo $lang_install['Install message'] ?></p><p><?php echo $lang_install['Welcome'] ?></p></div>
+		</div>
+	</div>
+</div>
+
+<div id="brdmain">
+<?php if (count($languages) > 1): ?><div class="blockform">
+	<h2><span><?php echo $lang_install['Choose install language'] ?></span></h2>
+	<div class="box">
+		<form id="install" method="post" action="install.php">
+			<div class="inform">
+				<fieldset>
+					<legend><?php echo $lang_install['Install language'] ?></legend>
+					<div class="infldset">
+						<p><?php echo $lang_install['Choose install language info'] ?></p>
+						<label><strong><?php echo $lang_install['Install language'] ?></strong>
+						<br /><select name="install_lang">
+<?php
+
+		foreach ($languages as $temp)
+		{
+			if ($temp == $install_lang)
+				echo "\t\t\t\t\t".'<option value="'.$temp.'" selected="selected">'.$temp.'</option>'."\n";
+			else
+				echo "\t\t\t\t\t".'<option value="'.$temp.'">'.$temp.'</option>'."\n";
+		}
+
+?>
+						</select>
+						<br /></label>
+					</div>
+				</fieldset>
+			</div>
+			<p class="buttons"><input type="submit" name="start" value="<?php echo $lang_install['Change language'] ?>" /></p>
+		</form>
+	</div>
+</div>
+<?php endif; ?>
+
+<div class="blockform">
+	<h2><span><?php echo sprintf($lang_install['Install'], FORUM_VERSION) ?></span></h2>
+	<div class="box">
+		<form id="install" method="post" action="install.php" onsubmit="this.start.disabled=true;if(process_form(this)){return true;}else{this.start.disabled=false;return false;}">
+		<div><input type="hidden" name="form_sent" value="1" /><input type="hidden" name="install_lang" value="<?php echo pun_htmlspecialchars($install_lang) ?>" /></div>
+			<div class="inform">
+<?php if (!empty($alerts)): ?>				<div class="forminfo error-info">
+					<h3><?php echo $lang_install['Errors'] ?></h3>
+					<ul class="error-list">
+<?php
+
+foreach ($alerts as $cur_alert)
+	echo "\t\t\t\t\t\t".'<li><strong>'.$cur_alert.'</strong></li>'."\n";
+?>
+					</ul>
+				</div>
+<?php endif; ?>			</div>
+			<div class="inform">
+				<div class="forminfo">
+					<h3><?php echo $lang_install['Database setup'] ?></h3>
+					<p><?php echo $lang_install['Info 1'] ?></p>
+				</div>
+				<fieldset>
+				<legend><?php echo $lang_install['Select database'] ?></legend>
+					<div class="infldset">
+						<p><?php echo $lang_install['Info 2'] ?></p>
+<?php if ($dual_mysql): ?>						<p><?php echo $lang_install['Dual MySQL'] ?></p>
+<?php endif; ?><?php if ($mysql_innodb): ?>						<p><?php echo $lang_install['InnoDB'] ?></p>
+<?php endif; ?>						<label class="required"><strong><?php echo $lang_install['Database type'] ?> <span><?php echo $lang_install['Required'] ?></span></strong>
+						<br /><select name="req_db_type">
+<?php
+
+	foreach ($db_extensions as $temp)
+	{
+		if ($temp[0] == $db_type)
+			echo "\t\t\t\t\t\t\t".'<option value="'.$temp[0].'" selected="selected">'.$temp[1].'</option>'."\n";
+		else
+			echo "\t\t\t\t\t\t\t".'<option value="'.$temp[0].'">'.$temp[1].'</option>'."\n";
+	}
+
+?>
+						</select>
+						<br /></label>
+					</div>
+				</fieldset>
+			</div>
+			<div class="inform">
+				<fieldset>
+					<legend><?php echo $lang_install['Database hostname'] ?></legend>
+					<div class="infldset">
+						<p><?php echo $lang_install['Info 3'] ?></p>
+						<label class="required"><strong><?php echo $lang_install['Database server hostname'] ?> <span><?php echo $lang_install['Required'] ?></span></strong><br /><input type="text" name="req_db_host" value="<?php echo pun_htmlspecialchars($db_host) ?>" size="50" /><br /></label>
+					</div>
+				</fieldset>
+			</div>
+			<div class="inform">
+				<fieldset>
+					<legend><?php echo $lang_install['Database enter name'] ?></legend>
+					<div class="infldset">
+						<p><?php echo $lang_install['Info 4'] ?></p>
+						<label class="required"><strong><?php echo $lang_install['Database name'] ?> <span><?php echo $lang_install['Required'] ?></span></strong><br /><input id="req_db_name" type="text" name="req_db_name" value="<?php echo pun_htmlspecialchars($db_name) ?>" size="30" /><br /></label>
+					</div>
+				</fieldset>
+			</div>
+			<div class="inform">
+				<fieldset>
+					<legend><?php echo $lang_install['Database enter informations'] ?></legend>
+					<div class="infldset">
+						<p><?php echo $lang_install['Info 5'] ?></p>
+						<label class="conl"><?php echo $lang_install['Database username'] ?><br /><input type="text" name="db_username" value="<?php echo pun_htmlspecialchars($db_username) ?>" size="30" /><br /></label>
+						<label class="conl"><?php echo $lang_install['Database password'] ?><br /><input type="password" name="db_password" size="30" /><br /></label>
+						<div class="clearer"></div>
+					</div>
+				</fieldset>
+			</div>
+			<div class="inform">
+				<fieldset>
+					<legend><?php echo $lang_install['Database enter prefix'] ?></legend>
+					<div class="infldset">
+						<p><?php echo $lang_install['Info 6'] ?></p>
+						<label><?php echo $lang_install['Table prefix'] ?><br /><input id="db_prefix" type="text" name="db_prefix" value="<?php echo pun_htmlspecialchars($db_prefix) ?>" size="20" maxlength="30" /><br /></label>
+					</div>
+				</fieldset>
+			</div>
+			<div class="inform">
+				<div class="forminfo">
+					<h3><?php echo $lang_install['Administration setup'] ?></h3>
+					<p><?php echo $lang_install['Info 7'] ?></p>
+				</div>
+				<fieldset>
+					<legend><?php echo $lang_install['Admin enter username'] ?></legend>
+					<div class="infldset">
+						<p><?php echo $lang_install['Info 8'] ?></p>
+						<label class="required"><strong><?php echo $lang_install['Administrator username'] ?> <span><?php echo $lang_install['Required'] ?></span></strong><br /><input type="text" name="req_username" value="<?php echo pun_htmlspecialchars($username) ?>" size="25" maxlength="25" /><br /></label>
+					</div>
+				</fieldset>
+			</div>
+			<div class="inform">
+				<fieldset>
+					<legend><?php echo $lang_install['Admin enter password'] ?></legend>
+					<div class="infldset">
+						<p><?php echo $lang_install['Info 9'] ?></p>
+						<label class="conl required"><strong><?php echo $lang_install['Password'] ?> <span><?php echo $lang_install['Required'] ?></span></strong><br /><input id="req_password1" type="password" name="req_password1" size="16" /><br /></label>
+						<label class="conl required"><strong><?php echo $lang_install['Confirm password'] ?> <span><?php echo $lang_install['Required'] ?></span></strong><br /><input type="password" name="req_password2" size="16" /><br /></label>
+						<div class="clearer"></div>
+					</div>
+				</fieldset>
+			</div>
+			<div class="inform">
+				<fieldset>
+					<legend><?php echo $lang_install['Admin enter email'] ?></legend>
+					<div class="infldset">
+						<p><?php echo $lang_install['Info 10'] ?></p>
+						<label class="required"><strong><?php echo $lang_install['Administrator email'] ?> <span><?php echo $lang_install['Required'] ?></span></strong><br /><input id="req_email" type="text" name="req_email" value="<?php echo pun_htmlspecialchars($email) ?>" size="50" maxlength="80" /><br /></label>
+					</div>
+				</fieldset>
+			</div>
+			<div class="inform">
+				<div class="forminfo">
+					<h3><?php echo $lang_install['Board setup'] ?></h3>
+					<p><?php echo $lang_install['Info 11'] ?></p>
+				</div>
+				<fieldset>
+					<legend><?php echo $lang_install['Enter board title'] ?></legend>
+					<div class="infldset">
+						<p><?php echo $lang_install['Info 12'] ?></p>
+						<label class="required"><strong><?php echo $lang_install['Board title'] ?> <span><?php echo $lang_install['Required'] ?></span></strong><br /><input id="req_title" type="text" name="req_title" value="<?php echo pun_htmlspecialchars($title) ?>" size="60" maxlength="255" /><br /></label>
+					</div>
+				</fieldset>
+			</div>
+			<div class="inform">
+				<fieldset>
+					<legend><?php echo $lang_install['Enter board description'] ?></legend>
+					<div class="infldset">
+						<p><?php echo $lang_install['Info 13'] ?></p>
+						<label><?php echo $lang_install['Board description'] ?><br /><input id="desc" type="text" name="desc" value="<?php echo pun_htmlspecialchars($description) ?>" size="60" maxlength="255" /><br /></label>
+					</div>
+				</fieldset>
+			</div>
+			<div class="inform">
+				<fieldset>
+					<legend><?php echo $lang_install['Enter base URL'] ?></legend>
+					<div class="infldset">
+						<p><?php echo $lang_install['Info 14'] ?></p>
+						<label class="required"><strong><?php echo $lang_install['Base URL'] ?> <span><?php echo $lang_install['Required'] ?></span></strong><br /><input id="req_base_url" type="text" name="req_base_url" value="<?php echo pun_htmlspecialchars($base_url) ?>" size="60" maxlength="100" /><br /></label>
+					</div>
+				</fieldset>
+			</div>
+			<div class="inform">
+				<fieldset>
+					<legend><?php echo $lang_install['Choose the default language'] ?></legend>
+					<div class="infldset">
+						<p><?php echo $lang_install['Info 15'] ?></p>
+						<label class="required"><strong><?php echo $lang_install['Default language'] ?> <span><?php echo $lang_install['Required'] ?></span></strong><br /><select id="req_default_lang" name="req_default_lang">
+<?php
+
+		$languages = forum_list_langs();
+		foreach ($languages as $temp)
+		{
+			if ($temp == $default_lang)
+				echo "\t\t\t\t\t\t\t\t\t\t\t".'<option value="'.$temp.'" selected="selected">'.$temp.'</option>'."\n";
+			else
+				echo "\t\t\t\t\t\t\t\t\t\t\t".'<option value="'.$temp.'">'.$temp.'</option>'."\n";
+		}
+
+?>
+						</select><br /></label>
+					</div>
+				</fieldset>
+			</div>
+			<div class="inform">
+				<fieldset>
+					<legend><?php echo $lang_install['Choose the default style'] ?></legend>
+					<div class="infldset">
+						<p><?php echo $lang_install['Info 16'] ?></p>
+						<label class="required"><strong><?php echo $lang_install['Default style'] ?> <span><?php echo $lang_install['Required'] ?></span></strong><br /><select id="req_default_style" name="req_default_style">
+<?php
+
+		$styles = forum_list_styles();
+		foreach ($styles as $temp)
+		{
+			if ($temp == $default_style)
+				echo "\t\t\t\t\t\t\t\t\t".'<option value="'.$temp.'" selected="selected">'.str_replace('_', ' ', $temp).'</option>'."\n";
+			else
+				echo "\t\t\t\t\t\t\t\t\t".'<option value="'.$temp.'">'.str_replace('_', ' ', $temp).'</option>'."\n";
+		}
+
+?>
+						</select><br /></label>
+					</div>
+				</fieldset>
+			</div>
+			<p class="buttons"><input type="submit" name="start" value="<?php echo $lang_install['Start install'] ?>" /></p>
+		</form>
+	</div>
+</div>
+</div>
+
+</div>
+<div class="end-box"><div><!-- Bottom Corners --></div></div>
+</div>
+
+</body>
+</html>
+<?php
+
+}
+else
+{
+	// Load the appropriate DB layer class
+	switch ($db_type)
+	{
+		case 'mysql':
+			require PUN_ROOT.'include/dblayer/mysql.php';
+			break;
+
+		case 'mysql_innodb':
+			require PUN_ROOT.'include/dblayer/mysql_innodb.php';
+			break;
+
+		case 'mysqli':
+			require PUN_ROOT.'include/dblayer/mysqli.php';
+			break;
+
+		case 'mysqli_innodb':
+			require PUN_ROOT.'include/dblayer/mysqli_innodb.php';
+			break;
+
+		case 'pgsql':
+			require PUN_ROOT.'include/dblayer/pgsql.php';
+			break;
+
+		case 'sqlite':
+			require PUN_ROOT.'include/dblayer/sqlite.php';
+			break;
+
+		default:
+			error(sprintf($lang_install['DB type not valid'], pun_htmlspecialchars($db_type)));
+	}
+
+	// Create the database object (and connect/select db)
+	$db = new DBLayer($db_host, $db_username, $db_password, $db_name, $db_prefix, false);
+
+	// Validate prefix
+	if (strlen($db_prefix) > 0 && (!preg_match('%^[a-zA-Z_][a-zA-Z0-9_]*$%', $db_prefix) || strlen($db_prefix) > 40))
+		error(sprintf($lang_install['Table prefix error'], $db->prefix));
+
+	// Do some DB type specific checks
+	switch ($db_type)
+	{
+		case 'mysql':
+		case 'mysqli':
+		case 'mysql_innodb':
+		case 'mysqli_innodb':
+			$mysql_info = $db->get_version();
+			if (version_compare($mysql_info['version'], MIN_MYSQL_VERSION, '<'))
+				error(sprintf($lang_install['You are running error'], 'MySQL', $mysql_info['version'], FORUM_VERSION, MIN_MYSQL_VERSION));
+			break;
+
+		case 'pgsql':
+			$pgsql_info = $db->get_version();
+			if (version_compare($pgsql_info['version'], MIN_PGSQL_VERSION, '<'))
+				error(sprintf($lang_install['You are running error'], 'PostgreSQL', $pgsql_info['version'], FORUM_VERSION, MIN_PGSQL_VERSION));
+			break;
+
+		case 'sqlite':
+			if (strtolower($db_prefix) == 'sqlite_')
+				error($lang_install['Prefix reserved']);
+			break;
+	}
+
+
+	// Make sure FluxBB isn't already installed
+	$result = $db->query('SELECT 1 FROM '.$db_prefix.'users WHERE id=1');
+	if ($db->num_rows($result))
+		error(sprintf($lang_install['Existing table error'], $db_prefix, $db_name));
+
+	// Check if InnoDB is available
+	if ($db_type == 'mysql_innodb' || $db_type == 'mysqli_innodb')
+	{
+		$result = $db->query('SHOW VARIABLES LIKE \'have_innodb\'');
+		list (, $result) = $db->fetch_row($result);
+		if ((strtoupper($result) != 'YES'))
+			error($lang_install['InnoDB off']);
+	}
+
+
+	// Start a transaction
+	$db->start_transaction();
+
+
+	// Create all tables
+	$schema = array(
+		'FIELDS'		=> array(
+			'id'			=> array(
+				'datatype'		=> 'SERIAL',
+				'allow_null'	=> false
+			),
+			'username'		=> array(
+				'datatype'		=> 'VARCHAR(200)',
+				'allow_null'	=> true
+			),
+			'ip'			=> array(
+				'datatype'		=> 'VARCHAR(255)',
+				'allow_null'	=> true
+			),
+			'email'			=> array(
+				'datatype'		=> 'VARCHAR(80)',
+				'allow_null'	=> true
+			),
+			'message'		=> array(
+				'datatype'		=> 'VARCHAR(255)',
+				'allow_null'	=> true
+			),
+			'expire'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> true
+			),
+			'ban_creator'	=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			)
+		),
+		'PRIMARY KEY'	=> array('id'),
+		'INDEXES'		=> array(
+			'username_idx'	=> array('username')
+		)
+	);
+
+	if ($db_type == 'mysql' || $db_type == 'mysqli' || $db_type == 'mysql_innodb' || $db_type == 'mysqli_innodb')
+		$schema['INDEXES']['username_idx'] = array('username(25)');
+
+	$db->create_table('bans', $schema) or error('Unable to create bans table', __FILE__, __LINE__, $db->error());
+
+
+	$schema = array(
+		'FIELDS'		=> array(
+			'id'			=> array(
+				'datatype'		=> 'SERIAL',
+				'allow_null'	=> false
+			),
+			'cat_name'		=> array(
+				'datatype'		=> 'VARCHAR(80)',
+				'allow_null'	=> false,
+				'default'		=> '\'New Category\''
+			),
+			'disp_position'	=> array(
+				'datatype'		=> 'INT(10)',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			)
+		),
+		'PRIMARY KEY'	=> array('id')
+	);
+
+	$db->create_table('categories', $schema) or error('Unable to create categories table', __FILE__, __LINE__, $db->error());
+
+
+	$schema = array(
+		'FIELDS'		=> array(
+			'id'			=> array(
+				'datatype'		=> 'SERIAL',
+				'allow_null'	=> false
+			),
+			'search_for'	=> array(
+				'datatype'		=> 'VARCHAR(60)',
+				'allow_null'	=> false,
+				'default'		=> '\'\''
+			),
+			'replace_with'	=> array(
+				'datatype'		=> 'VARCHAR(60)',
+				'allow_null'	=> false,
+				'default'		=> '\'\''
+			)
+		),
+		'PRIMARY KEY'	=> array('id')
+	);
+
+	$db->create_table('censoring', $schema) or error('Unable to create censoring table', __FILE__, __LINE__, $db->error());
+
+
+	$schema = array(
+		'FIELDS'		=> array(
+			'conf_name'		=> array(
+				'datatype'		=> 'VARCHAR(255)',
+				'allow_null'	=> false,
+				'default'		=> '\'\''
+			),
+			'conf_value'	=> array(
+				'datatype'		=> 'TEXT',
+				'allow_null'	=> true
+			)
+		),
+		'PRIMARY KEY'	=> array('conf_name')
+	);
+
+	$db->create_table('config', $schema) or error('Unable to create config table', __FILE__, __LINE__, $db->error());
+
+
+	$schema = array(
+		'FIELDS'		=> array(
+			'group_id'		=> array(
+				'datatype'		=> 'INT(10)',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'forum_id'		=> array(
+				'datatype'		=> 'INT(10)',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'read_forum'	=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '1'
+			),
+			'post_replies'	=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '1'
+			),
+			'post_topics'	=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '1'
+			)
+		),
+		'PRIMARY KEY'	=> array('group_id', 'forum_id')
+	);
+
+	$db->create_table('forum_perms', $schema) or error('Unable to create forum_perms table', __FILE__, __LINE__, $db->error());
+
+
+	$schema = array(
+		'FIELDS'		=> array(
+			'id'			=> array(
+				'datatype'		=> 'SERIAL',
+				'allow_null'	=> false
+			),
+			'forum_name'	=> array(
+				'datatype'		=> 'VARCHAR(80)',
+				'allow_null'	=> false,
+				'default'		=> '\'New forum\''
+			),
+			'forum_desc'	=> array(
+				'datatype'		=> 'TEXT',
+				'allow_null'	=> true
+			),
+			'redirect_url'	=> array(
+				'datatype'		=> 'VARCHAR(100)',
+				'allow_null'	=> true
+			),
+			'moderators'	=> array(
+				'datatype'		=> 'TEXT',
+				'allow_null'	=> true
+			),
+			'num_topics'	=> array(
+				'datatype'		=> 'MEDIUMINT(8) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'num_posts'		=> array(
+				'datatype'		=> 'MEDIUMINT(8) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'last_post'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> true
+			),
+			'last_post_id'	=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> true
+			),
+			'last_poster'	=> array(
+				'datatype'		=> 'VARCHAR(200)',
+				'allow_null'	=> true
+			),
+			'sort_by'		=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'disp_position'	=> array(
+				'datatype'		=> 'INT(10)',
+				'allow_null'	=> false,
+				'default'		=>	'0'
+			),
+			'cat_id'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=>	'0'
+			)
+		),
+		'PRIMARY KEY'	=> array('id')
+	);
+
+	$db->create_table('forums', $schema) or error('Unable to create forums table', __FILE__, __LINE__, $db->error());
+
+
+	$schema = array(
+		'FIELDS'		=> array(
+			'g_id'						=> array(
+				'datatype'		=> 'SERIAL',
+				'allow_null'	=> false
+			),
+			'g_title'					=> array(
+				'datatype'		=> 'VARCHAR(50)',
+				'allow_null'	=> false,
+				'default'		=> '\'\''
+			),
+			'g_user_title'				=> array(
+				'datatype'		=> 'VARCHAR(50)',
+				'allow_null'	=> true
+			),
+			'g_moderator'				=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'g_mod_edit_users'			=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'g_mod_rename_users'		=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'g_mod_change_passwords'	=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'g_mod_ban_users'			=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'g_read_board'				=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '1'
+			),
+			'g_view_users'				=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '1'
+			),
+			'g_post_replies'			=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '1'
+			),
+			'g_post_topics'				=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '1'
+			),
+			'g_edit_posts'				=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '1'
+			),
+			'g_delete_posts'			=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '1'
+			),
+			'g_delete_topics'			=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '1'
+			),
+			'g_set_title'				=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '1'
+			),
+			'g_search'					=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '1'
+			),
+			'g_search_users'			=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '1'
+			),
+			'g_send_email'				=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '1'
+			),
+			'g_post_flood'				=> array(
+				'datatype'		=> 'SMALLINT(6)',
+				'allow_null'	=> false,
+				'default'		=> '30'
+			),
+			'g_search_flood'			=> array(
+				'datatype'		=> 'SMALLINT(6)',
+				'allow_null'	=> false,
+				'default'		=> '30'
+			),
+			'g_email_flood'				=> array(
+				'datatype'		=> 'SMALLINT(6)',
+				'allow_null'	=> false,
+				'default'		=> '60'
+			),
+			'g_report_flood'			=> array(
+				'datatype'		=> 'SMALLINT(6)',
+				'allow_null'	=> false,
+				'default'		=> '60'
+			)
+		),
+		'PRIMARY KEY'	=> array('g_id')
+	);
+
+	$db->create_table('groups', $schema) or error('Unable to create groups table', __FILE__, __LINE__, $db->error());
+
+
+	$schema = array(
+		'FIELDS'		=> array(
+			'user_id'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '1'
+			),
+			'ident'			=> array(
+				'datatype'		=> 'VARCHAR(200)',
+				'allow_null'	=> false,
+				'default'		=> '\'\''
+			),
+			'logged'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'idle'			=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'last_post'			=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> true
+			),
+			'last_search'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> true
+			),
+		),
+		'UNIQUE KEYS'	=> array(
+			'user_id_ident_idx'	=> array('user_id', 'ident')
+		),
+		'INDEXES'		=> array(
+			'ident_idx'		=> array('ident'),
+			'logged_idx'	=> array('logged')
+		)
+	);
+
+	if ($db_type == 'mysql' || $db_type == 'mysqli' || $db_type == 'mysql_innodb' || $db_type == 'mysqli_innodb')
+	{
+		$schema['UNIQUE KEYS']['user_id_ident_idx'] = array('user_id', 'ident(25)');
+		$schema['INDEXES']['ident_idx'] = array('ident(25)');
+	}
+
+	if ($db_type == 'mysql_innodb' || $db_type == 'mysqli_innodb')
+		$schema['ENGINE'] = 'InnoDB';
+
+	$db->create_table('online', $schema) or error('Unable to create online table', __FILE__, __LINE__, $db->error());
+
+
+	$schema = array(
+		'FIELDS'		=> array(
+			'id'			=> array(
+				'datatype'		=> 'SERIAL',
+				'allow_null'	=> false
+			),
+			'poster'		=> array(
+				'datatype'		=> 'VARCHAR(200)',
+				'allow_null'	=> false,
+				'default'		=> '\'\''
+			),
+			'poster_id'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '1'
+			),
+			'poster_ip'		=> array(
+				'datatype'		=> 'VARCHAR(39)',
+				'allow_null'	=> true
+			),
+			'poster_email'	=> array(
+				'datatype'		=> 'VARCHAR(80)',
+				'allow_null'	=> true
+			),
+			'message'		=> array(
+				'datatype'		=> 'MEDIUMTEXT',
+				'allow_null'	=> true
+			),
+			'hide_smilies'	=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'posted'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'edited'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> true
+			),
+			'edited_by'		=> array(
+				'datatype'		=> 'VARCHAR(200)',
+				'allow_null'	=> true
+			),
+			'topic_id'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			)
+		),
+		'PRIMARY KEY'	=> array('id'),
+		'INDEXES'		=> array(
+			'topic_id_idx'	=> array('topic_id'),
+			'multi_idx'		=> array('poster_id', 'topic_id')
+		)
+	);
+
+	$db->create_table('posts', $schema) or error('Unable to create posts table', __FILE__, __LINE__, $db->error());
+
+
+	$schema = array(
+		'FIELDS'		=> array(
+			'id'			=> array(
+				'datatype'		=> 'SERIAL',
+				'allow_null'	=> false
+			),
+			'rank'			=> array(
+				'datatype'		=> 'VARCHAR(50)',
+				'allow_null'	=> false,
+				'default'		=> '\'\''
+			),
+			'min_posts'		=> array(
+				'datatype'		=> 'MEDIUMINT(8) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			)
+		),
+		'PRIMARY KEY'	=> array('id')
+	);
+
+	$db->create_table('ranks', $schema) or error('Unable to create ranks table', __FILE__, __LINE__, $db->error());
+
+
+	$schema = array(
+		'FIELDS'		=> array(
+			'id'			=> array(
+				'datatype'		=> 'SERIAL',
+				'allow_null'	=> false
+			),
+			'post_id'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'topic_id'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'forum_id'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'reported_by'	=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'created'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'message'		=> array(
+				'datatype'		=> 'TEXT',
+				'allow_null'	=> true
+			),
+			'zapped'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> true
+			),
+			'zapped_by'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> true
+			)
+		),
+		'PRIMARY KEY'	=> array('id'),
+		'INDEXES'		=> array(
+			'zapped_idx'	=> array('zapped')
+		)
+	);
+
+	$db->create_table('reports', $schema) or error('Unable to create reports table', __FILE__, __LINE__, $db->error());
+
+
+	$schema = array(
+		'FIELDS'		=> array(
+			'id'			=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'ident'			=> array(
+				'datatype'		=> 'VARCHAR(200)',
+				'allow_null'	=> false,
+				'default'		=> '\'\''
+			),
+			'search_data'	=> array(
+				'datatype'		=> 'MEDIUMTEXT',
+				'allow_null'	=> true
+			)
+		),
+		'PRIMARY KEY'	=> array('id'),
+		'INDEXES'		=> array(
+			'ident_idx'	=> array('ident')
+		)
+	);
+
+	if ($db_type == 'mysql' || $db_type == 'mysqli' || $db_type == 'mysql_innodb' || $db_type == 'mysqli_innodb')
+		$schema['INDEXES']['ident_idx'] = array('ident(8)');
+
+	$db->create_table('search_cache', $schema) or error('Unable to create search_cache table', __FILE__, __LINE__, $db->error());
+
+
+	$schema = array(
+		'FIELDS'		=> array(
+			'post_id'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'word_id'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'subject_match'	=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			)
+		),
+		'INDEXES'		=> array(
+			'word_id_idx'	=> array('word_id'),
+			'post_id_idx'	=> array('post_id')
+		)
+	);
+
+	$db->create_table('search_matches', $schema) or error('Unable to create search_matches table', __FILE__, __LINE__, $db->error());
+
+
+	$schema = array(
+		'FIELDS'		=> array(
+			'id'			=> array(
+				'datatype'		=> 'SERIAL',
+				'allow_null'	=> false
+			),
+			'word'			=> array(
+				'datatype'		=> 'VARCHAR(20)',
+				'allow_null'	=> false,
+				'default'		=> '\'\'',
+				'collation'		=> 'bin'
+			)
+		),
+		'PRIMARY KEY'	=> array('word'),
+		'INDEXES'		=> array(
+			'id_idx'	=> array('id')
+		)
+	);
+
+	if ($db_type == 'sqlite')
+	{
+		$schema['PRIMARY KEY'] = array('id');
+		$schema['UNIQUE KEYS'] = array('word_idx'	=> array('word'));
+	}
+
+	$db->create_table('search_words', $schema) or error('Unable to create search_words table', __FILE__, __LINE__, $db->error());
+
+
+	$schema = array(
+		'FIELDS'		=> array(
+			'user_id'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'topic_id'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			)
+		),
+		'PRIMARY KEY'	=> array('user_id', 'topic_id')
+	);
+
+	$db->create_table('topic_subscriptions', $schema) or error('Unable to create topic subscriptions table', __FILE__, __LINE__, $db->error());
+
+
+	$schema = array(
+		'FIELDS'		=> array(
+			'user_id'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'forum_id'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			)
+		),
+		'PRIMARY KEY'	=> array('user_id', 'forum_id')
+	);
+
+	$db->create_table('forum_subscriptions', $schema) or error('Unable to create forum subscriptions table', __FILE__, __LINE__, $db->error());
+
+
+	$schema = array(
+		'FIELDS'		=> array(
+			'id'			=> array(
+				'datatype'		=> 'SERIAL',
+				'allow_null'	=> false
+			),
+			'poster'		=> array(
+				'datatype'		=> 'VARCHAR(200)',
+				'allow_null'	=> false,
+				'default'		=> '\'\''
+			),
+			'subject'		=> array(
+				'datatype'		=> 'VARCHAR(255)',
+				'allow_null'	=> false,
+				'default'		=> '\'\''
+			),
+			'posted'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'first_post_id'	=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'last_post'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'last_post_id'	=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'last_poster'	=> array(
+				'datatype'		=> 'VARCHAR(200)',
+				'allow_null'	=> true
+			),
+			'num_views'		=> array(
+				'datatype'		=> 'MEDIUMINT(8) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'num_replies'	=> array(
+				'datatype'		=> 'MEDIUMINT(8) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'closed'		=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'sticky'		=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'moved_to'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> true
+			),
+			'forum_id'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			)
+		),
+		'PRIMARY KEY'	=> array('id'),
+		'INDEXES'		=> array(
+			'forum_id_idx'		=> array('forum_id'),
+			'moved_to_idx'		=> array('moved_to'),
+			'last_post_idx'		=> array('last_post'),
+			'first_post_id_idx'	=> array('first_post_id')
+		)
+	);
+
+	$db->create_table('topics', $schema) or error('Unable to create topics table', __FILE__, __LINE__, $db->error());
+
+
+	$schema = array(
+		'FIELDS'		=> array(
+			'id'				=> array(
+				'datatype'		=> 'SERIAL',
+				'allow_null'	=> false
+			),
+			'group_id'			=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '3'
+			),
+			'username'			=> array(
+				'datatype'		=> 'VARCHAR(200)',
+				'allow_null'	=> false,
+				'default'		=> '\'\''
+			),
+			'password'			=> array(
+				'datatype'		=> 'VARCHAR(40)',
+				'allow_null'	=> false,
+				'default'		=> '\'\''
+			),
+			'email'				=> array(
+				'datatype'		=> 'VARCHAR(80)',
+				'allow_null'	=> false,
+				'default'		=> '\'\''
+			),
+			'title'				=> array(
+				'datatype'		=> 'VARCHAR(50)',
+				'allow_null'	=> true
+			),
+			'realname'			=> array(
+				'datatype'		=> 'VARCHAR(40)',
+				'allow_null'	=> true
+			),
+			'url'				=> array(
+				'datatype'		=> 'VARCHAR(100)',
+				'allow_null'	=> true
+			),
+			'jabber'			=> array(
+				'datatype'		=> 'VARCHAR(80)',
+				'allow_null'	=> true
+			),
+			'icq'				=> array(
+				'datatype'		=> 'VARCHAR(12)',
+				'allow_null'	=> true
+			),
+			'msn'				=> array(
+				'datatype'		=> 'VARCHAR(80)',
+				'allow_null'	=> true
+			),
+			'aim'				=> array(
+				'datatype'		=> 'VARCHAR(30)',
+				'allow_null'	=> true
+			),
+			'yahoo'				=> array(
+				'datatype'		=> 'VARCHAR(30)',
+				'allow_null'	=> true
+			),
+			'location'			=> array(
+				'datatype'		=> 'VARCHAR(30)',
+				'allow_null'	=> true
+			),
+			'signature'			=> array(
+				'datatype'		=> 'TEXT',
+				'allow_null'	=> true
+			),
+			'disp_topics'		=> array(
+				'datatype'		=> 'TINYINT(3) UNSIGNED',
+				'allow_null'	=> true
+			),
+			'disp_posts'		=> array(
+				'datatype'		=> 'TINYINT(3) UNSIGNED',
+				'allow_null'	=> true
+			),
+			'email_setting'		=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '1'
+			),
+			'notify_with_post'	=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'auto_notify'		=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'show_smilies'		=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '1'
+			),
+			'show_img'			=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '1'
+			),
+			'show_img_sig'		=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '1'
+			),
+			'show_avatars'		=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '1'
+			),
+			'show_sig'			=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '1'
+			),
+			'timezone'			=> array(
+				'datatype'		=> 'FLOAT',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'dst'				=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'time_format'		=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'date_format'		=> array(
+				'datatype'		=> 'TINYINT(1)',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'language'			=> array(
+				'datatype'		=> 'VARCHAR(25)',
+				'allow_null'	=> false,
+				'default'		=> '\''.$db->escape($default_lang).'\''
+			),
+			'style'				=> array(
+				'datatype'		=> 'VARCHAR(25)',
+				'allow_null'	=> false,
+				'default'		=> '\''.$db->escape($default_style).'\''
+			),
+			'num_posts'			=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'last_post'			=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> true
+			),
+			'last_search'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> true
+			),
+			'last_email_sent'	=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> true
+			),
+			'last_report_sent'	=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> true
+			),
+			'registered'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'registration_ip'	=> array(
+				'datatype'		=> 'VARCHAR(39)',
+				'allow_null'	=> false,
+				'default'		=> '\'0.0.0.0\''
+			),
+			'last_visit'		=> array(
+				'datatype'		=> 'INT(10) UNSIGNED',
+				'allow_null'	=> false,
+				'default'		=> '0'
+			),
+			'admin_note'		=> array(
+				'datatype'		=> 'VARCHAR(30)',
+				'allow_null'	=> true
+			),
+			'activate_string'	=> array(
+				'datatype'		=> 'VARCHAR(80)',
+				'allow_null'	=> true
+			),
+			'activate_key'		=> array(
+				'datatype'		=> 'VARCHAR(8)',
+				'allow_null'	=> true
+			),
+		),
+		'PRIMARY KEY'	=> array('id'),
+		'UNIQUE KEYS'	=> array(
+			'username_idx'		=> array('username')
+		),
+		'INDEXES'		=> array(
+			'registered_idx'	=> array('registered')
+		)
+	);
+
+	if ($db_type == 'mysql' || $db_type == 'mysqli' || $db_type == 'mysql_innodb' || $db_type == 'mysqli_innodb')
+		$schema['UNIQUE KEYS']['username_idx'] = array('username(25)');
+
+	$db->create_table('users', $schema) or error('Unable to create users table', __FILE__, __LINE__, $db->error());
+
+
+	$now = time();
+
+	// Insert the four preset groups
+	$db->query('INSERT INTO '.$db->prefix.'groups ('.($db_type != 'pgsql' ? 'g_id, ' : '').'g_title, g_user_title, g_moderator, g_mod_edit_users, g_mod_rename_users, g_mod_change_passwords, g_mod_ban_users, g_read_board, g_view_users, g_post_replies, g_post_topics, g_edit_posts, g_delete_posts, g_delete_topics, g_set_title, g_search, g_search_users, g_send_email, g_post_flood, g_search_flood, g_email_flood, g_report_flood) VALUES('.($db_type != 'pgsql' ? '1, ' : '').'\''.$db->escape($lang_install['Administrators']).'\', \''.$db->escape($lang_install['Administrator']).'\', 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0)') or error('Unable to add group', __FILE__, __LINE__, $db->error());
+
+	$db->query('INSERT INTO '.$db->prefix.'groups ('.($db_type != 'pgsql' ? 'g_id, ' : '').'g_title, g_user_title, g_moderator, g_mod_edit_users, g_mod_rename_users, g_mod_change_passwords, g_mod_ban_users, g_read_board, g_view_users, g_post_replies, g_post_topics, g_edit_posts, g_delete_posts, g_delete_topics, g_set_title, g_search, g_search_users, g_send_email, g_post_flood, g_search_flood, g_email_flood, g_report_flood) VALUES('.($db_type != 'pgsql' ? '2, ' : '').'\''.$db->escape($lang_install['Moderators']).'\', \''.$db->escape($lang_install['Moderator']).'\', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0)') or error('Unable to add group', __FILE__, __LINE__, $db->error());
+
+	$db->query('INSERT INTO '.$db->prefix.'groups ('.($db_type != 'pgsql' ? 'g_id, ' : '').'g_title, g_user_title, g_moderator, g_mod_edit_users, g_mod_rename_users, g_mod_change_passwords, g_mod_ban_users, g_read_board, g_view_users, g_post_replies, g_post_topics, g_edit_posts, g_delete_posts, g_delete_topics, g_set_title, g_search, g_search_users, g_send_email, g_post_flood, g_search_flood, g_email_flood, g_report_flood) VALUES('.($db_type != 'pgsql' ? '3, ' : '').'\''.$db->escape($lang_install['Guests']).'\', NULL, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 60, 30, 0, 0)') or error('Unable to add group', __FILE__, __LINE__, $db->error());
+
+	$db->query('INSERT INTO '.$db->prefix.'groups ('.($db_type != 'pgsql' ? 'g_id, ' : '').'g_title, g_user_title, g_moderator, g_mod_edit_users, g_mod_rename_users, g_mod_change_passwords, g_mod_ban_users, g_read_board, g_view_users, g_post_replies, g_post_topics, g_edit_posts, g_delete_posts, g_delete_topics, g_set_title, g_search, g_search_users, g_send_email, g_post_flood, g_search_flood, g_email_flood, g_report_flood) VALUES('.($db_type != 'pgsql' ? '4, ' : '').'\''.$db->escape($lang_install['Members']).'\', NULL, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 60, 30, 60, 60)') or error('Unable to add group', __FILE__, __LINE__, $db->error());
+
+	// Insert guest and first admin user
+	$db->query('INSERT INTO '.$db_prefix.'users (group_id, username, password, email) VALUES(3, \''.$db->escape($lang_install['Guest']).'\', \''.$db->escape($lang_install['Guest']).'\', \''.$db->escape($lang_install['Guest']).'\')')
+		or error('Unable to add guest user. Please check your configuration and try again', __FILE__, __LINE__, $db->error());
+
+	$db->query('INSERT INTO '.$db_prefix.'users (group_id, username, password, email, language, style, num_posts, last_post, registered, registration_ip, last_visit) VALUES(1, \''.$db->escape($username).'\', \''.pun_hash($password1).'\', \''.$email.'\', \''.$db->escape($default_lang).'\', \''.$db->escape($default_style).'\', 1, '.$now.', '.$now.', \''.$db->escape(get_remote_address()).'\', '.$now.')')
+		or error('Unable to add administrator user. Please check your configuration and try again', __FILE__, __LINE__, $db->error());
+
+	// Enable/disable avatars depending on file_uploads setting in PHP configuration
+	$avatars = in_array(strtolower(@ini_get('file_uploads')), array('on', 'true', '1')) ? 1 : 0;
+
+	// Insert config data
+	$pun_config = array(
+		'o_cur_version'				=> FORUM_VERSION,
+		'o_database_revision'		=> FORUM_DB_REVISION,
+		'o_searchindex_revision'	=> FORUM_SI_REVISION,
+		'o_parser_revision'			=> FORUM_PARSER_REVISION,
+		'o_board_title'				=> $title,
+		'o_board_desc'				=> $description,
+		'o_default_timezone'		=> 0,
+		'o_time_format'				=> 'H:i:s',
+		'o_date_format'				=> 'Y-m-d',
+		'o_timeout_visit'			=> 1800,
+		'o_timeout_online'			=> 300,
+		'o_redirect_delay'			=> 1,
+		'o_show_version'			=> 0,
+		'o_show_user_info'			=> 1,
+		'o_show_post_count'			=> 1,
+		'o_signatures'				=> 1,
+		'o_smilies'					=> 1,
+		'o_smilies_sig'				=> 1,
+		'o_make_links'				=> 1,
+		'o_default_lang'			=> $default_lang,
+		'o_default_style'			=> $default_style,
+		'o_default_user_group'		=> 4,
+		'o_topic_review'			=> 15,
+		'o_disp_topics_default'		=> 30,
+		'o_disp_posts_default'		=> 25,
+		'o_indent_num_spaces'		=> 4,
+		'o_quote_depth'				=> 3,
+		'o_quickpost'				=> 1,
+		'o_users_online'			=> 1,
+		'o_censoring'				=> 0,
+		'o_ranks'					=> 1,
+		'o_show_dot'				=> 0,
+		'o_topic_views'				=> 1,
+		'o_quickjump'				=> 1,
+		'o_gzip'					=> 0,
+		'o_additional_navlinks'		=> '',
+		'o_report_method'			=> 0,
+		'o_regs_report'				=> 0,
+		'o_default_email_setting'	=> 1,
+		'o_mailing_list'			=> $email,
+		'o_avatars'					=> $avatars,
+		'o_avatars_dir'				=> 'img/avatars',
+		'o_avatars_width'			=> 60,
+		'o_avatars_height'			=> 60,
+		'o_avatars_size'			=> 10240,
+		'o_search_all_forums'		=> 1,
+		'o_base_url'				=> $base_url,
+		'o_admin_email'				=> $email,
+		'o_webmaster_email'			=> $email,
+		'o_forum_subscriptions'		=> 1,
+		'o_topic_subscriptions'		=> 1,
+		'o_smtp_host'				=> NULL,
+		'o_smtp_user'				=> NULL,
+		'o_smtp_pass'				=> NULL,
+		'o_smtp_ssl'				=> 0,
+		'o_regs_allow'				=> 1,
+		'o_regs_verify'				=> 0,
+		'o_announcement'			=> 0,
+		'o_announcement_message'	=> $lang_install['Announcement'],
+		'o_rules'					=> 0,
+		'o_rules_message'			=> $lang_install['Rules'],
+		'o_maintenance'				=> 0,
+		'o_maintenance_message'		=> $lang_install['Maintenance message'],
+		'o_default_dst'				=> 0,
+		'o_feed_type'				=> 2,
+		'o_feed_ttl'				=> 0,
+		'p_message_bbcode'			=> 1,
+		'p_message_img_tag'			=> 1,
+		'p_message_all_caps'		=> 1,
+		'p_subject_all_caps'		=> 1,
+		'p_sig_all_caps'			=> 1,
+		'p_sig_bbcode'				=> 1,
+		'p_sig_img_tag'				=> 0,
+		'p_sig_length'				=> 400,
+		'p_sig_lines'				=> 4,
+		'p_allow_banned_email'		=> 1,
+		'p_allow_dupe_email'		=> 0,
+		'p_force_guest_email'		=> 1
+	);
+
+	foreach ($pun_config as $conf_name => $conf_value)
+	{
+		$db->query('INSERT INTO '.$db_prefix.'config (conf_name, conf_value) VALUES(\''.$conf_name.'\', '.(is_null($conf_value) ? 'NULL' : '\''.$db->escape($conf_value).'\'').')')
+			or error('Unable to insert into table '.$db_prefix.'config. Please check your configuration and try again', __FILE__, __LINE__, $db->error());
+	}
+
+	// Insert some other default data
+	$subject = $lang_install['Test post'];
+	$message = $lang_install['Message'];
+
+	$db->query('INSERT INTO '.$db_prefix.'ranks (rank, min_posts) VALUES(\''.$db->escape($lang_install['New member']).'\', 0)')
+		or error('Unable to insert into table '.$db_prefix.'ranks. Please check your configuration and try again', __FILE__, __LINE__, $db->error());
+
+	$db->query('INSERT INTO '.$db_prefix.'ranks (rank, min_posts) VALUES(\''.$db->escape($lang_install['Member']).'\', 10)')
+		or error('Unable to insert into table '.$db_prefix.'ranks. Please check your configuration and try again', __FILE__, __LINE__, $db->error());
+
+	$db->query('INSERT INTO '.$db_prefix.'categories (cat_name, disp_position) VALUES(\''.$db->escape($lang_install['Test category']).'\', 1)')
+		or error('Unable to insert into table '.$db_prefix.'categories. Please check your configuration and try again', __FILE__, __LINE__, $db->error());
+
+	$db->query('INSERT INTO '.$db_prefix.'forums (forum_name, forum_desc, num_topics, num_posts, last_post, last_post_id, last_poster, disp_position, cat_id) VALUES(\''.$db->escape($lang_install['Test forum']).'\', \''.$db->escape($lang_install['This is just a test forum']).'\', 1, 1, '.$now.', 1, \''.$db->escape($username).'\', 1, 1)')
+		or error('Unable to insert into table '.$db_prefix.'forums. Please check your configuration and try again', __FILE__, __LINE__, $db->error());
+
+	$db->query('INSERT INTO '.$db_prefix.'topics (poster, subject, posted, first_post_id, last_post, last_post_id, last_poster, forum_id) VALUES(\''.$db->escape($username).'\', \''.$db->escape($subject).'\', '.$now.', 1, '.$now.', 1, \''.$db->escape($username).'\', 1)')
+		or error('Unable to insert into table '.$db_prefix.'topics. Please check your configuration and try again', __FILE__, __LINE__, $db->error());
+
+	$db->query('INSERT INTO '.$db_prefix.'posts (poster, poster_id, poster_ip, message, posted, topic_id) VALUES(\''.$db->escape($username).'\', 2, \''.$db->escape(get_remote_address()).'\', \''.$db->escape($message).'\', '.$now.', 1)')
+		or error('Unable to insert into table '.$db_prefix.'posts. Please check your configuration and try again', __FILE__, __LINE__, $db->error());
+
+	// Index the test post so searching for it works
+	require PUN_ROOT.'include/search_idx.php';
+	update_search_index('post', 1, $message, $subject);
+
+	$db->end_transaction();
+
+
+	$alerts = array();
+
+	// Check if we disabled uploading avatars because file_uploads was disabled
+	if ($avatars == '0')
+		$alerts[] = $lang_install['Alert upload'];
+
+	// Add some random bytes at the end of the cookie name to prevent collisions
+	$cookie_name = 'pun_cookie_'.random_key(6, false, true);
+
+	// Generate the config.php file data
+	$config = generate_config_file();
+
+	// Attempt to write config.php and serve it up for download if writing fails
+	$written = false;
+	if (forum_is_writable(PUN_ROOT))
+	{
+		$fh = @fopen(PUN_ROOT.'config.php', 'wb');
+		if ($fh)
+		{
+			fwrite($fh, $config);
+			fclose($fh);
+
+			$written = true;
+		}
+	}
+
+
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<title><?php echo $lang_install['FluxBB Installation'] ?></title>
+<link rel="stylesheet" type="text/css" href="style/<?php echo $default_style ?>.css" />
+</head>
+<body>
+
+<div id="puninstall" class="pun">
+<div class="top-box"><div><!-- Top Corners --></div></div>
+<div class="punwrap">
+
+<div id="brdheader" class="block">
+	<div class="box">
+		<div id="brdtitle" class="inbox">
+			<h1><span><?php echo $lang_install['FluxBB Installation'] ?></span></h1>
+			<div id="brddesc"><p><?php echo $lang_install['FluxBB has been installed'] ?></p></div>
+		</div>
+	</div>
+</div>
+
+<div id="brdmain">
+
+<div class="blockform">
+	<h2><span><?php echo $lang_install['Final instructions'] ?></span></h2>
+	<div class="box">
+<?php
+
+if (!$written)
+{
+
+?>
+		<form method="post" action="install.php">
+			<div class="inform">
+				<div class="forminfo">
+					<p><?php echo $lang_install['Info 17'] ?></p>
+					<p><?php echo $lang_install['Info 18'] ?></p>
+				</div>
+				<input type="hidden" name="generate_config" value="1" />
+				<input type="hidden" name="db_type" value="<?php echo $db_type; ?>" />
+				<input type="hidden" name="db_host" value="<?php echo $db_host; ?>" />
+				<input type="hidden" name="db_name" value="<?php echo pun_htmlspecialchars($db_name); ?>" />
+				<input type="hidden" name="db_username" value="<?php echo pun_htmlspecialchars($db_username); ?>" />
+				<input type="hidden" name="db_password" value="<?php echo pun_htmlspecialchars($db_password); ?>" />
+				<input type="hidden" name="db_prefix" value="<?php echo pun_htmlspecialchars($db_prefix); ?>" />
+				<input type="hidden" name="cookie_name" value="<?php echo pun_htmlspecialchars($cookie_name); ?>" />
+				<input type="hidden" name="cookie_seed" value="<?php echo pun_htmlspecialchars($cookie_seed); ?>" />
+
+<?php if (!empty($alerts)): ?>				<div class="forminfo error-info">
+					<ul class="error-list">
+<?php
+
+foreach ($alerts as $cur_alert)
+	echo "\t\t\t\t\t".'<li>'.$cur_alert.'</li>'."\n";
+?>
+					</ul>
+				</div>
+<?php endif; ?>			</div>
+			<p class="buttons"><input type="submit" value="<?php echo $lang_install['Download config.php file'] ?>" /></p>
+		</form>
+
+<?php
+
+}
+else
+{
+
+?>
+		<div class="fakeform">
+			<div class="inform">
+				<div class="forminfo">
+					<p><?php echo $lang_install['FluxBB fully installed'] ?></p>
+				</div>
+			</div>
+		</div>
+<?php
+
+}
+
+?>
+	</div>
+</div>
+
+</div>
+
+</div>
+<div class="end-box"><div><!-- Bottom Corners --></div></div>
+</div>
+
+</body>
+</html>
+<?php
+
+}
diff --git a/lang/English/admin_bans.php b/lang/English/admin_bans.php
new file mode 100644
index 0000000..7d5daa8
--- /dev/null
+++ b/lang/English/admin_bans.php
@@ -0,0 +1,67 @@
+<?php
+
+// Language definitions used in admin_bans.php
+$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.',
+'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.',
+'Invalid e-mail message'	=>	'The email address (e.g. user@domain.com) or partial email address domain (e.g. domain.com) you entered is invalid.',
+'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.',
+'Ban added redirect'		=>	'Ban added. Redirecting …' ,
+'Ban edited redirect'		=>	'Ban edited. Redirecting …',
+'Ban removed redirect'		=>	'Ban removed. Redirecting …',
+
+'New ban head'				=>	'New ban',
+'Add ban subhead'			=>	'Add ban',
+'Username label'			=>	'Username',
+'Username help'				=>	'The username to ban (case-insensitive).',
+'Username advanced help'	=>	'The username to ban (case-insensitive). The next page will let you enter a custom IP and email. If you just want to ban a specific IP/IP-range or email just leave it blank.',
+
+'Ban search head'			=>	'Ban search',
+'Ban search subhead'		=>	'Enter search criteria',
+'Ban search info'			=>	'Search for bans in the database. You can enter one or more terms to search for. Wildcards in the form of asterisks (*) are accepted. To show all bans leave all fields empty.',
+'Date help'					=>	'(yyyy-mm-dd)',
+'Message label'				=>	'Message',
+'Expire after label'		=>	'Expire after',
+'Expire before label'		=>	'Expire before',
+'Order by label'			=>	'Order by',
+'Order by username'			=>	'Username',
+'Order by ip'				=>	'IP',
+'Order by e-mail'			=>	'Email',
+'Order by expire'			=>	'Expire date',
+'Ascending'					=>	'Ascending',
+'Descending'				=>	'Descending',
+'Submit search'				=>	'Submit search',
+
+'E-mail label'				=>	'Email',
+'E-mail help'				=>	'The email or email domain you wish to ban (e.g. someone@somewhere.com or somewhere.com). See "Allow banned email addresses" in Permissions for more info.',
+'IP label'					=>	'IP address/IP-ranges',
+'IP help'					=>	'The IP address or IP-ranges you wish to ban (e.g. 150.11.110.1 or 150.11.110). Separate addresses with spaces. If an IP is entered already it is the last known IP of this user in the database.',
+'IP help link'				=>	'Click %s to see IP statistics for this user.',
+'Ban advanced head'			=>	'Ban advanced settings',
+'Ban advanced subhead'		=>	'Supplement ban with IP and email',
+'Ban message label'			=>	'Ban message',
+'Ban message help'			=>	'A message that will be displayed to the banned user when he/she visits the board.',
+'Message expiry subhead'	=>	'Ban message and expiry',
+'Ban IP range info'			=>	'You should be very careful when banning an IP-range because of the possibility of multiple users matching the same partial IP.',
+'Expire date label'			=>	'Expire date',
+'Expire date help'			=>	'The date when this ban should be automatically removed (format: yyyy-mm-dd). Leave blank to remove manually.',
+
+'Results head'				=>	'Search Results',
+'Results username head'		=>	'Username',
+'Results e-mail head'		=>	'Email',
+'Results IP address head'	=>	'IP/IP-ranges',
+'Results expire head'		=>	'Expires',
+'Results message head'		=>	'Message',
+'Results banned by head'	=>	'Banned by',
+'Results actions head'		=>	'Actions',
+'No match'					=>	'No match',
+'Unknown'					=>	'Unknown',
+
+);
diff --git a/lang/English/admin_categories.php b/lang/English/admin_categories.php
new file mode 100644
index 0000000..311a5af
--- /dev/null
+++ b/lang/English/admin_categories.php
@@ -0,0 +1,29 @@
+<?php
+
+// Language definitions used in admin-categories.php
+$lang_admin_categories = array(
+
+'Must enter name message'		=>	'You must enter a name for the category',
+'Category added redirect'		=>	'Category added. Redirecting …',
+'Category deleted redirect'		=>	'Category deleted. Redirecting …',
+'Delete category head'			=>	'Delete category (together with all forums and posts it contains)',
+'Confirm delete subhead'		=>	'Confirm delete category',
+'Confirm delete info'			=>	'Are you sure that you want to delete the category <strong>%s</strong>?',
+'Delete category warn'			=>	'<strong>WARNING!</strong> Deleting a category will delete all forums and posts (if any) in this category!',
+'Must enter integer message'	=>	'Position must be a positive integer value.',
+'Categories updated redirect'	=>	'Categories updated. Redirecting …',
+'Add categories head'			=>	'Add categories',
+'Add categories subhead'		=>	'Add categories',
+'Add category label'			=>	'Add a new category',
+'Add new submit'				=>	'Add new',
+'Add category help'				=>	'The name of the new category you want to add. You can edit the name of the category later (see below). Go to %s to add forums to your new category.',
+'Delete categories head'		=>	'Delete categories',
+'Delete categories subhead'		=>	'Delete categories',
+'Delete category label'			=>	'Delete a category',
+'Delete category help'			=>	'Select the name of the category you want to delete. You will be asked to confirm your choice of category for deletion before it is deleted.',
+'Edit categories head'			=>	'Edit categories',
+'Edit categories subhead'		=>	'Edit categories',
+'Category position label'		=>	'Position',
+'Category name label'			=>	'Name',
+
+);
diff --git a/lang/English/admin_censoring.php b/lang/English/admin_censoring.php
new file mode 100644
index 0000000..85ad048
--- /dev/null
+++ b/lang/English/admin_censoring.php
@@ -0,0 +1,21 @@
+<?php
+
+// Language definitions used in admin_censoring.php
+$lang_admin_censoring = array(
+
+'Must enter word message'	=>	'You must enter a word to censor.',
+'Word updated redirect'		=>	'Censor word updated. Redirecting …',
+'Word added redirect'		=>	'Censor word added. Redirecting …',
+'Word removed redirect'		=>	'Censor word removed. Redirecting …',
+'Censoring head'			=>	'Censoring',
+'Add word subhead'			=>	'Add word',
+'Add word info'				=>	'Enter a word that you want to censor and the replacement text for this word. Wildcards are accepted (i.e. *some* would match somewhere and lonesome). Censor words also affect usernames. New users will not be able to register with usernames containing any censored words. The search is case insensitive.',
+'Censoring enabled'			=>	'<strong>Censoring is enabled in %s.</strong>',
+'Censoring disabled'		=>	'<strong>Censoring is disabled in %s.</strong>',
+'Censored word label'		=>	'Censored word',
+'Replacement label'			=>	'Replacement word(s)',
+'Action label'				=>	'Action',
+'Edit remove subhead'		=>	'Edit or remove words',
+'No words in list'			=>	'No censor words in list.',
+
+);
diff --git a/lang/English/admin_common.php b/lang/English/admin_common.php
new file mode 100644
index 0000000..a87616c
--- /dev/null
+++ b/lang/English/admin_common.php
@@ -0,0 +1,44 @@
+<?php
+
+// Language definitions used in admin_common
+$lang_admin_common = array(
+
+// The menu
+'Admin menu'			=>	'Admin menu',
+'Plugins menu'			=>	'Plugins menu',
+'Moderator menu'		=>	'Moderator menu',
+'Index'					=>	'Index',
+'Categories'			=>	'Categories',
+'Forums'				=>	'Forums',
+'Users'					=>	'Users',
+'User groups'			=>	'User groups',
+'Options'				=>	'Options',
+'Permissions'			=>	'Permissions',
+'Censoring'				=>	'Censoring',
+'Ranks'					=>	'Ranks',
+'Bans'					=>	'Bans',
+'Prune'					=>	'Prune',
+'Maintenance'			=>	'Maintenance',
+'Reports'				=>	'Reports',
+
+'Admin'					=>	'Admin',
+'Go back'				=>	'Go back',
+'Delete'				=>	'Delete',
+'Update'				=>	'Update',
+'Add'					=>	'Add',
+'Edit'					=>	'Edit',
+'Remove'				=>	'Remove',
+'Yes'					=>	'Yes',
+'No'					=>	'No',
+'Save changes'			=>	'Save changes',
+'Save'					=>	'Save',
+'here'					=>	'here',
+'Action'				=>	'Action',
+'None'					=>	'None',
+'Maintenance mode'		=>	'maintenance mode', // Used for link text in more than one file
+
+// Admin loader
+'No plugin message'		=>	'There is no plugin called %s in the plugin directory.',
+'Plugin failed message'	=>	'Loading of the plugin - <strong>%s</strong> - failed.',
+
+);
diff --git a/lang/English/admin_forums.php b/lang/English/admin_forums.php
new file mode 100644
index 0000000..c933602
--- /dev/null
+++ b/lang/English/admin_forums.php
@@ -0,0 +1,53 @@
+<?php
+
+// Language definitions used in admin-forums.php
+$lang_admin_forums = array(
+
+'Forum added redirect'		=>	'Forum added. Redirecting …',
+'Forum deleted redirect'	=>	'Forum deleted. Redirecting …',
+'Forums updated redirect'	=>	'Forums updated. Redirecting …',
+'Forum updated redirect'	=>	'Forum updated. Redirecting …',
+'Perms reverted redirect'	=>	'Permissions reverted to defaults. Redirecting …',
+'Must enter name message'	=>	'You must enter a forum name.',
+'Must be integer message'	=>	'Position must be a positive integer value.',
+'New forum'					=>	'New forum',
+
+// Entry page
+'Add forum head'			=>	'Add forum',
+'Create new subhead'		=>	'Create a new forum',
+'Add forum label'			=>	'Add forum to category',
+'Add forum help'			=>	'Select the category to which you wish to add a new forum.',
+'Add forum'					=>	'Add forum',
+'No categories exist'		=>	'No categories exist',
+'Edit forums head'			=>	'Edit forums',
+'Category subhead'			=>	'Category:',
+'Forum label'				=>	'Forum',
+'Edit link'					=>	'Edit',
+'Delete link'				=>	'Delete',
+'Position label'			=>	'Position',
+'Update positions'			=>	'Update positions',
+'Confirm delete head'		=>	'Confirm delete forum',
+'Confirm delete subhead'	=>	'Important! Read before deleting',
+'Confirm delete info'		=>	'Are you sure that you want to delete the forum <strong>%s</strong>?',
+'Confirm delete warn'		=>	'WARNING! Deleting a forum will delete all posts (if any) in that forum!',
+
+// Detailed edit page
+'Edit forum head'			=>	'Edit forum',
+'Edit details subhead'		=>	'Edit forum details',
+'Forum name label'			=>	'Forum name',
+'Forum description label'	=>	'Description (HTML)',
+'Category label'			=>	'Category',
+'Sort by label'				=>	'Sort topics by',
+'Last post'					=>	'Last post',
+'Topic start'				=>	'Topic start',
+'Subject'					=>	'Subject',
+'Redirect label'			=>	'Redirect URL',
+'Redirect help'				=>	'Only available in empty forums',
+'Group permissions subhead'	=>	'Edit group permissions for this forum',
+'Group permissions info'	=>	'In this form, you can set the forum specific permissions for the different user groups. If you haven\'t made any changes to this forum\'s group permissions, what you see below is the default based on settings in %s. Administrators always have full permissions and are thus excluded. Permission settings that differ from the default permissions for the user group are marked red. The "Read forum" permission checkbox will be disabled if the group in question lacks the "Read board" permission. For redirect forums, only the "Read forum" permission is editable.',
+'Read forum label'			=>	'Read forum',
+'Post replies label'		=>	'Post replies',
+'Post topics label'			=>	'Post topics',
+'Revert to default'			=>	'Revert to default',
+
+);
diff --git a/lang/English/admin_groups.php b/lang/English/admin_groups.php
new file mode 100644
index 0000000..4b1b4ad
--- /dev/null
+++ b/lang/English/admin_groups.php
@@ -0,0 +1,84 @@
+<?php
+
+// Language definitions used in admin-groups.php
+$lang_admin_groups = array(
+
+'Must enter title message'		=>	'You must enter a group title.',
+'Title already exists message'	=>	'There is already a group with the title <strong>%s</strong>.',
+'Default group redirect'		=>	'Default group set. Redirecting …',
+'Cannot remove default message'	=>	'The default group cannot be removed. In order to delete this group, you must first setup a different group as the default.',
+'Group removed redirect'		=>	'Group removed. Redirecting …',
+'Group added redirect'			=>	'Group added. Redirecting …',
+'Group edited redirect'			=>	'Group edited. Redirecting …',
+
+'Add groups head'				=>	'Add/setup groups',
+'Add group subhead'				=>	'Add new group',
+'New group label'				=>	'Base new group on',
+'New group help'				=>	'Select a user group from which the new group will inherit its permission settings. The next page will let you fine-tune its settings.',
+'Default group subhead'			=>	'Set default group',
+'Default group label'			=>	'Default group',
+'Default group help'			=>	'This is the default user group, e.g. the group users are placed in when they register. For security reasons, users can\'t be placed in either the moderator or administrator user groups by default.',
+'Existing groups head'			=>	'Existing groups',
+'Edit groups subhead'			=>	'Edit/delete groups',
+'Edit groups info'				=>	'The pre-defined groups Guests, Administrators, Moderators and Members cannot be removed. However, they can be edited. Please note that in some groups, some options are unavailable (e.g. the <em>edit posts</em> permission for guests). Administrators always have full permissions.',
+'Edit link'						=>	'Edit',
+'Delete link'					=>	'Delete',
+'Group delete head'				=>	'Group delete',
+'Confirm delete subhead'		=>	'Confirm delete group',
+'Confirm delete info'			=>	'Are you sure that you want to delete the group <strong>%s</strong>?',
+'Confirm delete warn'			=>	'WARNING! After you deleted a group you cannot restore it.',
+'Delete group head'				=>	'Delete group',
+'Move users subhead'			=>	'Move users currently in group',
+'Move users info'				=>	'The group <strong>%s</strong> currently has <strong>%s</strong> members. Please select a group to which these members will be assigned upon deletion.',
+'Move users label'				=>	'Move users to',
+'Delete group'					=>	'Delete group',
+
+'Group settings head'			=>	'Group settings',
+'Group settings subhead'		=>	'Setup group options and permissions',
+'Group settings info'			=>	'Below options and permissions are the default permissions for the user group. These options apply if no forum specific permissions are in effect.',
+'Group title label'				=>	'Group title',
+'User title label'				=>	'User title',
+'User title help'				=>	'This title will override any rank users in this group have attained. Leave blank to use default title or rank.',
+'Mod privileges label'			=>	'Allow users moderator privileges',
+'Mod privileges help'			=>	'In order for a user in this group to have moderator abilities, he/she must be assigned to moderate one or more forums. This is done via the user administration page of the user\'s profile.',
+'Edit profile label'			=>	'Allow moderators to edit user profiles',
+'Edit profile help'				=>	'If moderator privileges are enabled, allow users in this group to edit user profiles.',
+'Rename users label'			=>	'Allow moderators to rename users',
+'Rename users help'				=>	'If moderator privileges are enabled, allow users in this group to rename users.',
+'Change passwords label'		=>	'Allow moderators to change passwords',
+'Change passwords help'			=>	'If moderator privileges are enabled, allow users in this group to change user passwords.',
+'Ban users label'				=>	'Allow moderators to ban users',
+'Ban users help'				=>	'If moderator privileges are enabled, allow users in this group to ban users.',
+'Read board label'				=>	'Read board',
+'Read board help'				=>	'Allow users in this group to view the board. This setting applies to every aspect of the board and can therefore not be overridden by forum specific settings. If this is set to "No", users in this group will only be able to login/logout and register.',
+'View user info label'			=>	'View user information',
+'View user info help'			=>	'Allow users to view the user list and user profiles.',
+'Post replies label'			=>	'Post replies',
+'Post replies help'				=>	'Allow users in this group to post replies in topics.',
+'Post topics label'				=>	'Post topics',
+'Post topics help'				=>	'Allow users in this group to post new topics.',
+'Edit posts label'				=>	'Edit posts',
+'Edit posts help'				=>	'Allow users in this group to edit their own posts.',
+'Delete posts label'			=>	'Delete posts',
+'Delete posts help'				=>	'Allow users in this group to delete their own posts.',
+'Delete topics label'			=>	'Delete topics',
+'Delete topics help'			=>	'Allow users in this group to delete their own topics (including any replies).',
+'Set own title label'			=>	'Set own user title',
+'Set own title help'			=>	'Allow users in this group to set their own user title.',
+'User search label'				=>	'Use search',
+'User search help'				=>	'Allow users in this group to use the search feature.',
+'User list search label'		=>	'Search user list',
+'User list search help'			=>	'Allow users in this group to freetext search for users in the user list.',
+'Send e-mails label'			=>	'Send e-mails',
+'Send e-mails help'				=>	'Allow users in this group to send e-mails to other users.',
+'Post flood label'				=>	'Post flood interval',
+'Post flood help'				=>	'Number of seconds that users in this group have to wait between posts. Set to 0 to disable.',
+'Search flood label'			=>	'Search flood interval',
+'Search flood help'				=>	'Number of seconds that users in this group have to wait between searches. Set to 0 to disable.',
+'E-mail flood label'			=>	'Email flood interval',
+'E-mail flood help'				=>	'Number of seconds that users in this group have to wait between emails. Set to 0 to disable.',
+'Report flood label'			=>	'Report flood interval',
+'Report flood help'				=>	'Number of seconds that users in this group have to wait between reports. Set to 0 to disable.',
+'Moderator info'				=>	'Please note that in order for a user in this group to have moderator abilities, he/she must be assigned to moderate one or more forums. This is done via the user administration page of the user\'s profile.',
+
+);
diff --git a/lang/English/admin_index.php b/lang/English/admin_index.php
new file mode 100644
index 0000000..bd2cd97
--- /dev/null
+++ b/lang/English/admin_index.php
@@ -0,0 +1,51 @@
+<?php
+
+// Language definitions used in admin_index.php
+$lang_admin_index = array(
+
+'fopen disabled message'			=>	'Unable to check for upgrade since \'allow_url_fopen\' is disabled on this system.',
+'Upgrade check failed message'		=>	'Check for upgrade failed for unknown reasons.',
+'Running latest version message'	=>	'You are running the latest version of FluxBB.',
+'New version available message'		=>	'A new version of FluxBB has been released. You can download the latest version at %s.',
+'PHPinfo disabled message'			=>	'The PHP function phpinfo() has been disabled on this server.',
+'Not available'						=>	'Not available',
+'Forum admin head'					=>	'Forum administration',
+'NA'								=>	'N/A',
+'Welcome to admin'					=>	'Welcome to the FluxBB administration control panel. From here you can control vital aspects of the board. Depending on whether you are an administrator or a moderator you can:',
+'Welcome 1'							=>	'Organize categories and forums.',
+'Welcome 2'							=>	'Set forum-wide options and preferences.',
+'Welcome 3'							=>	'Control permissions for users and guests.',
+'Welcome 4'							=>	'View IP statistics for users.',
+'Welcome 5'							=>	'Ban users.',
+'Welcome 6'							=>	'Censor words.',
+'Welcome 7'							=>	'Set up user ranks.',
+'Welcome 8'							=>	'Prune old posts.',
+'Welcome 9'							=>	'Handle post reports.',
+'Statistics head'					=>	'Statistics',
+'FluxBB version label'				=>	'FluxBB version',
+'Check for upgrade'					=>	'Check for upgrade',
+'FluxBB version data'				=>	'v%s - %s',
+'Server load label'					=>	'Server load',
+'Server load data'					=>	'%s - %s user(s) online',
+'Environment label'					=>	'Environment',
+'Environment data OS'				=>	'Operating system: %s',
+'Show info'							=>	'Show info',
+'Environment data version'			=>	'PHP: %s - %s',
+'Environment data acc'				=>	'Accelerator: %s',
+'Turck MMCache'						=>	'Turck MMCache',
+'Turck MMCache link'				=>	'turck-mmcache.sourceforge.net/',
+'ionCube PHP Accelerator'			=>	'ionCube PHP Accelerator',
+'ionCube PHP Accelerator link'		=>	'www.php-accelerator.co.uk/',
+'Alternative PHP Cache (APC)'		=>	'Alternative PHP Cache (APC)',
+'Alternative PHP Cache (APC) link'	=>	'www.php.net/apc/',
+'Zend Optimizer'					=>	'Zend Optimizer',
+'Zend Optimizer link'				=>	'www.zend.com/products/guard/zend-optimizer/',
+'eAccelerator'						=>	'eAccelerator',
+'eAccelerator link'					=>	'www.eaccelerator.net/',
+'XCache'							=>	'XCache',
+'XCache link'						=>	'xcache.lighttpd.net/',
+'Database label'					=>	'Database',
+'Database data rows'				=>	'Rows: %s',
+'Database data size'				=>	'Size: %s',
+
+);
diff --git a/lang/English/admin_maintenance.php b/lang/English/admin_maintenance.php
new file mode 100644
index 0000000..f4d5515
--- /dev/null
+++ b/lang/English/admin_maintenance.php
@@ -0,0 +1,40 @@
+<?php
+
+// Language definitions used in admin_maintenance.php
+$lang_admin_maintenance = array(
+
+'Maintenance head'				=>	'Forum maintenance',
+'Rebuild index subhead'			=>	'Rebuild search index',
+'Rebuild index info'			=>	'If you\'ve added, edited or removed posts manually in the database or if you\'re having problems searching, you should rebuild the search index. For best performance, you should put the forum in %s during rebuilding. <strong>Rebuilding the search index can take a long time and will increase server load during the rebuild process!</strong>',
+'Posts per cycle label'			=>	'Posts per cycle',
+'Posts per cycle help'			=>	'The number of posts to process per pageview. E.g. if you were to enter 300, three hundred posts would be processed and then the page would refresh. This is to prevent the script from timing out during the rebuild process.',
+'Starting post label'			=>	'Starting post ID',
+'Starting post help'			=>	'The post ID to start rebuilding at. The default value is the first available ID in the database. Normally you wouldn\'t want to change this.',
+'Empty index label'				=>	'Empty index',
+'Empty index help'				=>	'Select this if you want the search index to be emptied before rebuilding (see below).',
+'Rebuild completed info'		=>	'Once the process has completed, you will be redirected back to this page. It is highly recommended that you have JavaScript enabled in your browser during rebuilding (for automatic redirect when a cycle has completed). If you are forced to abort the rebuild process, make a note of the last processed post ID and enter that ID+1 in "Starting post ID" when/if you want to continue ("Empty index" must not be selected).',
+'Rebuild index'					=>	'Rebuild index',
+'Rebuilding search index'		=>	'Rebuilding search index',
+'Rebuilding index info'			=>	'Rebuilding index. This might be a good time to put on some coffee :-)',
+'Processing post'				=>	'Processing post <strong>%s</strong> …',
+'Click here'					=>	'Click here',
+'Javascript redirect failed'	=>	'JavaScript redirect unsuccessful. %s to continue …',
+'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
new file mode 100644
index 0000000..0274bdf
--- /dev/null
+++ b/lang/English/admin_options.php
@@ -0,0 +1,228 @@
+<?php
+
+// Language definitions used in admin-options.php
+$lang_admin_options = array(
+
+'Bad HTTP Referer message'			=>	'Bad HTTP_REFERER. If you have moved these forums from one location to another or switched domains, you need to update the Base URL manually in the database (look for o_base_url in the config table) and then clear the cache by deleting all .php files in the /cache directory.',
+'Must enter title message'			=>	'You must enter a board title.',
+'Invalid e-mail message'			=>	'The admin email address you entered is invalid.',
+'Invalid webmaster e-mail message'	=>	'The webmaster email address you entered is invalid.',
+'SMTP passwords did not match'		=>	'You need to enter the SMTP password twice exactly the same to change it.',
+'Enter announcement here'			=>	'Enter your announcement here.',
+'Enter rules here'					=>	'Enter your rules here.',
+'Default maintenance message'		=>	'The forums are temporarily down for maintenance. Please try again in a few minutes.',
+'Timeout error message'				=>	'The value of "Timeout online" must be smaller than the value of "Timeout visit".',
+'Options updated redirect'			=>	'Options updated. Redirecting …',
+'Options head'						=>	'Options',
+
+// Essentials section
+'Essentials subhead'				=>	'Essentials',
+'Board title label'					=>	'Board title',
+'Board title help'					=>	'The title of this bulletin board (shown at the top of every page). This field may <strong>not</strong> contain HTML.',
+'Board desc label'					=>	'Board description',
+'Board desc help'					=>	'A short description of this bulletin board (shown at the top of every page). This field may contain HTML.',
+'Base URL label'					=>	'Base URL',
+'Base URL help'						=>	'The complete URL of the board without trailing slash (i.e. http://www.mydomain.com/forums). This <strong>must</strong> be correct in order for all admin and moderator features to work. If you get "Bad referer" errors, it\'s probably incorrect.',
+'Timezone label'					=>	'Default time zone',
+'Timezone help'						=>	'The default time zone for guests and users attempting to register for the board.',
+'DST label'							=>	'Adjust for DST',
+'DST help'							=>	'Check if daylight savings is in effect (advances times by 1 hour).',
+'Language label'					=>	'Default language',
+'Language help'						=>	'The default language for guests and users who haven\'t changed from the default in their profile. If you remove a language pack, this must be updated.',
+'Default style label'				=>	'Default style',
+'Default style help'				=>	'The default style for guests and users who haven\'t changed from the default in their profile.',
+
+// Essentials section timezone options
+'UTC-12:00'							=>	'(UTC-12:00) International Date Line West',
+'UTC-11:00'							=>	'(UTC-11:00) Niue, Samoa',
+'UTC-10:00'							=>	'(UTC-10:00) Hawaii-Aleutian, Cook Island',
+'UTC-09:30'							=>	'(UTC-09:30) Marquesas Islands',
+'UTC-09:00'							=>	'(UTC-09:00) Alaska, Gambier Island',
+'UTC-08:30'							=>	'(UTC-08:30) Pitcairn Islands',
+'UTC-08:00'							=>	'(UTC-08:00) Pacific',
+'UTC-07:00'							=>	'(UTC-07:00) Mountain',
+'UTC-06:00'							=>	'(UTC-06:00) Central',
+'UTC-05:00'							=>	'(UTC-05:00) Eastern',
+'UTC-04:00'							=>	'(UTC-04:00) Atlantic',
+'UTC-03:30'							=>	'(UTC-03:30) Newfoundland',
+'UTC-03:00'							=>	'(UTC-03:00) Amazon, Central Greenland',
+'UTC-02:00'							=>	'(UTC-02:00) Mid-Atlantic',
+'UTC-01:00'							=>	'(UTC-01:00) Azores, Cape Verde, Eastern Greenland',
+'UTC'								=>	'(UTC) Western European, Greenwich',
+'UTC+01:00'							=>	'(UTC+01:00) Central European, West African',
+'UTC+02:00'							=>	'(UTC+02:00) Eastern European, Central African',
+'UTC+03:00'							=>	'(UTC+03:00) Eastern African',
+'UTC+03:30'							=>	'(UTC+03:30) Iran',
+'UTC+04:00'							=>	'(UTC+04:00) Moscow, Gulf, Samara',
+'UTC+04:30'							=>	'(UTC+04:30) Afghanistan',
+'UTC+05:00'							=>	'(UTC+05:00) Pakistan',
+'UTC+05:30'							=>	'(UTC+05:30) India, Sri Lanka',
+'UTC+05:45'							=>	'(UTC+05:45) Nepal',
+'UTC+06:00'							=>	'(UTC+06:00) Bangladesh, Bhutan, Yekaterinburg',
+'UTC+06:30'							=>	'(UTC+06:30) Cocos Islands, Myanmar',
+'UTC+07:00'							=>	'(UTC+07:00) Indochina, Novosibirsk',
+'UTC+08:00'							=>	'(UTC+08:00) Greater China, Australian Western, Krasnoyarsk',
+'UTC+08:45'							=>	'(UTC+08:45) Southeastern Western Australia',
+'UTC+09:00'							=>	'(UTC+09:00) Japan, Korea, Chita, Irkutsk',
+'UTC+09:30'							=>	'(UTC+09:30) Australian Central',
+'UTC+10:00'							=>	'(UTC+10:00) Australian Eastern',
+'UTC+10:30'							=>	'(UTC+10:30) Lord Howe',
+'UTC+11:00'							=>	'(UTC+11:00) Solomon Island, Vladivostok',
+'UTC+11:30'							=>	'(UTC+11:30) Norfolk Island',
+'UTC+12:00'							=>	'(UTC+12:00) New Zealand, Fiji, Magadan',
+'UTC+12:45'							=>	'(UTC+12:45) Chatham Islands',
+'UTC+13:00'							=>	'(UTC+13:00) Tonga, Phoenix Islands, Kamchatka',
+'UTC+14:00'							=>	'(UTC+14:00) Line Islands',
+
+// Timeout Section
+'Timeouts subhead'					=>	'Time and timeouts',
+'Time format label'					=>	'Time format',
+'PHP manual'						=>	'PHP manual',
+'Time format help'					=>	'[Current format: %s]. See %s for formatting options.',
+'Date format label'					=>	'Date format',
+'Date format help'					=>	'[Current format: %s]. See %s for formatting options.',
+'Visit timeout label'				=>	'Visit timeout',
+'Visit timeout help'				=>	'Number of seconds a user must be idle before his/hers last visit data is updated (primarily affects new message indicators).',
+'Online timeout label'				=>	'Online timeout',
+'Online timeout help'				=>	'Number of seconds a user must be idle before being removed from the online users list.',
+'Redirect time label'				=>	'Redirect time',
+'Redirect time help'				=>	'Number of seconds to wait when redirecting. If set to 0, no redirect page will be displayed (not recommended).',
+
+// Display Section
+'Display subhead'					=>	'Display',
+'Version number label'				=>	'Version number',
+'Version number help'				=>	'Show FluxBB version number in footer.',
+'Info in posts label'				=>	'User info in posts',
+'Info in posts help'				=>	'Show information about the poster under the username in topic view. The information affected is location, register date, post count and the contact links (email and URL).',
+'Post count label'					=>	'User post count',
+'Post count help'					=>	'Show the number of posts a user has made (affects topic view, profile and user list).',
+'Smilies label'						=>	'Smilies in posts',
+'Smilies help'						=>	'Convert smilies to small graphic icons.',
+'Smilies sigs label'				=>	'Smilies in signatures',
+'Smilies sigs help'					=>	'Convert smilies to small graphic icons in user signatures.',
+'Clickable links label'				=>	'Make clickable links',
+'Clickable links help'				=>	'When enabled, FluxBB will automatically detect any URLs in posts and make them clickable hyperlinks.',
+'Topic review label'				=>	'Topic review',
+'Topic review help'					=>	'Maximum number of posts to display when posting (newest first). Set to 0 to disable.',
+'Topics per page label'				=>	'Topics per page',
+'Topics per page help'				=>	'The default number of topics to display per page in a forum. Users can personalize this setting.',
+'Posts per page label'				=>	'Posts per page',
+'Posts per page help'				=>	'The default number of posts to display per page in a topic. Users can personalize this setting.',
+'Indent label'						=>	'Indent size',
+'Indent help'						=>	'If set to 8, a regular tab will be used when displaying text within the [code][/code] tag. Otherwise this many spaces will be used to indent the text.',
+'Quote depth label'					=>	'Maximum [quote] depth',
+'Quote depth help'					=>	'The maximum times a [quote] tag can go inside other [quote] tags, any tags deeper than this will be discarded.',
+
+// Features section
+'Features subhead'					=>	'Features',
+'Quick post label'					=>	'Quick post',
+'Quick post help'					=>	'When enabled, FluxBB will add a quick post form at the bottom of topics. This way users can post directly from the topic view.',
+'Users online label'				=>	'Users online',
+'Users online help'					=>	'Display info on the index page about guests and registered users currently browsing the board.',
+'Censor words label'				=>	'Censor words',
+'Censor words help'					=>	'Enable this to censor specific words in the board. See %s for more info.',
+'Signatures label'					=>	'Signatures',
+'Signatures help'					=>	'Allow users to attach a signature to their posts.',
+'User ranks label'					=>	'User ranks',
+'User ranks help'					=>	'Enable this to use user ranks. See %s for more info.',
+'User has posted label'				=>	'User has posted earlier',
+'User has posted help'				=>	'This feature displays a dot in front of topics in viewforum.php in case the currently logged in user has posted in that topic earlier. Disable if you are experiencing high server load.',
+'Topic views label'					=>	'Topic views',
+'Topic views help'					=>	'Keep track of the number of views a topic has. Disable if you are experiencing high server load in a busy forum.',
+'Quick jump label'					=>	'Quick jump',
+'Quick jump help'					=>	'Enable the quick jump (jump to forum) drop list.',
+'GZip label'						=>	'GZip output',
+'GZip help'							=>	'If enabled, FluxBB will gzip the output sent to browsers. This will reduce bandwidth usage, but use a little more CPU. This feature requires that PHP is configured with zlib (--with-zlib). Note: If you already have one of the Apache modules mod_gzip or mod_deflate set up to compress PHP scripts, you should disable this feature.',
+'Search all label'					=>	'Search all forums',
+'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 = &lt;a href="URL"&gt;LINK&lt;/a&gt; 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',
+'Reporting method label'			=>	'Reporting method',
+'Internal'							=>	'Internal',
+'By e-mail'							=>	'Email',
+'Both'								=>	'Both',
+'Reporting method help'				=>	'Select the method for handling topic/post reports. You can choose whether topic/post reports should be handled by the internal report system, emailed to the addresses on the mailing list (see below) or both.',
+'Mailing list label'				=>	'Mailing list',
+'Mailing list help'					=>	'A comma separated list of subscribers. The people on this list are the recipients of reports.',
+
+// Avatars section
+'Avatars subhead'					=>	'Avatars',
+'Use avatars label'					=>	'Use avatars',
+'Use avatars help'					=>	'When enabled, users will be able to upload an avatar which will be displayed under their title/rank.',
+'Upload directory label'			=>	'Upload directory',
+'Upload directory help'				=>	'The upload directory for avatars (relative to the FluxBB root directory). PHP must have write permissions to this directory.',
+'Max width label'					=>	'Max width',
+'Max width help'					=>	'The maximum allowed width of avatars in pixels (60 is recommended).',
+'Max height label'					=>	'Max height',
+'Max height help'					=>	'The maximum allowed height of avatars in pixels (60 is recommended).',
+'Max size label'					=>	'Max size',
+'Max size help'						=>	'The maximum allowed size of avatars in bytes (10240 is recommended).',
+
+// E-mail section
+'E-mail subhead'					=>	'Email',
+'Admin e-mail label'				=>	'Admin email',
+'Admin e-mail help'					=>	'The email address of the board administrator.',
+'Webmaster e-mail label'			=>	'Webmaster email',
+'Webmaster e-mail help'				=>	'This is the address that all emails sent by the board will be addressed from.',
+'Forum subscriptions label'			=>	'Forum subscriptions',
+'Forum subscriptions help'			=>	'Enable users to subscribe to forums (receive email when someone creates a new topic).',
+'Topic subscriptions label'			=>	'Topic subscriptions',
+'Topic subscriptions help'			=>	'Enable users to subscribe to topics (receive email when someone replies).',
+'SMTP address label'				=>	'SMTP server address',
+'SMTP address help'					=>	'The address of an external SMTP server to send emails with. You can specify a custom port number if the SMTP server doesn\'t run on the default port 25 (example: mail.myhost.com:3580). Leave blank to use the local mail program.',
+'SMTP username label'				=>	'SMTP username',
+'SMTP username help'				=>	'Username for SMTP server. Only enter a username if it is required by the SMTP server (most servers <strong>do not</strong> require authentication).',
+'SMTP password label'				=>	'SMTP password',
+'SMTP change password help'			=>	'Check this if you want to change or delete the currently stored password.',
+'SMTP password help'				=>	'Password for SMTP server. Only enter a password if it is required by the SMTP server (most servers <strong>do not</strong> require authentication). Please enter your password twice to confirm.',
+'SMTP SSL label'					=>	'Encrypt SMTP using SSL',
+'SMTP SSL help'						=>	'Encrypts the connection to the SMTP server using SSL. Should only be used if your SMTP server requires it and your version of PHP supports SSL.',
+
+// Registration Section
+'Registration subhead'				=>	'Registration',
+'Allow new label'					=>	'Allow new registrations',
+'Allow new help'					=>	'Controls whether this board accepts new registrations. Disable only under special circumstances.',
+'Verify label'						=>	'Verify registrations',
+'Verify help'						=>	'When enabled, users are emailed a random password when they register. They can then log in and change the password in their profile if they see fit. This feature also requires users to verify new email addresses if they choose to change from the one they registered with. This is an effective way of avoiding registration abuse and making sure that all users have "correct" email addresses in their profiles.',
+'Report new label'					=>	'Report new registrations',
+'Report new help'					=>	'If enabled, FluxBB will notify users on the mailing list (see above) when a new user registers in the forums.',
+'Use rules label'					=>	'User forum rules',
+'Use rules help'					=>	'When enabled, users must agree to a set of rules when registering (enter text below). The rules will always be available through a link in the navigation table at the top of every page.',
+'Rules label'						=>	'Enter your rules here',
+'Rules help'						=>	'Here you can enter any rules or other information that the user must review and accept when registering. If you enabled rules above you have to enter something here, otherwise it will be disabled. This text will not be parsed like regular posts and thus may contain HTML.',
+'E-mail default label'				=>	'Default email setting',
+'E-mail default help'				=>	'Choose the default privacy setting for new user registrations.',
+'Display e-mail label'				=>	'Display email address to other users.',
+'Hide allow form label'				=>	'Hide email address but allow form e-mail.',
+'Hide both label'					=>	'Hide email address and disallow form email.',
+
+// Announcement Section
+'Announcement subhead'				=>	'Announcements',
+'Display announcement label'		=>	'Display announcement',
+'Display announcement help'			=>	'Enable this to display the below message in the board.',
+'Announcement message label'		=>	'Announcement message',
+'Announcement message help'			=>	'This text will not be parsed like regular posts and thus may contain HTML.',
+
+// Maintenance Section
+'Maintenance subhead'				=>	'Maintenance',
+'Maintenance mode label'			=>	'Maintenance mode',
+'Maintenance mode help'				=>	'When enabled, the board will only be available to administrators. This should be used if the board needs to be taken down temporarily for maintenance. <strong>WARNING! Do not log out when the board is in maintenance mode.</strong> You will not be able to login again.',
+'Maintenance message label'			=>	'Maintenance message',
+'Maintenance message help'			=>	'The message that will be displayed to users when the board is in maintenance mode. If left blank, a default message will be used. This text will not be parsed like regular posts and thus may contain HTML.',
+
+);
diff --git a/lang/English/admin_permissions.php b/lang/English/admin_permissions.php
new file mode 100644
index 0000000..3c3600c
--- /dev/null
+++ b/lang/English/admin_permissions.php
@@ -0,0 +1,36 @@
+<?php
+
+// Language definitions used in admin-permissions.php
+$lang_admin_permissions = array(
+
+'Perms updated redirect'	=>	'Permissions updated. Redirecting …',
+'Permissions head'			=>	'Permissions',
+'Posting subhead'			=>	'Posting',
+'BBCode label'				=>	'BBCode',
+'BBCode help'				=>	'Allow BBCode in posts (recommended).',
+'Image tag label'			=>	'Image tag',
+'Image tag help'			=>	'Allow the BBCode [img][/img] tag in posts.',
+'All caps message label'	=>	'All caps message',
+'All caps message help'		=>	'Allow a message to contain only capital letters.',
+'All caps subject label'	=>	'All caps subject',
+'All caps subject help'		=>	'Allow a subject to contain only capital letters.',
+'Require e-mail label'		=>	'Require guest email',
+'Require e-mail help'		=>	'Require guests to supply an email address when posting.',
+'Signatures subhead'		=>	'Signatures',
+'BBCode sigs label'			=>	'BBCodes in signatures',
+'BBCode sigs help'			=>	'Allow BBCodes in user signatures.',
+'Image tag sigs label'		=>	'Image tag in signatures',
+'Image tag sigs help'		=>	'Allow the BBCode [img][/img] tag in user signatures (not recommended).',
+'All caps sigs label'		=>	'All caps signature',
+'All caps sigs help'		=>	'Allow a signature to contain only capital letters.',
+'Max sig length label'		=>	'Maximum signature length',
+'Max sig length help'		=>	'The maximum number of characters a user signature may contain.',
+'Max sig lines label'		=>	'Maximum signature lines',
+'Max sig lines help'		=>	'The maximum number of lines a user signature may contain.',
+'Registration subhead'		=>	'Registration',
+'Banned e-mail label'		=>	'Allow banned email addresses',
+'Banned e-mail help'		=>	'Allow users to register with or change to a banned email address/domain. If left at its default setting (yes), this action will be allowed, but an alert email will be sent to the mailing list (an effective way of detecting multiple registrations).',
+'Duplicate e-mail label'	=>	'Allow duplicate email addresses',
+'Duplicate e-mail help'		=>	'Controls whether users should be allowed to register with an email address that another user already has. If allowed, an alert email will be sent to the mailing list if a duplicate is detected.',
+
+);
diff --git a/lang/English/admin_ranks.php b/lang/English/admin_ranks.php
new file mode 100644
index 0000000..de53834
--- /dev/null
+++ b/lang/English/admin_ranks.php
@@ -0,0 +1,23 @@
+<?php
+
+// Language definitions used in admin_ranks.php
+$lang_admin_ranks = array(
+
+'Must be integer message'	=>	'Minimum posts must be a positive integer value.',
+'Dupe min posts message'	=>	'There is already a rank with a minimun posts value of %s.',
+'Must enter title message'	=>	'You must enter a rank title.',
+'Rank added redirect'		=>	'Rank added. Redirecting …',
+'Rank updated redirect'		=>	'Rank updated. Redirecting …',
+'Rank removed redirect'		=>	'Rank removed. Redirecting …',
+'Ranks head'				=>	'Ranks',
+'Add rank subhead'			=>	'Add rank',
+'Add rank info'				=>	'Enter a rank and the minimum number of posts a user must have made to attain the rank. Different ranks cannot have the same value for minimum posts. If a title is set for a user, the title will be displayed instead of any rank.',
+'Ranks enabled'				=>	'<strong>User ranks is enabled in %s.</strong>',
+'Ranks disabled'			=>	'<strong>User ranks is disabled in %s.</strong>',
+'Rank title label'			=>	'Rank title',
+'Minimum posts label'		=>	'Minimum posts',
+'Actions label'				=>	'Actions',
+'Edit remove subhead'		=>	'Edit/remove ranks',
+'No ranks in list'			=>	'No ranks in list',
+
+);
diff --git a/lang/English/admin_reports.php b/lang/English/admin_reports.php
new file mode 100644
index 0000000..0daa8da
--- /dev/null
+++ b/lang/English/admin_reports.php
@@ -0,0 +1,21 @@
+<?php
+
+// Language definitions used in admin_reports.php
+$lang_admin_reports = array(
+
+'Report zapped redirect'	=>	'Report marked as read. Redirecting …',
+'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',
+'Zap'						=>	'Mark as read',
+'No new reports'			=>	'There are no new reports.',
+'Last 10 head'				=>	'10 last read reports',
+'NA'						=>	'N/A',
+'Zapped subhead'			=>	'Marked as read %s by %s',
+'No zapped reports'			=>	'There are no read reports.',
+
+);
diff --git a/lang/English/admin_users.php b/lang/English/admin_users.php
new file mode 100644
index 0000000..49fad72
--- /dev/null
+++ b/lang/English/admin_users.php
@@ -0,0 +1,110 @@
+<?php
+
+// Language definitions used in admin-users.php
+$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.',
+'Username label'			=>	'Username',
+'E-mail address label'		=>	'Email address',
+'Title label'				=>	'Title',
+'Real name label'			=>	'Real name',
+'Website label'				=>	'Website',
+'Jabber label'				=>	'Jabber',
+'ICQ label'					=>	'ICQ',
+'MSN label'					=>	'MSN Messenger',
+'AOL label'					=>	'AOL IM',
+'Yahoo label'				=>	'Yahoo Messenger',
+'Location label'			=>	'Location',
+'Signature label'			=>	'Signature',
+'Admin note label'			=>	'Admin note',
+'Posts more than label'		=>	'Number of posts greater than',
+'Posts less than label'		=>	'Number of posts less than',
+'Last post after label'		=>	'Last post is after',
+'Date help'					=>	'(yyyy-mm-dd hh:mm:ss)',
+'Last post before label'	=>	'Last post is before',
+'Last visit after label'	=>	'Last visit is after',
+'Last visit before label'	=>	'Last visit is before',
+'Registered after label'	=>	'Registered after',
+'Registered before label'	=>	'Registered before',
+'Order by label'			=>	'Order by',
+'Order by username'			=>	'Username',
+'Order by e-mail'			=>	'Email',
+'Order by posts'			=>	'Number of posts',
+'Order by last post'		=>	'Last post',
+'Order by last visit'		=>	'Last visit',
+'Order by registered'		=>	'Registered',
+'Ascending'					=>	'Ascending',
+'Descending'				=>	'Descending',
+'User group label'			=>	'User group',
+'All groups'				=>	'All groups',
+'Unverified users'			=>	'Unverified users',
+'Submit search'				=>	'Submit search',
+'IP search head'			=>	'IP search',
+'IP search subhead'			=>	'Enter IP to search for',
+'IP address label'			=>	'IP address',
+'IP address help'			=>	'The IP address to search for in the post database.',
+'Find IP address'			=>	'Find IP address',
+
+'Results head'				=>	'Search Results',
+'Results username head'		=>	'Username',
+'Results e-mail head'		=>	'Email',
+'Results title head'		=>	'Title/Status',
+'Results posts head'		=>	'Posts',
+'Results admin note head'	=>	'Admin note',
+'Results actions head'		=>	'Actions',
+'Results IP address head'	=>	'IP address',
+'Results last used head'	=>	'Last used',
+'Results times found head'	=>	'Times found',
+'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',
+'Results guest'				=>	'Guest',
+'Results no IP found'		=>	'The supplied IP address could not be found in the database.',
+'No match'					=>	'No match'
+
+);
diff --git a/lang/English/common.php b/lang/English/common.php
new file mode 100644
index 0000000..6a0c85e
--- /dev/null
+++ b/lang/English/common.php
@@ -0,0 +1,163 @@
+<?php
+
+// Language definitions for frequently used strings
+$lang_common = array(
+
+// Text orientation and encoding
+'lang_direction'					=>	'ltr', // ltr (Left-To-Right) or rtl (Right-To-Left)
+'lang_identifier'					=>	'en',
+
+// Number formatting
+'lang_decimal_point'				=>	'.',
+'lang_thousands_sep'				=>	',',
+
+// Notices
+'Bad request'						=>	'Bad request. The link you followed is incorrect or outdated.',
+'No view'							=>	'You do not have permission to view these forums.',
+'No permission'						=>	'You do not have permission to access this page.',
+'Bad referrer'						=>	'Bad HTTP_REFERER. You were referred to this page from an unauthorized source. If the problem persists please make sure that \'Base URL\' is correctly set in Admin/Options and that you are visiting the forum by navigating to that URL. More information regarding the referrer check can be found in the FluxBB documentation.',
+'No cookie'							=>	'You appear to have logged in successfully, however a cookie has not been set. Please check your settings and if applicable, enable cookies for this website.',
+'Pun include error'					=>	'Unable to process user include %s from template %s. There is no such file in neither the template directory nor in the user include directory.',
+
+// Miscellaneous
+'Announcement'						=>	'Announcement',
+'Options'							=>	'Options',
+'Submit'							=>	'Submit', // "Name" of submit buttons
+'Ban message'						=>	'You are banned from this forum.',
+'Ban message 2'						=>	'The ban expires at the end of',
+'Ban message 3'						=>	'The administrator or moderator that banned you left the following message:',
+'Ban message 4'						=>	'Please direct any inquiries to the forum administrator at',
+'Never'								=>	'Never',
+'Today'								=>	'Today',
+'Yesterday'							=>	'Yesterday',
+'Info'								=>	'Info', // A common table header
+'Go back'							=>	'Go back',
+'Maintenance'						=>	'Maintenance',
+'Redirecting'						=>	'Redirecting',
+'Click redirect'					=>	'Click here if you do not want to wait any longer (or if your browser does not automatically forward you)',
+'on'								=>	'on', // As in "BBCode is on"
+'off'								=>	'off',
+'Invalid email'						=>	'The email address you entered is invalid.',
+'Required'							=>	'(Required)',
+'required field'					=>	'is a required field in this form.', // For javascript form validation
+'Last post'							=>	'Last post',
+'by'								=>	'by', // As in last post by someuser
+'New posts'							=>	'New posts', // The link that leads to the first new post
+'New posts info'					=>	'Go to the first new post in this topic.', // The popup text for new posts links
+'Username'							=>	'Username',
+'Password'							=>	'Password',
+'Email'								=>	'Email',
+'Send email'						=>	'Send email',
+'Moderated by'						=>	'Moderated by',
+'Registered'						=>	'Registered',
+'Subject'							=>	'Subject',
+'Message'							=>	'Message',
+'Topic'								=>	'Topic',
+'Forum'								=>	'Forum',
+'Posts'								=>	'Posts',
+'Replies'							=>	'Replies',
+'Pages'								=>	'Pages:',
+'Page'								=>	'Page %s',
+'BBCode'							=>	'BBCode:', // You probably shouldn't change this
+'img tag'							=>	'[img] tag:',
+'Smilies'							=>	'Smilies:',
+'and'								=>	'and',
+'Image link'						=>	'image', // This is displayed (i.e. <image>) instead of images when "Show images" is disabled in the profile
+'wrote'								=>	'wrote:', // For [quote]'s
+'Mailer'							=>	'%s Mailer', // As in "MyForums Mailer" in the signature of outgoing emails
+'Important information'				=>	'Important information',
+'Write message legend'				=>	'Write your message and submit',
+'Previous'							=>	'Previous',
+'Next'								=>	'Next',
+'Spacer'							=>	'…', // Ellipsis for paginate
+
+// Title
+'Title'								=>	'Title',
+'Member'							=>	'Member', // Default title
+'Moderator'							=>	'Moderator',
+'Administrator'						=>	'Administrator',
+'Banned'							=>	'Banned',
+'Guest'								=>	'Guest',
+
+// Stuff for include/parser.php
+'BBCode error no opening tag'		=>	'[/%1$s] was found without a matching [%1$s]',
+'BBCode error invalid nesting'		=>	'[%1$s] was opened within [%2$s], this is not allowed',
+'BBCode error invalid self-nesting'	=>	'[%s] was opened within itself, this is not allowed',
+'BBCode error no closing tag'		=>	'[%1$s] was found without a matching [/%1$s]',
+'BBCode error empty attribute'		=>	'[%s] tag had an empty attribute section',
+'BBCode code problem'				=>	'There is a problem with your [code] tags',
+'BBCode list size error'			=>	'Your list was too long to parse, please make it smaller!',
+
+// Stuff for the navigator (top of every page)
+'Index'								=>	'Index',
+'User list'							=>	'User list',
+'Rules'								=>	'Rules',
+'Search'							=>	'Search',
+'Register'							=>	'Register',
+'Login'								=>	'Login',
+'Not logged in'						=>	'You are not logged in.',
+'Profile'							=>	'Profile',
+'Logout'							=>	'Logout',
+'Logged in as'						=>	'Logged in as',
+'Admin'								=>	'Administration',
+'Last visit'						=>	'Last visit: %s',
+'Topic searches'					=>	'Topics:',
+'New posts header'					=>	'New',
+'Active topics'						=>	'Active',
+'Unanswered topics'					=>	'Unanswered',
+'Posted topics'						=>	'Posted',
+'Show new posts'					=>	'Find topics with new posts since your last visit.',
+'Show active topics'				=>	'Find topics with recent posts.',
+'Show unanswered topics'			=>	'Find topics with no replies.',
+'Show posted topics'				=>	'Find topics you have posted to.',
+'Mark all as read'					=>	'Mark all topics as read',
+'Mark forum read'					=>	'Mark this forum as read',
+'Title separator'					=>	' / ',
+
+// Stuff for the page footer
+'Board footer'						=>	'Board footer',
+'Jump to'							=>	'Jump to',
+'Go'								=>	' Go ', // Submit button in forum jump
+'Moderate topic'					=>	'Moderate topic',
+'Move topic'						=>	'Move topic',
+'Open topic'						=>	'Open topic',
+'Close topic'						=>	'Close topic',
+'Unstick topic'						=>	'Unstick topic',
+'Stick topic'						=>	'Stick topic',
+'Moderate forum'					=>	'Moderate forum',
+'Powered by'						=>	'Powered by %s',
+
+// Debug information
+'Debug table'						=>	'Debug information',
+'Querytime'							=>	'Generated in %1$s seconds, %2$s queries executed',
+'Memory usage'						=>	'Memory usage: %1$s',
+'Peak usage'						=>	'(Peak: %1$s)',
+'Query times'						=>	'Time (s)',
+'Query'								=>	'Query',
+'Total query time'					=>	'Total query time: %s',
+
+// For extern.php RSS feed
+'RSS description'					=>	'The most recent topics at %s.',
+'RSS description topic'				=>	'The most recent posts in %s.',
+'RSS reply'							=>	'Re: ', // The topic subject will be appended to this string (to signify a reply)
+'RSS active topics feed'			=>	'RSS active topics feed',
+'Atom active topics feed'			=>	'Atom active topics feed',
+'RSS forum feed'					=>	'RSS forum feed',
+'Atom forum feed'					=>	'Atom forum feed',
+'RSS topic feed'					=>	'RSS topic feed',
+'Atom topic feed'					=>	'Atom topic feed',
+
+// Admin related stuff in the header
+'New reports'						=>	'There are new reports',
+'Maintenance mode enabled'			=>	'Maintenance mode is enabled!',
+
+// Units for file sizes
+'Size unit B'						=>	'%s B',
+'Size unit KiB'						=>	'%s KiB',
+'Size unit MiB'						=>	'%s MiB',
+'Size unit GiB'						=>	'%s GiB',
+'Size unit TiB'						=>	'%s TiB',
+'Size unit PiB'						=>	'%s PiB',
+'Size unit EiB'						=>	'%s EiB',
+
+);
diff --git a/lang/English/delete.php b/lang/English/delete.php
new file mode 100644
index 0000000..b97478e
--- /dev/null
+++ b/lang/English/delete.php
@@ -0,0 +1,16 @@
+<?php
+
+// Language definitions used in delete.php
+$lang_delete = array(
+
+'Delete post'			=>	'Delete post',
+'Warning'				=>	'You are about to permanently delete this post.',
+'Topic warning'			=>	'Warning! This is the first post in the topic, the whole topic will be permanently deleted.',
+'Delete info'			=>	'The post you have chosen to delete is set out below for you to review before proceeding.',
+'Reply by'				=>	'Reply by %s - %s',
+'Topic by'				=>	'Topic started by %s - %s',
+'Delete'				=>	'Delete', // The submit button
+'Post del redirect'		=>	'Post deleted. Redirecting …',
+'Topic del redirect'	=>	'Topic deleted. Redirecting …'
+
+);
diff --git a/lang/English/forum.php b/lang/English/forum.php
new file mode 100644
index 0000000..a5973b8
--- /dev/null
+++ b/lang/English/forum.php
@@ -0,0 +1,17 @@
+<?php
+
+// Language definitions used in viewforum.php
+$lang_forum = array(
+
+'Post topic'	=>	'Post new topic',
+'Views'			=>	'Views',
+'Moved'			=>	'Moved:',
+'Sticky'		=>	'Sticky:',
+'Closed'		=>	'Closed:',
+'Empty forum'	=>	'Forum is empty.',
+'Mod controls'	=>	'Moderator controls',
+'Is subscribed'	=>	'You are currently subscribed to this forum',
+'Unsubscribe'	=>	'Unsubscribe',
+'Subscribe'		=>	'Subscribe to this forum'
+
+);
diff --git a/lang/English/help.php b/lang/English/help.php
new file mode 100644
index 0000000..7b4e533
--- /dev/null
+++ b/lang/English/help.php
@@ -0,0 +1,66 @@
+<?php
+
+// Language definitions used in help.php
+$lang_help = array(
+
+'Help'					=>	'Help',
+'produces'				=>	'produces',
+
+'BBCode'				=>	'BBCode',
+'BBCode info 1'			=>	'BBCode is a collection of formatting tags that are used to change the look of text in this forum. BBCode is based on the same principal as, and is very similar to, HTML. Below is a list of all the available BBCodes and instructions on how to use them.',
+'BBCode info 2'			=>	'Administrators have the ability to enable or disable BBCode. You can tell if BBCode is enabled or disabled out in the left margin whenever you post a message or edit your signature.',
+
+'Text style'			=>	'Text style',
+'Text style info'		=>	'The following tags change the appearance of text:',
+'Bold text'				=>	'Bold text',
+'Underlined text'		=>	'Underlined text',
+'Italic text'			=>	'Italic text',
+'Strike-through text'	=>	'Strike-through text',
+'Red text'				=>	'Red text',
+'Blue text'				=>	'Blue text',
+'Heading text'			=>	'Heading text',
+'Deleted text'			=>	'Deleted text',
+'Inserted text'			=>	'Inserted text',
+'Emphasised text'		=>	'Emphasised text',
+
+'Links and images'		=>	'Links and images',
+'Links info'			=>	'You can create links to other documents or to email addresses using the following tags:',
+'This help page'		=>	'This help page',
+'My email address'		=>	'My email address',
+'Images info'			=>	'If you want to display an image you can use the img tag. The text appearing after the "=" sign in the opening tag is used for the alt attribute and should be included whenever possible.',
+'FluxBB bbcode test'	=>	'FluxBB bbcode test',
+
+'Test topic'			=>	'Test topic',
+'Test post'				=>	'Test post',
+'Test forum'			=>	'Test forum',
+'Test user'				=>	'Test user',
+
+'Quotes'				=>	'Quotes',
+'Quotes info'			=>	'If you want to quote someone, you should use the quote tag.',
+'Quotes info 2'			=>	'If you don\'t want to quote anyone in particular, you can use the quote tag without specifying a name.',
+'Quote text'			=>	'This is the text i want to quote.',
+'produces quote box'	=>	'produces a quote box like this:',
+'quote note'			=>	'Note: If a username contains the characters [ or ] you can enclose it in quote marks.',
+
+'Code'					=>	'Code',
+'Code info'				=>	'When displaying source code you should make sure that you use the code tag. Text displayed with the code tag will use a monospaced font and will not be affected by other tags.',
+'Code text'				=>	'This is some code.',
+'produces code box'		=>	'produces a code box like this:',
+
+'Nested tags'			=>	'Nested tags',
+'Nested tags info'		=>	'BBCode can be nested to create more advanced formatting. For example:',
+'Bold, underlined text'	=>	'Bold, underlined text',
+
+'Lists'					=>	'Lists',
+'List info'				=>	'To create a list you can use the list tag. You can create 3 types of lists using the list tag.',
+'List text 1'			=>	'Example list item 1.',
+'List text 2'			=>	'Example list item 2.',
+'List text 3'			=>	'Example list item 3.',
+'produces list'			=>	'produces a bulleted list.',
+'produces decimal list'	=>	'produces a numbered list.',
+'produces alpha list'	=>	'produces an alphabetically labelled list.',
+
+'Smilies'				=>	'Smilies',
+'Smilies info'			=>	'If you like (and if it is enabled), the forum can convert a series of smilies to images representations of that smiley. This forum recognizes the following smilies and replaces them with images:'
+
+);
diff --git a/lang/English/index.html b/lang/English/index.html
new file mode 100644
index 0000000..89337b2
--- /dev/null
+++ b/lang/English/index.html
@@ -0,0 +1 @@
+<html><head><title>.</title></head><body>.</body></html>
diff --git a/lang/English/index.php b/lang/English/index.php
new file mode 100644
index 0000000..0ed222a
--- /dev/null
+++ b/lang/English/index.php
@@ -0,0 +1,20 @@
+<?php
+
+// Language definitions used in index.php
+$lang_index = array(
+
+'Topics'		=>	'Topics',
+'Link to'		=>	'Link to:', // As in "Link to: http://fluxbb.org/"
+'Empty board'	=>	'Board is empty.',
+'Newest user'	=>	'Newest registered user: %s',
+'Users online'	=>	'Registered users online: %s',
+'Guests online'	=>	'Guests online: %s',
+'No of users'	=>	'Total number of registered users: %s',
+'No of topics'	=>	'Total number of topics: %s',
+'No of posts'	=>	'Total number of posts: %s',
+'Online'		=>	'Online:', // As in "Online: User A, User B etc."
+'Board info'	=>	'Board information',
+'Board stats'	=>	'Board statistics',
+'User info'		=>	'User information'
+
+);
diff --git a/lang/English/install.php b/lang/English/install.php
new file mode 100644
index 0000000..318faf3
--- /dev/null
+++ b/lang/English/install.php
@@ -0,0 +1,120 @@
+<?php
+
+// Language definitions used in install.php, localized by adaur
+
+$lang_install = array(
+
+'Install message'				=>	'The file \'config.php\' doesn\'t exist or is corrupt. This implies that FluxBB has not yet been installed.',
+'Choose install language'		=>	'Choose the install script language',
+'Choose install language info'	=>	'The language used for this install script. The default language used for the board itself can be set below.',
+'Install language'				=>	'Install language',
+'Change language'				=>	'Change language',
+'Next'							=>	'Next',
+'Already installed'				=>	'It seems like FluxBB is already installed. You should go <a href="index.php">here</a> instead.',
+'You are running error'			=>	'You are running %1$s version %2$s. FluxBB %3$s requires at least %1$s %4$s to run properly. You must upgrade your %1$s installation before you can continue.',
+'My FluxBB Forum'				=>	'My FluxBB Forum',
+'Description'					=>	'Unfortunately no one can be told what FluxBB is - you have to see it for yourself.',
+'Username 1'					=>	'Usernames must be at least 2 characters long.',
+'Username 2'					=>	'Usernames must not be more than 25 characters long.',
+'Username 3'					=>	'The username guest is reserved.',
+'Username 4'					=>	'Usernames may not be in the form of an IP address.',
+'Username 5'					=>	'Usernames may not contain all the characters \', " and [ or ] at once.',
+'Username 6'					=>	'Usernames may not contain any of the text formatting tags (BBCode) that the forum uses.',
+'Short password'				=>	'Passwords must be at least 4 characters long.',
+'Passwords not match'			=>	'Passwords do not match.',
+'Wrong email'					=>	'The administrator email address you entered is invalid.',
+'No board title'				=>	'You must enter a board title.',
+'Error default language'		=>	'The default language chosen doesn\'t seem to exist.',
+'Error default style'			=>	'The default style chosen doesn\'t seem to exist.',
+'No DB extensions'				=>	'This PHP environment does not have support for any of the databases that FluxBB supports. PHP needs to have support for either MySQL, PostgreSQL or SQLite in order for FluxBB to be installed.',
+'Administrator username'		=>	'Administrator\'s username',
+'Administrator password 1'		=>	'Administrator password 1',
+'Administrator password 2'		=>	'Administrator password 2',
+'Administrator email'			=>	'Administrator\'s email',
+'Board title'					=>	'Board title',
+'Base URL'						=>	'Base URL',
+'Required field'				=>	'is a required field in this form.',
+'FluxBB Installation'			=>	'FluxBB Installation',
+'Welcome'						=>	'You are about to install FluxBB. In order to install FluxBB, you must complete the form set out below. If you encounter any difficulties with the installation, please refer to the documentation.',
+'Install'						=>	'Install FluxBB %s',
+'Errors'						=>	'The following errors need to be corrected:',
+'Database setup'				=>	'Database setup',
+'Info 1'						=>	'Please enter the requested information in order to setup your database for FluxBB. You must know all the information asked for before proceeding with the installation.',
+'Select database'				=>	'Select your database type',
+'Info 2'						=>	'FluxBB currently supports MySQL, PostgreSQL and SQLite. If your database of choice is missing from the drop-down menu below, it means this PHP environment does not have support for that particular database. More information regarding support for particular versions of each database can be found in the FAQ.',
+'Dual MySQL'					=>	'FluxBB has detected that your PHP environment supports two different ways of communicating with MySQL. The two options are called standard and improved. If you are uncertain which one to use, start by trying improved and if that fails, try standard.',
+'InnoDB'						=>	'FluxBB has detected that your MySQL server might support <a href="http://dev.mysql.com/doc/refman/5.0/en/innodb.html">InnoDB</a>. This would be a good choice if you are planning to run a large forum. If you are uncertain, it is recommended that you do not use InnoDB.',
+'Database type'					=>	'Database type',
+'Required'						=>	'(Required)',
+'Database hostname'				=>	'Enter your database server hostname',
+'Info 3'						=>	'The address of the database server (example: localhost, db.myhost.com or 192.168.0.15). You can specify a custom port number if your database doesn\'t run on the default port (example: localhost:3580). For SQLite support, just enter anything or leave it at \'localhost\'.',
+'Database server hostname'		=>'Database server hostname',
+'Database enter name'			=>	'Enter the name of your database',
+'Info 4'						=>	'The name of the database that FluxBB will be installed into. The database must exist. For SQLite, this is the relative path to the database file. For security reasons, it is highly recommended to place the SQLite database in a private (not web accessible) directory whenever possible. If the SQLite database file does not exist, FluxBB will attempt to create it.',
+'Database name'					=>	'Database name',
+'Database enter informations'	=>	'Enter your database username and password',
+'Database username'				=>	'Database username',
+'Info 5'						=>	'Enter the username and password with which you connect to the database. Ignore for SQLite.',
+'Database password'				=>	'Database password',
+'Database enter prefix'			=>	'Enter database table prefix',
+'Info 6'						=>	'If you like, you can specify a table prefix. This way you can run multiple copies of FluxBB in the same database (example: foo_).',
+'Table prefix'					=>	'Table prefix',
+'Administration setup'			=>	'Administration setup',
+'Info 7'						=>	'Please enter the requested information in order to setup an administrator for your FluxBB installation.',
+'Admin enter username'			=>	'Enter Administrator\'s username',
+'Info 8'						=>	'The username of the forum administrator. You can later create more administrators and moderators. Usernames can be between 2 and 25 characters long.',
+'Admin enter password'			=>	'Enter and confirm Administrator\'s password',
+'Info 9'						=>	'Passwords must be at least 4 characters long. Passwords are case sensitive.',
+'Password'						=>	'Password',
+'Confirm password'				=>	'Confirm password',
+'Admin enter email'				=>	'Enter Administrator\'s email',
+'Info 10'						=>	'The email address of the forum administrator.',
+'Board setup'					=>	'Board setup',
+'Info 11'						=>	'Please enter the requested information in order to setup your FluxBB board.',
+'Enter board title'				=>	'Enter your board\'s title',
+'Info 12'						=>	'The title of this bulletin board (shown at the top of every page).',
+'Enter board description'		=>	'Enter your board\'s description',
+'Info 13'						=>	'A short description of this bulletin board (shown at the top of every page). This field may contain HTML.',
+'Board description'				=>	'Board description',
+'Enter base URL'				=>	'Enter the Base URL of your FluxBB installation',
+'Info 14'						=>	'The URL (without trailing slash) of your FluxBB forum (example: http://forum.myhost.com or http://myhost.com/~myuser). This <strong>must</strong> be correct, otherwise, administrators and moderators will not be able to submit any forms. Please note that the preset value below is just an educated guess by FluxBB.',
+'Choose the default language'	=>	'Choose the default language',
+'Info 15'						=>	'The default language used for guests and users who haven\'t changed from the default in their profile.',
+'Default language'				=>	'Default language',
+'Choose the default style'		=>	'Choose the default style',
+'Info 16'						=>	'The default style used for guests and users who haven\'t changed from the default in their profile.',
+'Default style'					=>	'Default style',
+'Start install'					=>	'Start install',
+'DB type not valid'				=>	'\'%s\' is not a valid database type',
+'Table prefix error'			=>	'The table prefix \'%s\' contains illegal characters or is too long. The prefix may contain the letters a to z, any numbers and the underscore character. They must however not start with a number. The maximum length is 40 characters. Please choose a different prefix',
+'Prefix reserved'				=>	'The table prefix \'sqlite_\' is reserved for use by the SQLite engine. Please choose a different prefix',
+'Existing table error'			=>	'A table called \'%susers\' is already present in the database \'%s\'. This could mean that FluxBB is already installed or that another piece of software is installed and is occupying one or more of the table names FluxBB requires. If you want to install multiple copies of FluxBB in the same database, you must choose a different table prefix',
+'InnoDB off'					=>	'InnoDB does not seem to be enabled. Please choose a database layer that does not have InnoDB support, or enable InnoDB on your MySQL server',
+'Administrators'				=>	'Administrators',
+'Administrator'					=>	'Administrator',
+'Moderators'					=>	'Moderators',
+'Moderator'						=>	'Moderator',
+'Guests'						=>	'Guests',
+'Guest'							=>	'Guest',
+'New member'					=>	'New member',
+'Members'						=>	'Members',
+'Member'						=>	'Member',
+'Announcement'					=>	'Enter your announcement here.',
+'Rules'							=>	'Enter your rules here',
+'Maintenance message'			=>	'The forums are temporarily down for maintenance. Please try again in a few minutes.',
+'Test post'						=>	'Test topic',
+'Message'						=>	'If you are looking at this (which I guess you are), the install of FluxBB appears to have worked! Now log in and head over to the administration control panel to configure your forum.',
+'Test category'					=>	'Test category',
+'Test forum'					=>	'Test forum',
+'This is just a test forum'		=>	'This is just a test forum',
+'Alert cache'					=>	'<strong>The cache directory is currently not writable!</strong> In order for FluxBB to function properly, the directory <em>%s</em> must be writable by PHP. Use chmod to set the appropriate directory permissions. If in doubt, chmod to 0777.',
+'Alert avatar'					=>	'<strong>The avatar directory is currently not writable!</strong> If you want users to be able to upload their own avatar images you must see to it that the directory <em>%s</em> is writable by PHP. You can later choose to save avatar images in a different directory (see Admin/Options). Use chmod to set the appropriate directory permissions. If in doubt, chmod to 0777.',
+'Alert upload'					=>	'<strong>File uploads appear to be disallowed on this server!</strong> If you want users to be able to upload their own avatar images you must enable the file_uploads configuration setting in PHP. Once file uploads have been enabled, avatar uploads can be enabled in Administration/Options/Features.',
+'FluxBB has been installed'		=>	'FluxBB has been installed. To finalize the installation please follow the instructions below.',
+'Final instructions'			=>	'Final instructions',
+'Info 17'						=>	'To finalize the installation, you need to click on the button below to download a file called config.php. You then need to upload this file to the root directory of your FluxBB installation.',
+'Info 18'						=>	'Once you have uploaded config.php, FluxBB will be fully installed! At that point, you may <a href="index.php">go to the forum index</a>.',
+'Download config.php file'		=>	'Download config.php file',
+'FluxBB fully installed'		=>	'FluxBB has been fully installed! You may now <a href="index.php">go to the forum index</a>.',
+
+);
diff --git a/lang/English/login.php b/lang/English/login.php
new file mode 100644
index 0000000..244ee06
--- /dev/null
+++ b/lang/English/login.php
@@ -0,0 +1,26 @@
+<?php
+
+// Language definitions used in login.php
+$lang_login = array(
+
+// Miscellaneous
+'Wrong user/pass'			=>	'Wrong username and/or password.',
+'Forgotten pass'			=>	'Forgotten your password?',
+'Login redirect'			=>	'Logged in successfully. Redirecting …',
+'Logout redirect'			=>	'Logged out. Redirecting …',
+'No email match'			=>	'There is no user registered with the email address',
+'Request pass'				=>	'Request password',
+'Request pass legend'		=>	'Enter the email address with which you registered',
+'Request pass info'			=>	'A new password together with a link to activate the new password will be sent to that address.',
+'Not registered'			=>	'Not registered yet?',
+'Login legend'				=>	'Enter your username and password below',
+'Remember me'				=>	'Log me in automatically each time I visit.',
+'Login info'				=>	'If you have not registered or have forgotten your password click on the appropriate link below.',
+'New password errors'		=>	'Password request error',
+'New passworderrors info'	=>	'The following error needs to be corrected before a new password can be sent:',
+
+// Forget password mail stuff
+'Forget mail'				=>	'An email has been sent to the specified address with instructions on how to change your password. If it does not arrive you can contact the forum administrator at',
+'Email flood'				=>	'This account has already requested a password reset in the past hour. Please wait a while before requesting a new password again.'
+
+);
diff --git a/lang/English/mail_templates/activate_email.tpl b/lang/English/mail_templates/activate_email.tpl
new file mode 100644
index 0000000..49095c0
--- /dev/null
+++ b/lang/English/mail_templates/activate_email.tpl
@@ -0,0 +1,12 @@
+Subject: Change email address requested
+
+Hello <username>,
+
+You have requested to have a new email address assigned to your account in the discussion forum at <base_url>. If you didn't request this or if you don't want to change your email address you should just ignore this message. Only if you visit the activation page below will your email address be changed. In order for the activation page to work, you must be logged in to the forum.
+
+To change your email address, please visit the following page:
+<activation_url>
+
+--
+<board_mailer> Mailer
+(Do not reply to this message)
diff --git a/lang/English/mail_templates/activate_password.tpl b/lang/English/mail_templates/activate_password.tpl
new file mode 100644
index 0000000..b408927
--- /dev/null
+++ b/lang/English/mail_templates/activate_password.tpl
@@ -0,0 +1,14 @@
+Subject: New password requested
+
+Hello <username>,
+
+You have requested to have a new password assigned to your account in the discussion forum at <base_url>. If you didn't request this or if you don't want to change your password you should just ignore this message. Only if you visit the activation page below will your password be changed.
+
+Your new password is: <new_password>
+
+To change your password, please visit the following page:
+<activation_url>
+
+--
+<board_mailer> Mailer
+(Do not reply to this message)
diff --git a/lang/English/mail_templates/banned_email_change.tpl b/lang/English/mail_templates/banned_email_change.tpl
new file mode 100644
index 0000000..276662f
--- /dev/null
+++ b/lang/English/mail_templates/banned_email_change.tpl
@@ -0,0 +1,9 @@
+Subject: Alert - Banned email detected
+
+User '<username>' changed to banned email address: <email>
+
+User profile: <profile_url>
+
+--
+<board_mailer> Mailer
+(Do not reply to this message)
diff --git a/lang/English/mail_templates/banned_email_post.tpl b/lang/English/mail_templates/banned_email_post.tpl
new file mode 100644
index 0000000..f7e0243
--- /dev/null
+++ b/lang/English/mail_templates/banned_email_post.tpl
@@ -0,0 +1,9 @@
+Subject: Alert - Banned email detected
+
+User '<username>' posted with banned email address: <email>
+
+Post URL: <post_url>
+
+--
+<board_mailer> Mailer
+(Do not reply to this message)
diff --git a/lang/English/mail_templates/banned_email_register.tpl b/lang/English/mail_templates/banned_email_register.tpl
new file mode 100644
index 0000000..f0085ec
--- /dev/null
+++ b/lang/English/mail_templates/banned_email_register.tpl
@@ -0,0 +1,9 @@
+Subject: Alert - Banned email detected
+
+User '<username>' registered with banned email address: <email>
+
+User profile: <profile_url>
+
+--
+<board_mailer> Mailer
+(Do not reply to this message)
diff --git a/lang/English/mail_templates/dupe_email_change.tpl b/lang/English/mail_templates/dupe_email_change.tpl
new file mode 100644
index 0000000..583fb24
--- /dev/null
+++ b/lang/English/mail_templates/dupe_email_change.tpl
@@ -0,0 +1,9 @@
+Subject: Alert - Duplicate email detected
+
+User '<username>' changed to an email address that also belongs to: <dupe_list>
+
+User profile: <profile_url>
+
+--
+<board_mailer> Mailer
+(Do not reply to this message)
diff --git a/lang/English/mail_templates/dupe_email_register.tpl b/lang/English/mail_templates/dupe_email_register.tpl
new file mode 100644
index 0000000..b1cb363
--- /dev/null
+++ b/lang/English/mail_templates/dupe_email_register.tpl
@@ -0,0 +1,9 @@
+Subject: Alert - Duplicate email detected
+
+User '<username>' registered with an email address that also belongs to: <dupe_list>
+
+User profile: <profile_url>
+
+--
+<board_mailer> Mailer
+(Do not reply to this message)
diff --git a/lang/English/mail_templates/form_email.tpl b/lang/English/mail_templates/form_email.tpl
new file mode 100644
index 0000000..e3e0d5f
--- /dev/null
+++ b/lang/English/mail_templates/form_email.tpl
@@ -0,0 +1,13 @@
+Subject: <mail_subject>
+
+<sender> from <board_title> has sent you a message. You can reply to <sender> by replying to this email.
+
+The message reads as follows:
+-----------------------------------------------------------------------
+
+<mail_message>
+
+-----------------------------------------------------------------------
+
+--
+<board_mailer> Mailer
diff --git a/lang/English/mail_templates/index.html b/lang/English/mail_templates/index.html
new file mode 100644
index 0000000..89337b2
--- /dev/null
+++ b/lang/English/mail_templates/index.html
@@ -0,0 +1 @@
+<html><head><title>.</title></head><body>.</body></html>
diff --git a/lang/English/mail_templates/new_reply.tpl b/lang/English/mail_templates/new_reply.tpl
new file mode 100644
index 0000000..286914e
--- /dev/null
+++ b/lang/English/mail_templates/new_reply.tpl
@@ -0,0 +1,11 @@
+Subject: Reply to topic: '<topic_subject>'
+
+<replier> has replied to the topic '<topic_subject>' to which you are subscribed. There may be more new replies, but this is the only notification you will receive until you visit the board again.
+
+The post is located at <post_url>
+
+You can unsubscribe by going to <unsubscribe_url>
+
+--
+<board_mailer> Mailer
+(Do not reply to this message)
diff --git a/lang/English/mail_templates/new_reply_full.tpl b/lang/English/mail_templates/new_reply_full.tpl
new file mode 100644
index 0000000..4fbc777
--- /dev/null
+++ b/lang/English/mail_templates/new_reply_full.tpl
@@ -0,0 +1,18 @@
+Subject: Reply to topic: '<topic_subject>'
+
+<replier> has replied to the topic '<topic_subject>' to which you are subscribed. There may be more new replies, but this is the only notification you will receive until you visit the board again.
+
+The post is located at <post_url>
+
+The message reads as follows:
+-----------------------------------------------------------------------
+
+<message>
+
+-----------------------------------------------------------------------
+
+You can unsubscribe by going to <unsubscribe_url>
+
+--
+<board_mailer> Mailer
+(Do not reply to this message)
diff --git a/lang/English/mail_templates/new_report.tpl b/lang/English/mail_templates/new_report.tpl
new file mode 100644
index 0000000..dedb113
--- /dev/null
+++ b/lang/English/mail_templates/new_report.tpl
@@ -0,0 +1,9 @@
+Subject: Report(<forum_id>) - '<topic_subject>'
+
+User '<username>' has reported the following message: <post_url>
+
+Reason: <reason>
+
+--
+<board_mailer> Mailer
+(Do not reply to this message)
diff --git a/lang/English/mail_templates/new_topic.tpl b/lang/English/mail_templates/new_topic.tpl
new file mode 100644
index 0000000..2bc048b
--- /dev/null
+++ b/lang/English/mail_templates/new_topic.tpl
@@ -0,0 +1,11 @@
+Subject: New topic in forum: '<forum_name>'
+
+<poster> has posted a new topic '<topic_subject>' in the forum '<forum_name>', to which you are subscribed.
+
+The topic is located at <topic_url>
+
+You can unsubscribe by going to <unsubscribe_url>
+
+--
+<board_mailer> Mailer
+(Do not reply to this message)
diff --git a/lang/English/mail_templates/new_topic_full.tpl b/lang/English/mail_templates/new_topic_full.tpl
new file mode 100644
index 0000000..f70c726
--- /dev/null
+++ b/lang/English/mail_templates/new_topic_full.tpl
@@ -0,0 +1,18 @@
+Subject: New topic in forum: '<forum_name>'
+
+<poster> has posted a new topic '<topic_subject>' in the forum '<forum_name>', to which you are subscribed.
+
+The topic is located at <topic_url>
+
+The message reads as follows:
+-----------------------------------------------------------------------
+
+<message>
+
+-----------------------------------------------------------------------
+
+You can unsubscribe by going to <unsubscribe_url>
+
+--
+<board_mailer> Mailer
+(Do not reply to this message)
diff --git a/lang/English/mail_templates/new_user.tpl b/lang/English/mail_templates/new_user.tpl
new file mode 100644
index 0000000..5725b1c
--- /dev/null
+++ b/lang/English/mail_templates/new_user.tpl
@@ -0,0 +1,9 @@
+Subject: Alert - New registration
+
+User '<username>' registered in the forums at <base_url>
+
+User profile: <profile_url>
+
+--
+<board_mailer> Mailer
+(Do not reply to this message)
diff --git a/lang/English/mail_templates/rename.tpl b/lang/English/mail_templates/rename.tpl
new file mode 100644
index 0000000..3cba50d
--- /dev/null
+++ b/lang/English/mail_templates/rename.tpl
@@ -0,0 +1,12 @@
+Subject: User account renamed
+
+During an upgrade to the forums at <base_url> it was determined your username is too similar to an existing user. Your username has been changed accordingly.
+
+Old username: <old_username>
+New username: <new_username>
+
+We apologise for any inconvenience caused.
+
+--
+<board_mailer> Mailer
+(Do not reply to this message)
diff --git a/lang/English/mail_templates/welcome.tpl b/lang/English/mail_templates/welcome.tpl
new file mode 100644
index 0000000..779a574
--- /dev/null
+++ b/lang/English/mail_templates/welcome.tpl
@@ -0,0 +1,12 @@
+Subject: Welcome to <board_title>!
+
+Thank you for registering in the forums at <base_url>. Your account details are:
+
+Username: <username>
+Password: <password>
+
+Login at <login_url> to activate the account.
+
+--
+<board_mailer> Mailer
+(Do not reply to this message)
diff --git a/lang/English/misc.php b/lang/English/misc.php
new file mode 100644
index 0000000..9bca09c
--- /dev/null
+++ b/lang/English/misc.php
@@ -0,0 +1,93 @@
+<?php
+
+// Language definitions used in various scripts
+$lang_misc = array(
+
+'Mark read redirect'			=>	'All topics and forums have been marked as read. Redirecting …',
+'Mark forum read redirect'		=>	'All topics in the specified forum have been marked as read. Redirecting …',
+
+// Send email
+'Form email disabled'			=>	'The user you are trying to send an email to has disabled form email.',
+'No email subject'				=>	'You must enter a subject.',
+'No email message'				=>	'You must enter a message.',
+'Too long email message'		=>	'Messages cannot be longer than 65535 characters (64 KB).',
+'Email flood'					=>	'At least %s seconds have to pass between sent emails. Please wait a while and try sending again.',
+'Email sent redirect'			=>	'Email sent. Redirecting …',
+'Send email to'					=>	'Send email to',
+'Email subject'					=>	'Subject',
+'Email message'					=>	'Message',
+'Email disclosure note'			=>	'Please note that by using this form, your email address will be disclosed to the recipient.',
+'Write email'					=>	'Write and submit your email message',
+
+// Report
+'No reason'						=>	'You must enter a reason.',
+'Reason too long'				=>	'Your message must be under 65535 bytes (~64kb).',
+'Report flood'					=>	'At least %s seconds have to pass between reports. Please wait a while and try sending again.',
+'Report redirect'				=>	'Post reported. Redirecting …',
+'Report post'					=>	'Report post',
+'Reason'						=>	'Reason',
+'Reason desc'					=>	'Please enter a short reason why you are reporting this post',
+
+// Subscriptions
+'Already subscribed topic'		=>	'You are already subscribed to this topic.',
+'Already subscribed forum'		=>	'You are already subscribed to this forum.',
+'Subscribe redirect'			=>	'Your subscription has been added. Redirecting …',
+'Not subscribed topic'			=>	'You are not subscribed to this topic.',
+'Not subscribed forum'			=>	'You are not subscribed to this forum.',
+'Unsubscribe redirect'			=>	'Your subscription has been removed. Redirecting …',
+
+// General forum and topic moderation
+'Moderate'						=>	'Moderate',
+'Select'						=>	'Select', // the header of a column of checkboxes
+'Move'							=>	'Move',
+'Split'							=>	'Split',
+'Delete'						=>	'Delete',
+'Merge'							=>	'Merge',
+
+// Moderate forum
+'Open'							=>	'Open',
+'Close'							=>	'Close',
+'Move topic'					=>	'Move topic',
+'Move topics'					=>	'Move topics',
+'Move legend'					=>	'Select destination of move',
+'Move to'						=>	'Move to',
+'Nowhere to move'				=>	'There are no forums into which you can move topics.',
+'Leave redirect'				=>	'Leave redirect topic(s)',
+'Move topic redirect'			=>	'Topic moved. Redirecting …',
+'Move topics redirect'			=>	'Topics moved. Redirecting …',
+'Confirm delete legend'			=>	'Please confirm deletion',
+'Delete topics'					=>	'Delete topics',
+'Delete topics comply'			=>	'Are you sure you want to delete the selected topics?',
+'Delete topics redirect'		=>	'Topics deleted. Redirecting …',
+'Open topic redirect'			=>	'Topic opened. Redirecting …',
+'Open topics redirect'			=>	'Topics opened. Redirecting …',
+'Close topic redirect'			=>	'Topic closed. Redirecting …',
+'Close topics redirect'			=>	'Topics closed. Redirecting …',
+'No topics selected'			=>	'You must select at least one topic for move/delete/open/close.',
+'Not enough topics selected'	=>	'You must select at least two topics for merge.',
+'Stick topic redirect'			=>	'Topic sticked. Redirecting …',
+'Unstick topic redirect'		=>	'Topic unsticked. Redirecting …',
+'Merge topics'					=>	'Merge topics',
+'Merge topics redirect'			=>	'Topics merged. Redirecting …',
+'Confirm merge legend'			=>	'Please confirm merge',
+'New subject'					=>	'New subject',
+
+// Split multiple posts in topic
+'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 …',
+
+// Delete multiple posts in topic
+'Delete posts'					=>	'Delete posts',
+'Cannot select first'			=>	'First post cannot be selected for split/delete.',
+'Delete posts comply'			=>	'Are you sure you want to delete the selected posts?',
+'Delete posts redirect'			=>	'Posts deleted. Redirecting …',
+'No posts selected'				=>	'You must select at least one post for split/delete.',
+
+// Get host
+'Host info 1'					=>	'The IP address is: %s',
+'Host info 2'					=>	'The host name is: %s',
+'Show more users'				=>	'Show more users for this IP',
+
+);
diff --git a/lang/English/post.php b/lang/English/post.php
new file mode 100644
index 0000000..b89804b
--- /dev/null
+++ b/lang/English/post.php
@@ -0,0 +1,39 @@
+<?php
+
+// Language definitions used in post.php and edit.php
+$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 than %s bytes.',
+'All caps subject'	=>	'Subjects cannot contain only capital letters.',
+'All caps message'	=>	'Posts cannot contain only capital letters.',
+'Empty after strip'	=>	'It seems your post consisted of empty BBCodes only. It is possible that this happened because e.g. the innermost quote was discarded because of the maximum quote depth level.',
+
+// Posting
+'Post errors'		=>	'Post errors',
+'Post errors info'	=>	'The following errors need to be corrected before the message can be posted:',
+'Post preview'		=>	'Post preview',
+'Guest name'		=>	'Name', // For guests (instead of Username)
+'Post redirect'		=>	'Post entered. Redirecting …',
+'Post a reply'		=>	'Post a reply',
+'Post new topic'	=>	'Post new topic',
+'Hide smilies'		=>	'Never show smilies as icons for this post',
+'Subscribe'			=>	'Subscribe to this topic',
+'Stay subscribed'	=>	'Stay subscribed to this topic',
+'Topic review'		=>	'Topic review (newest first)',
+'Flood start'		=>	'At least',
+'flood end'			=>	'seconds have to pass between posts. Please wait a little while and try posting again.',
+'Preview'			=>	'Preview', // submit button to preview message
+
+// Edit post
+'Edit post legend'	=>	'Edit the post and submit changes',
+'Silent edit'		=>	'Silent edit (don\'t display "Edited by ..." in topic view)',
+'Edit post'			=>	'Edit post',
+'Edit redirect'		=>	'Post updated. Redirecting …'
+
+);
diff --git a/lang/English/prof_reg.php b/lang/English/prof_reg.php
new file mode 100644
index 0000000..7a0a6f6
--- /dev/null
+++ b/lang/English/prof_reg.php
@@ -0,0 +1,79 @@
+<?php
+
+// Language definitions used in both profile.php and register.php
+$lang_prof_reg = array(
+
+'Email legend'				=>	'Enter a valid email address',
+'Email legend 2'			=>	'Enter and confirm a valid email address',
+'Localisation legend'		=>	'Set your localisation options',
+'Time zone'					=>	'Time zone',
+'Time zone info'			=>	'For the forum to display times correctly you must select your local time zone. If Daylight Savings Time is in effect you should also check the option provided which will advance times by 1 hour.',
+'DST'						=>	'Daylight Savings Time is in effect (advance time by 1 hour).',
+'Time format'				=>	'Time format',
+'Date format'				=>	'Date format',
+'Default'					=>	'Default',
+'Language'					=>	'Language',
+'Email setting info'		=>	'Select whether you want your email address to be viewable to other users or not and if you want other users to be able to send you email via the forum (form email) or not.',
+'Email setting 1'			=>	'Display your email address.',
+'Email setting 2'			=>	'Hide your email address but allow form email.',
+'Email setting 3'			=>	'Hide your email address and disallow form email.',
+'Privacy options legend'	=>	'Set your privacy options',
+'Confirm pass'				=>	'Confirm password',
+
+'Username too short'		=>	'Usernames must be at least 2 characters long. Please choose another (longer) username.',
+'Username too long'			=>	'Usernames must not be more than 25 characters long. Please choose another (shorter) username.',
+'Username guest'			=>	'The username guest is reserved. Please choose another username.',
+'Username IP'				=>	'Usernames may not be in the form of an IP address. Please choose another username.',
+'Username reserved chars'	=>	'Usernames may not contain all the characters \', " and [ or ] at once. Please choose another username.',
+'Username BBCode'			=>	'Usernames may not contain any of the text formatting tags (BBCode) that the forum uses. Please choose another username.',
+'Banned username'			=>	'The username you entered is banned in this forum. Please choose another username.',
+'Pass too short'			=>	'Passwords must be at least 4 characters long. Please choose another (longer) password.',
+'Pass not match'			=>	'Passwords do not match.',
+'Banned email'				=>	'The email address you entered is banned in this forum. Please choose another email address.',
+'Dupe email'				=>	'Someone else is already registered with that email address. Please choose another email address.',
+'Sig too long'				=>	'Signatures cannot be longer than %1$s characters. Please reduce your signature by %2$s characters.',
+'Sig too many lines'		=>	'Signatures cannot have more than %s lines.',
+'Bad ICQ'					=>	'You entered an invalid ICQ UIN. Please go back and correct.',
+
+'UTC-12:00'					=>	'(UTC-12:00) International Date Line West',
+'UTC-11:00'					=>	'(UTC-11:00) Niue, Samoa',
+'UTC-10:00'					=>	'(UTC-10:00) Hawaii-Aleutian, Cook Island',
+'UTC-09:30'					=>	'(UTC-09:30) Marquesas Islands',
+'UTC-09:00'					=>	'(UTC-09:00) Alaska, Gambier Island',
+'UTC-08:30'					=>	'(UTC-08:30) Pitcairn Islands',
+'UTC-08:00'					=>	'(UTC-08:00) Pacific',
+'UTC-07:00'					=>	'(UTC-07:00) Mountain',
+'UTC-06:00'					=>	'(UTC-06:00) Central',
+'UTC-05:00'					=>	'(UTC-05:00) Eastern',
+'UTC-04:00'					=>	'(UTC-04:00) Atlantic',
+'UTC-03:30'					=>	'(UTC-03:30) Newfoundland',
+'UTC-03:00'					=>	'(UTC-03:00) Amazon, Central Greenland',
+'UTC-02:00'					=>	'(UTC-02:00) Mid-Atlantic',
+'UTC-01:00'					=>	'(UTC-01:00) Azores, Cape Verde, Eastern Greenland',
+'UTC'						=>	'(UTC) Western European, Greenwich',
+'UTC+01:00'					=>	'(UTC+01:00) Central European, West African',
+'UTC+02:00'					=>	'(UTC+02:00) Eastern European, Central African',
+'UTC+03:00'					=>	'(UTC+03:00) Eastern African',
+'UTC+03:30'					=>	'(UTC+03:30) Iran',
+'UTC+04:00'					=>	'(UTC+04:00) Moscow, Gulf, Samara',
+'UTC+04:30'					=>	'(UTC+04:30) Afghanistan',
+'UTC+05:00'					=>	'(UTC+05:00) Pakistan',
+'UTC+05:30'					=>	'(UTC+05:30) India, Sri Lanka',
+'UTC+05:45'					=>	'(UTC+05:45) Nepal',
+'UTC+06:00'					=>	'(UTC+06:00) Bangladesh, Bhutan, Yekaterinburg',
+'UTC+06:30'					=>	'(UTC+06:30) Cocos Islands, Myanmar',
+'UTC+07:00'					=>	'(UTC+07:00) Indochina, Novosibirsk',
+'UTC+08:00'					=>	'(UTC+08:00) Greater China, Australian Western, Krasnoyarsk',
+'UTC+08:45'					=>	'(UTC+08:45) Southeastern Western Australia',
+'UTC+09:00'					=>	'(UTC+09:00) Japan, Korea, Chita, Irkutsk',
+'UTC+09:30'					=>	'(UTC+09:30) Australian Central',
+'UTC+10:00'					=>	'(UTC+10:00) Australian Eastern',
+'UTC+10:30'					=>	'(UTC+10:30) Lord Howe',
+'UTC+11:00'					=>	'(UTC+11:00) Solomon Island, Vladivostok',
+'UTC+11:30'					=>	'(UTC+11:30) Norfolk Island',
+'UTC+12:00'					=>	'(UTC+12:00) New Zealand, Fiji, Magadan',
+'UTC+12:45'					=>	'(UTC+12:45) Chatham Islands',
+'UTC+13:00'					=>	'(UTC+13:00) Tonga, Phoenix Islands, Kamchatka',
+'UTC+14:00'					=>	'(UTC+14:00) Line Islands'
+
+);
diff --git a/lang/English/profile.php b/lang/English/profile.php
new file mode 100644
index 0000000..5687e35
--- /dev/null
+++ b/lang/English/profile.php
@@ -0,0 +1,141 @@
+<?php
+
+// Language definitions used in profile.php
+$lang_profile = array(
+
+// Navigation and sections
+'Profile menu'					=>	'Profile menu',
+'Section essentials'			=>	'Essentials',
+'Section personal'				=>	'Personal',
+'Section messaging'				=>	'Messaging',
+'Section personality'			=>	'Personality',
+'Section display'				=>	'Display',
+'Section privacy'				=>	'Privacy',
+'Section admin'					=>	'Administration',
+
+// Miscellaneous
+'Username and pass legend'		=>	'Enter your username and password',
+'Personal details legend'		=>	'Enter your personal details',
+'Contact details legend'		=>	'Enter your messaging details',
+'User activity'					=>	'User activity',
+'Paginate info'					=>	'Enter the number of topics and posts you wish to view on each page.',
+
+// Password stuff
+'Pass key bad'					=>	'The specified password activation key was incorrect or has expired. Please re-request a new password. If that fails, contact the forum administrator at',
+'Pass updated'					=>	'Your password has been updated. You can now login with your new password.',
+'Pass updated redirect'			=>	'Password updated. Redirecting …',
+'Wrong pass'					=>	'Wrong old password.',
+'Change pass'					=>	'Change password',
+'Change pass legend'			=>	'Enter and confirm your new password',
+'Old pass'						=>	'Old password',
+'New pass'						=>	'New password',
+'Confirm new pass'				=>	'Confirm new password',
+'Pass info'						=>	'Passwords must be at least 4 characters long. Passwords are case sensitive.',
+
+// Email stuff
+'Email key bad'					=>	'The specified email activation key was incorrect or has expired. Please re-request change of email address. If that fails, contact the forum administrator at',
+'Email updated'					=>	'Your email address has been updated.',
+'Activate email sent'			=>	'An email has been sent to the specified address with instructions on how to activate the new email address. If it doesn\'t arrive you can contact the forum administrator at',
+'Email legend'					=>	'Enter your new email address',
+'Email instructions'			=>	'An email will be sent to your new address with an activation link. You must click the link in the email you receive to activate the new address.',
+'Change email'					=>	'Change email address',
+'New email'						=>	'New email',
+
+// Avatar upload stuff
+'Avatars disabled'				=>	'The administrator has disabled avatar support.',
+'Too large ini'					=>	'The selected file was too large to upload. The server didn\'t allow the upload.',
+'Partial upload'				=>	'The selected file was only partially uploaded. Please try again.',
+'No tmp directory'				=>	'PHP was unable to save the uploaded file to a temporary location.',
+'No file'						=>	'You did not select a file for upload.',
+'Bad type'						=>	'The file you tried to upload is not of an allowed type. Allowed types are gif, jpeg and png.',
+'Too wide or high'				=>	'The file you tried to upload is wider and/or higher than the maximum allowed',
+'Too large'						=>	'The file you tried to upload is larger than the maximum allowed',
+'pixels'						=>	'pixels',
+'bytes'							=>	'bytes',
+'Move failed'					=>	'The server was unable to save the uploaded file. Please contact the forum administrator at',
+'Unknown failure'				=>	'An unknown error occurred. Please try again.',
+'Avatar upload redirect'		=>	'Avatar uploaded. Redirecting …',
+'Avatar deleted redirect'		=>	'Avatar deleted. Redirecting …',
+'Avatar desc'					=>	'An avatar is a small image that will be displayed under your username in your posts. It must not be any bigger than',
+'Upload avatar'					=>	'Upload avatar',
+'Upload avatar legend'			=>	'Enter an avatar file to upload',
+'Delete avatar'					=>	'Delete avatar', // only for admins
+'File'							=>	'File',
+'Upload'						=>	'Upload', // submit button
+
+// Form validation stuff
+'Forbidden title'				=>	'The title you entered contains a forbidden word. You must choose a different title.',
+'Profile redirect'				=>	'Profile updated. Redirecting …',
+
+// Profile display stuff
+'Users profile'					=>	'%s\'s profile',
+'Username info'					=>	'Username: %s',
+'Email info'					=>	'Email: %s',
+'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',
+'Realname'						=>	'Real name',
+'Location'						=>	'Location',
+'Website'						=>	'Website',
+'Invalid website URL'					=>	'The website URL you entered is invalid.',
+'Jabber'						=>	'Jabber',
+'ICQ'							=>	'ICQ',
+'MSN'							=>	'MSN Messenger',
+'AOL IM'						=>	'AOL IM',
+'Yahoo'							=>	'Yahoo! Messenger',
+'Avatar'						=>	'Avatar',
+'Signature'						=>	'Signature',
+'Sig max size'					=>	'Max length: %s characters / Max lines: %s',
+'Avatar legend'					=>	'Set your avatar display options',
+'Avatar info'					=>	'An avatar is a small image that will be displayed with all your posts. You can upload an avatar by clicking the link below.',
+'Change avatar'					=>	'Change avatar',
+'Signature legend'				=>	'Compose your signature',
+'Signature info'				=>	'A signature is a small piece of text that is attached to your posts. In it, you can enter just about anything you like. Perhaps you would like to enter your favourite quote or your star sign. It\'s up to you! In your signature you can use BBCode if it is allowed in this particular forum. You can see the features that are allowed/enabled listed below whenever you edit your signature.',
+'Sig preview'					=>	'Current signature preview:',
+'No sig'						=>	'No signature currently stored in profile.',
+'Signature quote/code/list/h'	=>	'The quote, code, list, and heading BBCodes are not allowed in signatures.',
+'Topics per page'				=>	'Topics',
+'Posts per page'				=>	'Posts',
+'Leave blank'					=>	'Leave blank to use forum default.',
+'Subscription legend'			=>	'Set your subscription options',
+'Notify full'					=>	'Include a plain text version of new posts in subscription notification emails.',
+'Auto notify full'				=>	'Automatically subscribe to every topic you post in.',
+'Show smilies'					=>	'Show smilies as graphic icons.',
+'Show images'					=>	'Show images in posts.',
+'Show images sigs'				=>	'Show images in user signatures.',
+'Show avatars'					=>	'Show user avatars in posts.',
+'Show sigs'						=>	'Show user signatures.',
+'Style legend'					=>	'Select your preferred style',
+'Styles'						=>	'Styles',
+'Admin note'					=>	'Admin note',
+'Pagination legend'				=>	'Enter your pagination options',
+'Post display legend'			=>	'Set your options for viewing posts',
+'Post display info'				=>	'If you are on a slow connection, disabling these options, particularly showing images in posts and signatures, will make pages load faster.',
+'Instructions'					=>	'When you update your profile, you will be redirected back to this page.',
+
+// Administration stuff
+'Group membership legend'		=>	'Choose user group',
+'Save'							=>	'Save',
+'Set mods legend'				=>	'Set moderator access',
+'Moderator in info'				=>	'Choose which forums this user should be allowed to moderate. Note: This only applies to moderators. Administrators always have full permissions in all forums.',
+'Update forums'					=>	'Update forums',
+'Delete ban legend'				=>	'Delete (administrators only) or ban user',
+'Delete user'					=>	'Delete user',
+'Ban user'						=>	'Ban user',
+'Confirm delete legend'			=>	'Important: read before deleting user',
+'Confirm delete user'			=>	'Confirm delete user',
+'Confirmation info'				=>	'Please confirm that you want to delete the user', // the username will be appended to this string
+'Delete warning'				=>	'Warning! Deleted users and/or posts cannot be restored. If you choose not to delete the posts made by this user, the posts can only be deleted manually at a later time.',
+'Delete posts'					=>	'Delete any posts and topics this user has made.',
+'Delete'						=>	'Delete', // submit button (confirm user delete)
+'User delete redirect'			=>	'User deleted. Redirecting …',
+'Group membership redirect'		=>	'Group membership saved. Redirecting …',
+'Update forums redirect'		=>	'Forum moderator rights updated. Redirecting …',
+'Ban redirect'					=>	'Redirecting …',
+'No delete admin message'		=>	'Administrators cannot be deleted. In order to delete this user, you must first move him/her to a different user group.',
+
+);
diff --git a/lang/English/register.php b/lang/English/register.php
new file mode 100644
index 0000000..c3e24df
--- /dev/null
+++ b/lang/English/register.php
@@ -0,0 +1,37 @@
+<?php
+
+// Language definitions used in register.php
+$lang_register = array(
+
+// Miscellaneous
+'No new regs'				=>	'This forum is not accepting new registrations.',
+'Reg cancel redirect'		=>	'Registration cancelled. Redirecting …',
+'Forum rules'				=>	'Forum rules',
+'Rules legend'				=>	'You must agree to the following in order to register',
+'Registration flood'		=>	'A new user was registered with the same IP address as you within the last hour. To prevent registration flooding, at least an hour has to pass between registrations from the same IP. Sorry for the inconvenience.',
+'Agree'						=>	'Agree',
+'Cancel'					=>	'Cancel',
+'Register'					=>	'Register',
+
+// Form validation stuff (some of these are also used in post.php)
+'Registration errors'		=>	'Registration errors',
+'Registration errors info'	=>	'The following errors need to be corrected before you can register:',
+'Username censor'			=>	'The username you entered contains one or more censored words. Please choose a different username.',
+'Username dupe 1'			=>	'Someone is already registered with the username',
+'Username dupe 2'			=>	'The username you entered is too similar. The username must differ from that by at least one alphanumerical character (a-z or 0-9). Please choose a different username.',
+'Email not match'			=>	'Email addresses do not match.',
+
+// Registration email stuff
+'Reg email'					=>	'Thank you for registering. Your password has been sent to the specified address. If it doesn\'t arrive you can contact the forum administrator at',
+'Reg complete'				=>	'Registration complete. Logging in and redirecting …',
+
+// Register info
+'Desc 1'					=>	'Registration will grant you access to a number of features and capabilities otherwise unavailable. These functions include the ability to edit and delete posts, design your own signature that accompanies your posts and much more. If you have any questions regarding this forum you should ask an administrator.',
+'Desc 2'					=>	'Below is a form you must fill out in order to register. Once you are registered you should visit your profile and review the different settings you can change. The fields below only make up a small part of all the settings you can alter in your profile.',
+'Username legend'			=>	'Please enter a username between 2 and 25 characters long',
+'Pass legend'				=>	'Please enter and confirm your chosen password',
+'Pass info'					=>	'Passwords must be at least 4 characters long. Passwords are case sensitive.',
+'Email info'				=>	'You must enter a valid email address as your randomly generated password will be sent to that address.',
+'Confirm email'				=>	'Confirm email address',
+
+);
diff --git a/lang/English/search.php b/lang/English/search.php
new file mode 100644
index 0000000..b0f7fba
--- /dev/null
+++ b/lang/English/search.php
@@ -0,0 +1,65 @@
+<?php
+
+// Language definitions used in search.php
+$lang_search = array(
+
+// The search form
+'User search'						=>	'User search',
+'No search permission'				=>	'You do not have permission to use the search feature.',
+'Search flood'						=>	'At least %s seconds have to pass between searches. Please wait a while and try searching again.',
+'Search'							=>	'Search',
+'Search criteria legend'			=>	'Enter your search criteria',
+'Search info'						=>	'To search by keyword, enter a term or terms to search for. Separate terms with spaces. Use AND, OR and NOT to refine your search. To search by author enter the username of the author whose posts you wish to search for. Use wildcard character * for partial matches.',
+'Keyword search'					=>	'Keyword search',
+'Author search'						=>	'Author search',
+'Search in legend'					=>	'Select where to search',
+'Search in info'					=>	'Choose in which forum you would like to search and if you want to search in topic subjects, message text or both.',
+'Search multiple forums info'		=>	'If no forums are selected, all forums will be searched.',
+'Forum search'						=>	'Forum',
+'All forums'						=>	'All forums',
+'Search in'							=>	'Search in',
+'Message and subject'				=>	'Message text and topic subject',
+'Message only'						=>	'Message text only',
+'Topic only'						=>	'Topic subject only',
+'Sort by'							=>	'Sort by',
+'Sort order'						=>	'Sort order',
+'Search results legend'				=>	'Select how to view search results',
+'Search results info'				=>	'You can choose how you wish to sort and show your results.',
+'Sort by post time'					=>	'Post time',
+'Sort by author'					=>	'Author',
+'Sort by subject'					=>	'Subject',
+'Sort by forum'						=>	'Forum',
+'Ascending'							=>	'Ascending',
+'Descending'						=>	'Descending',
+'Show as'							=>	'Show results as',
+'Show as topics'					=>	'Topics',
+'Show as posts'						=>	'Posts',
+
+// Results
+'Search'							=>	'Search',
+'Search results'					=>	'Search results',
+'Quick search show_new'				=>	'New',
+'Quick search show_recent'			=>	'Active',
+'Quick search show_unanswered'		=>	'Unanswered',
+'Quick search show_replies'			=>	'Posted',
+'Quick search show_user_topics'		=>	'Topics by %s',
+'Quick search show_user_posts'		=>	'Posts by %s',
+'Quick search show_subscriptions'	=>	'Subscribed by %s',
+'By keywords show as topics'		=>	'Topics with posts containing \'%s\'',
+'By keywords show as posts'			=>	'Posts containing \'%s\'',
+'By user show as topics'			=>	'Topics with posts by %s',
+'By user show as posts'				=>	'Posts by %s',
+'By both show as topics'			=>	'Topics with posts containing \'%s\', by %s',
+'By both show as posts'				=>	'Posts containing \'%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.',
+'No user topics'					=>	'There are no topics by this user in this forum.',
+'No subscriptions'					=>	'This user is currently not subscribed to any topics.',
+'No new posts'						=>	'There are no topics with new posts since your last visit.',
+'No recent posts'					=>	'No new posts have been made within the last 24 hours.',
+'No unanswered'						=>	'There are no unanswered posts in this forum.',
+'Go to post'						=>	'Go to post',
+'Go to topic'						=>	'Go to topic'
+
+);
diff --git a/lang/English/stopwords.txt b/lang/English/stopwords.txt
new file mode 100644
index 0000000..907a260
--- /dev/null
+++ b/lang/English/stopwords.txt
@@ -0,0 +1,175 @@
+about
+after
+ago
+all
+almost
+along
+also
+any
+anybody
+anywhere
+are
+arent
+aren't
+around
+ask
+been
+before
+being
+between
+but
+came
+can
+cant
+can't
+come
+could
+couldnt
+couldn't
+did
+didnt
+didn't
+does
+doesnt
+doesn't
+dont
+don't
+each
+either
+else
+even
+every
+everybody
+everyone
+find
+for
+from
+get
+going
+gone
+got
+had
+has
+have
+havent
+haven't
+having
+her
+here
+hers
+him
+his
+how
+ill
+i'll
+i'm
+into
+isnt
+isn't
+itll
+it'll
+its
+it's
+ive
+i've
+just
+know
+less
+like
+make
+many
+may
+more
+most
+much
+must
+near
+never
+none
+nothing
+now
+off
+often
+once
+one
+only
+other
+our
+ours
+our's
+out
+over
+please
+rather
+really
+said
+see
+she
+should
+small
+some
+something
+sometime
+somewhere
+take
+than
+thank
+thanks
+that
+thats
+that's
+the
+their
+theirs
+them
+then
+there
+these
+they
+thing
+think
+this
+those
+though
+through
+thus
+too
+true
+two
+under
+until
+upon
+use
+very
+want
+was
+way
+well
+were
+what
+when
+where
+which
+who
+whom
+whose
+why
+will
+with
+within
+without
+would
+yes
+yet
+you
+your
+youre
+you're
+yours
+http
+https
+ftp
+www
+com
+net
+org
diff --git a/lang/English/topic.php b/lang/English/topic.php
new file mode 100644
index 0000000..ff70bf4
--- /dev/null
+++ b/lang/English/topic.php
@@ -0,0 +1,32 @@
+<?php
+
+// Language definitions used in viewtopic.php
+$lang_topic = array(
+
+'Post reply'		=>	'Post reply',
+'Topic closed'		=>	'Topic closed',
+'From'				=>	'From:', // User location
+'IP address logged'	=>	'IP address logged',
+'Note'				=>	'Note:', // Admin note
+'Posts'				=>	'Posts:',
+'Registered'		=>	'Registered:',
+'Replies'			=>	'Replies:',
+'Website'			=>	'Website',
+'Guest'				=>	'Guest',
+'Online'			=>	'Online',
+'Offline'			=>	'Offline',
+'Last edit'			=>	'Last edited by',
+'Report'			=>	'Report',
+'Delete'			=>	'Delete',
+'Edit'				=>	'Edit',
+'Quote'				=>	'Quote',
+'Is subscribed'		=>	'You are currently subscribed to this topic',
+'Unsubscribe'		=>	'Unsubscribe',
+'Subscribe'			=>	'Subscribe to this topic',
+'Quick post'		=>	'Quick post',
+'Mod controls'		=>	'Moderator controls',
+'New icon'			=>	'New post',
+'Re'				=>	'Re:',
+'Preview'			=>	'Preview'
+
+);
diff --git a/lang/English/update.php b/lang/English/update.php
new file mode 100644
index 0000000..437e7fe
--- /dev/null
+++ b/lang/English/update.php
@@ -0,0 +1,76 @@
+<?php
+
+// Language definitions used in db_update.php
+
+$lang_update = array(
+
+'Update'						=>	'Update FluxBB',
+'Update message'				=>	'Your FluxBB database is out-of-date and must be upgraded in order to continue. If you are the board administrator, please follow the instructions below to complete the upgrade.',
+'Note'							=>	'Note:',
+'Members message'				=>	'This process is for board administators only. If you are a member there is nothing to worry about - the forums will be back shortly!',
+'Administrator only'			=>	'This step is for the board administrator only!',
+'Database password info'		=>	'To perform the database update please enter the database password with which FluxBB was installed. If you cannot remember, this is stored in your \'config.php\' file.',
+'Database password note'		=>	'If you are running SQLite (and hence have no database password) please use the database file name instead. This must exactly match the database file name given in your configuration file.',
+'Database password'				=>	'Database password',
+'Maintenance'					=>	'Maintenance',
+'Maintenance message info'		=>	'The message that will be displayed to users during the updating process. This text will not be parsed like regular posts and thus may contain HTML.',
+'Maintenance message'		    =>	'Maintenance message',
+'Next'							=>	'Next',
+
+'You are running error'			=>	'You are running %1$s version %2$s. FluxBB %3$s requires at least %1$s %4$s to run properly. You must upgrade your %1$s installation before you can continue.',
+'Version mismatch error'		=>	'Version mismatch. The database \'%s\' doesn\'t seem to be running a FluxBB database schema supported by this update script.',
+'Invalid file error'			=>	'Invalid database file name. When using SQLite the database file name must be entered exactly as it appears in your \'%s\'',
+'Invalid password error'		=>	'Invalid database password. To upgrade FluxBB you must enter your database password exactly as it appears in your \'%s\'',
+'No password error'				=>	'No database password provided',
+'Script runs error'				=>	'It appears the update script is already being ran by someone else. If this is not the case, please manually delete the file \'%s\' and try again',
+'No update error'				=>	'Your forum is already as up-to-date as this script can make it',
+
+'Intro 1'						=>	'This script will update your forum database. The update procedure might take anything from a second to hours depending on the speed of the server and the size of the forum database. Don\'t forget to make a backup of the database before continuing.',
+'Intro 2'						=>	'Did you read the update instructions in the documentation? If not, start there.',
+'No charset conversion'			=>	'<strong>IMPORTANT!</strong> FluxBB has detected that this PHP environment does not have support for the encoding mechanisms required to do UTF-8 conversion from character sets other than ISO-8859-1. What this means is that if the current character set is not ISO-8859-1, FluxBB won\'t be able to convert your forum database to UTF-8 and you will have to do it manually. Instructions for doing manual charset conversion can be found in the update instructions.',
+'Enable conversion'				=>	'<strong>Enable conversion:</strong> When enabled this update script will, after it has made the required structural changes to the database, convert all text in the database from the current character set to UTF-8. This conversion is required if you\'re upgrading from version 1.2.',
+'Current character set'			=>	'<strong>Current character set:</strong> If the primary language in your forum is English, you can leave this at the default value. However, if your forum is non-English, you should enter the character set of the primary language pack used in the forum. <em>Getting this wrong can corrupt your database so don\'t just guess!</em> Note: This is required even if the old database is UTF-8.',
+'Charset conversion'			=>	'Charset conversion',
+'Enable conversion label'		=>	'<strong>Enable conversion</strong> (perform database charset conversion).',
+'Current character set label'	=>	'Current character set',
+'Current character set info'	=>	'Accept default for English forums otherwise the character set of the primary language pack.',
+'Start update'					=>	'Start update',
+'Error converting users'		=>	'Error converting users',
+'Error info 1'					=>	'There was an error converting some users. This can occur when converting from FluxBB v1.2 if multiple users have registered with very similar usernames, for example "bob" and "böb".',
+'Error info 2'					=>	'Below is a list of users who failed to convert. Please choose a new username for each user. Users who are renamed will automatically be sent an email alerting them of the change.',
+'New username'					=>	'New username',
+'Required'						=>	'(Required)',
+'Correct errors'				=>	'The following errors need to be corrected:',
+'Rename users'					=>	'Rename users',
+'Successfully updated'			=>	'Your forum database was successfully updated. You may now %s.',
+'go to index'					=>	'go to the forum index',
+
+'Unable to lock error'			=>	'Unable to write update lock. Please make sure PHP has write access to the directory \'%s\' and no-one else is currently running the update script.',
+
+'Converting'					=>	'Converting %s …',
+'Converting item'				=>	'Converting %1$s %2$s …',
+'Preparsing item'				=>	'Preparsing %1$s %2$s …',
+'Rebuilding index item'			=>	'Rebuilding index for %1$s %2$s',
+
+'ban'							=>	'ban',
+'categories'					=>	'categories',
+'censor words'					=>	'censor words',
+'configuration'					=>	'configuration',
+'forums'						=>	'forums',
+'groups'						=>	'groups',
+'post'							=>	'post',
+'ranks'							=>	'ranks',
+'report'						=>	'report',
+'topic'							=>	'topic',
+'user'							=>	'user',
+'signature'						=>	'signature',
+
+'Username too short error'		=>	'Usernames must be at least 2 characters long. Please choose another (longer) username.',
+'Username too long error'		=>	'Usernames must not be more than 25 characters long. Please choose another (shorter) username.',
+'Username Guest reserved error'	=>	'The username guest is reserved. Please choose another username.',
+'Username IP format error'		=>	'Usernames may not be in the form of an IP address. Please choose another username.',
+'Username bad characters error'	=>	'Usernames may not contain all the characters \', " and [ or ] at once. Please choose another username.',
+'Username BBCode error'			=>	'Usernames may not contain any of the text formatting tags (BBCode) that the forum uses. Please choose another username.',
+'Username duplicate error'		=>	'Someone is already registered with the username %s. The username you entered is too similar. The username must differ from that by at least one alphanumerical character (a-z or 0-9). Please choose a different username.',
+
+);
diff --git a/lang/English/userlist.php b/lang/English/userlist.php
new file mode 100644
index 0000000..0853d25
--- /dev/null
+++ b/lang/English/userlist.php
@@ -0,0 +1,13 @@
+<?php
+
+// Language definitions used in userlist.php
+$lang_ul = array(
+
+'User find legend'	=>	'Find and sort users',
+'User search info'	=>	'Enter a username to search for and/or a user group to filter by. The username field can be left blank. Use the wildcard character * for partial matches.',
+'User sort info'	=>	'Sort users by name, date registered or number of posts and in ascending/descending order.',
+'User group'		=>	'User group',
+'No of posts'		=>	'Number of posts',
+'All users'			=>	'All'
+
+);
diff --git a/lang/index.html b/lang/index.html
new file mode 100644
index 0000000..89337b2
--- /dev/null
+++ b/lang/index.html
@@ -0,0 +1 @@
+<html><head><title>.</title></head><body>.</body></html>
diff --git a/login.php b/login.php
new file mode 100644
index 0000000..0497ad6
--- /dev/null
+++ b/login.php
@@ -0,0 +1,296 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+if (isset($_GET['action']))
+	define('PUN_QUIET_VISIT', 1);
+
+define('PUN_ROOT', dirname(__FILE__).'/');
+require PUN_ROOT.'include/common.php';
+
+
+// Load the login.php language file
+require PUN_ROOT.'lang/'.$pun_user['language'].'/login.php';
+
+$action = isset($_GET['action']) ? $_GET['action'] : null;
+
+if (isset($_POST['form_sent']) && $action == 'in')
+{
+	$form_username = pun_trim($_POST['req_username']);
+	$form_password = pun_trim($_POST['req_password']);
+	$save_pass = isset($_POST['save_pass']);
+
+	$username_sql = ($db_type == 'mysql' || $db_type == 'mysqli' || $db_type == 'mysql_innodb' || $db_type == 'mysqli_innodb') ? 'username=\''.$db->escape($form_username).'\'' : 'LOWER(username)=LOWER(\''.$db->escape($form_username).'\')';
+
+	$result = $db->query('SELECT * FROM '.$db->prefix.'users WHERE '.$username_sql) or error('Unable to fetch user info', __FILE__, __LINE__, $db->error());
+	$cur_user = $db->fetch_assoc($result);
+
+	$authorized = false;
+
+	if (!empty($cur_user['password']))
+	{
+		$form_password_hash = pun_hash($form_password); // Will result in a SHA-1 hash
+
+		// If there is a salt in the database we have upgraded from 1.3-legacy though havent yet logged in
+		if (!empty($cur_user['salt']))
+		{
+			if (sha1($cur_user['salt'].sha1($form_password)) == $cur_user['password']) // 1.3 used sha1(salt.sha1(pass))
+			{
+				$authorized = true;
+
+				$db->query('UPDATE '.$db->prefix.'users SET password=\''.$form_password_hash.'\', salt=NULL WHERE id='.$cur_user['id']) or error('Unable to update user password', __FILE__, __LINE__, $db->error());
+			}
+		}
+		// If the length isn't 40 then the password isn't using sha1, so it must be md5 from 1.2
+		else if (strlen($cur_user['password']) != 40)
+		{
+			if (md5($form_password) == $cur_user['password'])
+			{
+				$authorized = true;
+
+				$db->query('UPDATE '.$db->prefix.'users SET password=\''.$form_password_hash.'\' WHERE id='.$cur_user['id']) or error('Unable to update user password', __FILE__, __LINE__, $db->error());
+			}
+		}
+		// Otherwise we should have a normal sha1 password
+		else
+			$authorized = ($cur_user['password'] == $form_password_hash);
+	}
+
+	if (!$authorized)
+		message($lang_login['Wrong user/pass'].' <a href="login.php?action=forget">'.$lang_login['Forgotten pass'].'</a>');
+
+	// 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());
+
+	$expire = ($save_pass == '1') ? time() + 1209600 : time() + $pun_config['o_timeout_visit'];
+	pun_setcookie($cur_user['id'], $form_password_hash, $expire);
+
+	// Reset tracked topics
+	set_tracked_topics(null);
+
+	redirect(htmlspecialchars($_POST['redirect_url']), $lang_login['Login redirect']);
+}
+
+
+else if ($action == 'out')
+{
+	if ($pun_user['is_guest'] || !isset($_GET['id']) || $_GET['id'] != $pun_user['id'] || !isset($_GET['csrf_token']) || $_GET['csrf_token'] != pun_hash($pun_user['id'].pun_hash(get_remote_address())))
+	{
+		header('Location: index.php');
+		exit;
+	}
+
+	// Remove user from "users online" list
+	$db->query('DELETE FROM '.$db->prefix.'online WHERE user_id='.$pun_user['id']) or error('Unable to delete from online list', __FILE__, __LINE__, $db->error());
+
+	// Update last_visit (make sure there's something to update it with)
+	if (isset($pun_user['logged']))
+		$db->query('UPDATE '.$db->prefix.'users SET last_visit='.$pun_user['logged'].' WHERE id='.$pun_user['id']) or error('Unable to update user visit data', __FILE__, __LINE__, $db->error());
+
+	pun_setcookie(1, pun_hash(uniqid(rand(), true)), time() + 31536000);
+
+	redirect('index.php', $lang_login['Logout redirect']);
+}
+
+
+else if ($action == 'forget' || $action == 'forget_2')
+{
+	if (!$pun_user['is_guest'])
+		header('Location: index.php');
+
+	if (isset($_POST['form_sent']))
+	{
+		// Start with a clean slate
+		$errors = array();
+
+		require PUN_ROOT.'include/email.php';
+
+		// Validate the email address
+		$email = strtolower(pun_trim($_POST['req_email']));
+		if (!is_valid_email($email))
+			$errors[] = $lang_common['Invalid email'];
+
+		// Did everything go according to plan?
+		if (empty($errors))
+		{
+			$result = $db->query('SELECT id, username, last_email_sent FROM '.$db->prefix.'users WHERE email=\''.$db->escape($email).'\'') or error('Unable to fetch user info', __FILE__, __LINE__, $db->error());
+
+			if ($db->num_rows($result))
+			{
+				// Load the "activate password" template
+				$mail_tpl = trim(file_get_contents(PUN_ROOT.'lang/'.$pun_user['language'].'/mail_templates/activate_password.tpl'));
+
+				// The first row contains the subject
+				$first_crlf = strpos($mail_tpl, "\n");
+				$mail_subject = trim(substr($mail_tpl, 8, $first_crlf-8));
+				$mail_message = trim(substr($mail_tpl, $first_crlf));
+
+				// Do the generic replacements first (they apply to all emails sent out here)
+				$mail_message = str_replace('<base_url>', get_base_url().'/', $mail_message);
+				$mail_message = str_replace('<board_mailer>', $pun_config['o_board_title'], $mail_message);
+
+				// Loop through users we found
+				while ($cur_hit = $db->fetch_assoc($result))
+				{
+					if ($cur_hit['last_email_sent'] != '' && (time() - $cur_hit['last_email_sent']) < 3600 && (time() - $cur_hit['last_email_sent']) >= 0)
+						message($lang_login['Email flood'], true);
+
+					// Generate a new password and a new password activation code
+					$new_password = random_pass(8);
+					$new_password_key = random_pass(8);
+
+					$db->query('UPDATE '.$db->prefix.'users SET activate_string=\''.pun_hash($new_password).'\', activate_key=\''.$new_password_key.'\', last_email_sent = '.time().' WHERE id='.$cur_hit['id']) or error('Unable to update activation data', __FILE__, __LINE__, $db->error());
+
+					// Do the user specific replacements to the template
+					$cur_mail_message = str_replace('<username>', $cur_hit['username'], $mail_message);
+					$cur_mail_message = str_replace('<activation_url>', get_base_url().'/profile.php?id='.$cur_hit['id'].'&action=change_pass&key='.$new_password_key, $cur_mail_message);
+					$cur_mail_message = str_replace('<new_password>', $new_password, $cur_mail_message);
+
+					pun_mail($email, $mail_subject, $cur_mail_message);
+				}
+
+				message($lang_login['Forget mail'].' <a href="mailto:'.$pun_config['o_admin_email'].'">'.$pun_config['o_admin_email'].'</a>.', true);
+			}
+			else
+				$errors[] = $lang_login['No email match'].' '.htmlspecialchars($email).'.';
+			}
+		}
+
+	$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_login['Request pass']);
+	$required_fields = array('req_email' => $lang_common['Email']);
+	$focus_element = array('request_pass', 'req_email');
+	define ('PUN_ACTIVE_PAGE', 'login');
+	require PUN_ROOT.'header.php';
+
+// If there are errors, we display them
+if (!empty($errors))
+{
+
+?>
+<div id="posterror" class="block">
+	<h2><span><?php echo $lang_login['New password errors'] ?></span></h2>
+	<div class="box">
+		<div class="inbox error-info">
+			<p><?php echo $lang_login['New passworderrors info'] ?></p>
+			<ul class="error-list">
+<?php
+
+	foreach ($errors as $cur_error)
+		echo "\t\t\t\t".'<li><strong>'.$cur_error.'</strong></li>'."\n";
+?>
+			</ul>
+		</div>
+	</div>
+</div>
+
+<?php
+
+}
+?>
+<div class="blockform">
+	<h2><span><?php echo $lang_login['Request pass'] ?></span></h2>
+	<div class="box">
+		<form id="request_pass" method="post" action="login.php?action=forget_2" onsubmit="this.request_pass.disabled=true;if(process_form(this)){return true;}else{this.request_pass.disabled=false;return false;}">
+			<div class="inform">
+				<fieldset>
+					<legend><?php echo $lang_login['Request pass legend'] ?></legend>
+					<div class="infldset">
+						<input type="hidden" name="form_sent" value="1" />
+						<label class="required"><strong><?php echo $lang_common['Email'] ?> <span><?php echo $lang_common['Required'] ?></span></strong><br /><input id="req_email" type="text" name="req_email" size="50" maxlength="80" /><br /></label>
+						<p><?php echo $lang_login['Request pass info'] ?></p>
+					</div>
+				</fieldset>
+			</div>
+			<p class="buttons"><input type="submit" name="request_pass" value="<?php echo $lang_common['Submit'] ?>" /><?php if (empty($errors)): ?> <a href="javascript:history.go(-1)"><?php echo $lang_common['Go back'] ?></a><?php endif; ?></p>
+		</form>
+	</div>
+</div>
+<?php
+
+	require PUN_ROOT.'footer.php';
+}
+
+
+if (!$pun_user['is_guest'])
+	header('Location: index.php');
+
+// Try to determine if the data in HTTP_REFERER is valid (if not, we redirect to index.php after login)
+if (!empty($_SERVER['HTTP_REFERER']))
+{
+	$referrer = parse_url($_SERVER['HTTP_REFERER']);
+	// Remove www subdomain if it exists
+	if (strpos($referrer['host'], 'www.') === 0)
+		$referrer['host'] = substr($referrer['host'], 4);
+
+	// Make sure the path component exists
+	if (!isset($referrer['path']))
+		$referrer['path'] = '';
+
+	$valid = parse_url(get_base_url());
+	// Remove www subdomain if it exists
+	if (strpos($valid['host'], 'www.') === 0)
+		$valid['host'] = substr($valid['host'], 4);
+
+	// Make sure the path component exists
+	if (!isset($valid['path']))
+		$valid['path'] = '';
+
+	if ($referrer['host'] == $valid['host'] && preg_match('%^'.preg_quote($valid['path'], '%').'/(.*?)\.php%i', $referrer['path']))
+		$redirect_url = $_SERVER['HTTP_REFERER'];
+}
+
+if (!isset($redirect_url))
+	$redirect_url = 'index.php';
+
+$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_common['Login']);
+$required_fields = array('req_username' => $lang_common['Username'], 'req_password' => $lang_common['Password']);
+$focus_element = array('login', 'req_username');
+define('PUN_ACTIVE_PAGE', 'login');
+require PUN_ROOT.'header.php';
+
+?>
+<div class="blockform">
+	<h2><span><?php echo $lang_common['Login'] ?></span></h2>
+	<div class="box">
+		<form id="login" method="post" action="login.php?action=in" onsubmit="return process_form(this)">
+			<div class="inform">
+				<fieldset>
+					<legend><?php echo $lang_login['Login legend'] ?></legend>
+					<div class="infldset">
+						<input type="hidden" name="form_sent" value="1" />
+						<input type="hidden" name="redirect_url" value="<?php echo pun_htmlspecialchars($redirect_url) ?>" />
+						<label class="conl required"><strong><?php echo $lang_common['Username'] ?> <span><?php echo $lang_common['Required'] ?></span></strong><br /><input type="text" name="req_username" size="25" maxlength="25" tabindex="1" /><br /></label>
+						<label class="conl required"><strong><?php echo $lang_common['Password'] ?> <span><?php echo $lang_common['Required'] ?></span></strong><br /><input type="password" name="req_password" size="25" tabindex="2" /><br /></label>
+
+						<div class="rbox clearb">
+							<label><input type="checkbox" name="save_pass" value="1" tabindex="3" /><?php echo $lang_login['Remember me'] ?><br /></label>
+						</div>
+
+						<p class="clearb"><?php echo $lang_login['Login info'] ?></p>
+						<p class="actions"><span><a href="register.php" tabindex="5"><?php echo $lang_login['Not registered'] ?></a></span> <span><a href="login.php?action=forget" tabindex="6"><?php echo $lang_login['Forgotten pass'] ?></a></span></p>
+					</div>
+				</fieldset>
+			</div>
+			<p class="buttons"><input type="submit" name="login" value="<?php echo $lang_common['Login'] ?>" tabindex="4" /></p>
+		</form>
+	</div>
+</div>
+<?php
+
+require PUN_ROOT.'footer.php';
diff --git a/misc.php b/misc.php
new file mode 100644
index 0000000..c79a8d8
--- /dev/null
+++ b/misc.php
@@ -0,0 +1,421 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+if (isset($_GET['action']))
+	define('PUN_QUIET_VISIT', 1);
+
+define('PUN_ROOT', dirname(__FILE__).'/');
+require PUN_ROOT.'include/common.php';
+
+
+// Load the misc.php language file
+require PUN_ROOT.'lang/'.$pun_user['language'].'/misc.php';
+
+$action = isset($_GET['action']) ? $_GET['action'] : null;
+
+
+if ($action == 'rules')
+{
+	if ($pun_config['o_rules'] == '0' || ($pun_user['is_guest'] && $pun_user['g_read_board'] == '0' && $pun_config['o_regs_allow'] == '0'))
+		message($lang_common['Bad request']);
+
+	// Load the register.php language file
+	require PUN_ROOT.'lang/'.$pun_user['language'].'/register.php';
+
+	$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_register['Forum rules']);
+	define('PUN_ACTIVE_PAGE', 'rules');
+	require PUN_ROOT.'header.php';
+
+?>
+<div id="rules" class="block">
+	<div class="hd"><h2><span><?php echo $lang_register['Forum rules'] ?></span></h2></div>
+	<div class="box">
+		<div id="rules-block" class="inbox">
+			<div class="usercontent"><?php echo $pun_config['o_rules_message'] ?></div>
+		</div>
+	</div>
+</div>
+<?php
+
+	require PUN_ROOT.'footer.php';
+}
+
+
+else if ($action == 'markread')
+{
+	if ($pun_user['is_guest'])
+		message($lang_common['No permission'], false, '403 Forbidden');
+
+	$db->query('UPDATE '.$db->prefix.'users SET last_visit='.$pun_user['logged'].' WHERE id='.$pun_user['id']) or error('Unable to update user last visit data', __FILE__, __LINE__, $db->error());
+
+	// Reset tracked topics
+	set_tracked_topics(null);
+
+	redirect('index.php', $lang_misc['Mark read redirect']);
+}
+
+
+// Mark the topics/posts in a forum as read?
+else if ($action == 'markforumread')
+{
+	if ($pun_user['is_guest'])
+		message($lang_common['No permission'], false, '403 Forbidden');
+
+	$fid = isset($_GET['fid']) ? intval($_GET['fid']) : 0;
+	if ($fid < 1)
+		message($lang_common['Bad request']);
+
+	$tracked_topics = get_tracked_topics();
+	$tracked_topics['forums'][$fid] = time();
+	set_tracked_topics($tracked_topics);
+
+	redirect('viewforum.php?id='.$fid, $lang_misc['Mark forum read redirect']);
+}
+
+
+else if (isset($_GET['email']))
+{
+	if ($pun_user['is_guest'] || $pun_user['g_send_email'] == '0')
+		message($lang_common['No permission'], false, '403 Forbidden');
+
+	$recipient_id = intval($_GET['email']);
+	if ($recipient_id < 2)
+		message($lang_common['Bad request']);
+
+	$result = $db->query('SELECT username, email, email_setting FROM '.$db->prefix.'users WHERE id='.$recipient_id) or error('Unable to fetch user info', __FILE__, __LINE__, $db->error());
+	if (!$db->num_rows($result))
+		message($lang_common['Bad request']);
+
+	list($recipient, $recipient_email, $email_setting) = $db->fetch_row($result);
+
+	if ($email_setting == 2 && !$pun_user['is_admmod'])
+		message($lang_misc['Form email disabled']);
+
+
+	if (isset($_POST['form_sent']))
+	{
+		confirm_referrer('misc.php');
+
+		// Clean up message and subject from POST
+		$subject = pun_trim($_POST['req_subject']);
+		$message = pun_trim($_POST['req_message']);
+
+		if ($subject == '')
+			message($lang_misc['No email subject']);
+		else if ($message == '')
+			message($lang_misc['No email message']);
+		else if (pun_strlen($message) > PUN_MAX_POSTSIZE)
+			message($lang_misc['Too long email message']);
+
+		if ($pun_user['last_email_sent'] != '' && (time() - $pun_user['last_email_sent']) < $pun_user['g_email_flood'] && (time() - $pun_user['last_email_sent']) >= 0)
+			message(sprintf($lang_misc['Email flood'], $pun_user['g_email_flood']));
+
+		// Load the "form email" template
+		$mail_tpl = trim(file_get_contents(PUN_ROOT.'lang/'.$pun_user['language'].'/mail_templates/form_email.tpl'));
+
+		// The first row contains the subject
+		$first_crlf = strpos($mail_tpl, "\n");
+		$mail_subject = pun_trim(substr($mail_tpl, 8, $first_crlf-8));
+		$mail_message = pun_trim(substr($mail_tpl, $first_crlf));
+
+		$mail_subject = str_replace('<mail_subject>', $subject, $mail_subject);
+		$mail_message = str_replace('<sender>', $pun_user['username'], $mail_message);
+		$mail_message = str_replace('<board_title>', $pun_config['o_board_title'], $mail_message);
+		$mail_message = str_replace('<mail_message>', $message, $mail_message);
+		$mail_message = str_replace('<board_mailer>', $pun_config['o_board_title'], $mail_message);
+
+		require_once PUN_ROOT.'include/email.php';
+
+		pun_mail($recipient_email, $mail_subject, $mail_message, $pun_user['email'], $pun_user['username']);
+
+		$db->query('UPDATE '.$db->prefix.'users SET last_email_sent='.time().' WHERE id='.$pun_user['id']) or error('Unable to update user', __FILE__, __LINE__, $db->error());
+
+		redirect(htmlspecialchars($_POST['redirect_url']), $lang_misc['Email sent redirect']);
+	}
+
+
+	// Try to determine if the data in HTTP_REFERER is valid (if not, we redirect to the users profile after the email is sent)
+	if (!empty($_SERVER['HTTP_REFERER']))
+	{
+		$referrer = parse_url($_SERVER['HTTP_REFERER']);
+		// Remove www subdomain if it exists
+		if (strpos($referrer['host'], 'www.') === 0)
+			$referrer['host'] = substr($referrer['host'], 4);
+
+		// Make sure the path component exists
+		if (!isset($referrer['path']))
+			$referrer['path'] = '';
+
+		$valid = parse_url(get_base_url());
+		// Remove www subdomain if it exists
+		if (strpos($valid['host'], 'www.') === 0)
+			$valid['host'] = substr($valid['host'], 4);
+
+		// Make sure the path component exists
+		if (!isset($valid['path']))
+			$valid['path'] = '';
+
+		if ($referrer['host'] == $valid['host'] && preg_match('%^'.preg_quote($valid['path'], '%').'/(.*?)\.php%i', $referrer['path']))
+			$redirect_url = $_SERVER['HTTP_REFERER'];
+	}
+
+	if (!isset($redirect_url))
+		$redirect_url = 'profile.php?id='.$recipient_id;
+
+	$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_misc['Send email to'].' '.pun_htmlspecialchars($recipient));
+	$required_fields = array('req_subject' => $lang_misc['Email subject'], 'req_message' => $lang_misc['Email message']);
+	$focus_element = array('email', 'req_subject');
+	define('PUN_ACTIVE_PAGE', 'index');
+	require PUN_ROOT.'header.php';
+
+?>
+<div id="emailform" class="blockform">
+	<h2><span><?php echo $lang_misc['Send email to'] ?> <?php echo pun_htmlspecialchars($recipient) ?></span></h2>
+	<div class="box">
+		<form id="email" method="post" action="misc.php?email=<?php echo $recipient_id ?>" onsubmit="this.submit.disabled=true;if(process_form(this)){return true;}else{this.submit.disabled=false;return false;}">
+			<div class="inform">
+				<fieldset>
+					<legend><?php echo $lang_misc['Write email'] ?></legend>
+					<div class="infldset txtarea">
+						<input type="hidden" name="form_sent" value="1" />
+						<input type="hidden" name="redirect_url" value="<?php echo pun_htmlspecialchars($redirect_url) ?>" />
+						<label class="required"><strong><?php echo $lang_misc['Email subject'] ?> <span><?php echo $lang_common['Required'] ?></span></strong><br />
+						<input class="longinput" type="text" name="req_subject" size="75" maxlength="70" tabindex="1" /><br /></label>
+						<label class="required"><strong><?php echo $lang_misc['Email message'] ?> <span><?php echo $lang_common['Required'] ?></span></strong><br />
+						<textarea name="req_message" rows="10" cols="75" tabindex="2"></textarea><br /></label>
+						<p><?php echo $lang_misc['Email disclosure note'] ?></p>
+					</div>
+				</fieldset>
+			</div>
+			<p class="buttons"><input type="submit" name="submit" value="<?php echo $lang_common['Submit'] ?>" tabindex="3" accesskey="s" /> <a href="javascript:history.go(-1)"><?php echo $lang_common['Go back'] ?></a></p>
+		</form>
+	</div>
+</div>
+<?php
+
+	require PUN_ROOT.'footer.php';
+}
+
+
+else if (isset($_GET['report']))
+{
+	if ($pun_user['is_guest'])
+		message($lang_common['No permission']);
+
+	$post_id = intval($_GET['report']);
+	if ($post_id < 1)
+		message($lang_common['Bad request']);
+
+	if (isset($_POST['form_sent']))
+	{
+		// Make sure they got here from the site
+		confirm_referrer('misc.php');
+	
+		// Clean up reason from POST
+		$reason = pun_linebreaks(pun_trim($_POST['req_reason']));
+		if ($reason == '')
+			message($lang_misc['No reason']);
+		else if (strlen($reason) > 65535) // TEXT field can only hold 65535 bytes
+			message($lang_misc['Reason too long']);
+
+		if ($pun_user['last_report_sent'] != '' && (time() - $pun_user['last_report_sent']) < $pun_user['g_report_flood'] && (time() - $pun_user['last_report_sent']) >= 0)
+			message(sprintf($lang_misc['Report flood'], $pun_user['g_report_flood']));
+
+		// Get the topic ID
+		$result = $db->query('SELECT topic_id FROM '.$db->prefix.'posts WHERE id='.$post_id) or error('Unable to fetch post info', __FILE__, __LINE__, $db->error());
+		if (!$db->num_rows($result))
+			message($lang_common['Bad request']);
+
+		$topic_id = $db->result($result);
+
+		// Get the subject and forum ID
+		$result = $db->query('SELECT subject, forum_id FROM '.$db->prefix.'topics WHERE id='.$topic_id) or error('Unable to fetch topic info', __FILE__, __LINE__, $db->error());
+		if (!$db->num_rows($result))
+			message($lang_common['Bad request']);
+
+		list($subject, $forum_id) = $db->fetch_row($result);
+
+		// Should we use the internal report handling?
+		if ($pun_config['o_report_method'] == '0' || $pun_config['o_report_method'] == '2')
+			$db->query('INSERT INTO '.$db->prefix.'reports (post_id, topic_id, forum_id, reported_by, created, message) VALUES('.$post_id.', '.$topic_id.', '.$forum_id.', '.$pun_user['id'].', '.time().', \''.$db->escape($reason).'\')' ) or error('Unable to create report', __FILE__, __LINE__, $db->error());
+
+		// Should we email the report?
+		if ($pun_config['o_report_method'] == '1' || $pun_config['o_report_method'] == '2')
+		{
+			// We send it to the complete mailing-list in one swoop
+			if ($pun_config['o_mailing_list'] != '')
+			{
+				// Load the "new report" template
+				$mail_tpl = trim(file_get_contents(PUN_ROOT.'lang/'.$pun_user['language'].'/mail_templates/new_report.tpl'));
+
+				// The first row contains the subject
+				$first_crlf = strpos($mail_tpl, "\n");
+				$mail_subject = trim(substr($mail_tpl, 8, $first_crlf-8));
+				$mail_message = trim(substr($mail_tpl, $first_crlf));
+
+				$mail_subject = str_replace('<forum_id>', $forum_id, $mail_subject);
+				$mail_subject = str_replace('<topic_subject>', $subject, $mail_subject);
+				$mail_message = str_replace('<username>', $pun_user['username'], $mail_message);
+				$mail_message = str_replace('<post_url>', get_base_url().'/viewtopic.php?pid='.$post_id.'#p'.$post_id, $mail_message);
+				$mail_message = str_replace('<reason>', $reason, $mail_message);
+				$mail_message = str_replace('<board_mailer>', $pun_config['o_board_title'], $mail_message);
+
+				require PUN_ROOT.'include/email.php';
+
+				pun_mail($pun_config['o_mailing_list'], $mail_subject, $mail_message);
+			}
+		}
+
+		$db->query('UPDATE '.$db->prefix.'users SET last_report_sent='.time().' WHERE id='.$pun_user['id']) or error('Unable to update user', __FILE__, __LINE__, $db->error());
+
+		redirect('viewtopic.php?pid='.$post_id.'#p'.$post_id, $lang_misc['Report redirect']);
+	}
+
+	// Fetch some info about the post, the topic and the forum
+	$result = $db->query('SELECT f.id AS fid, f.forum_name, t.id AS tid, t.subject 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 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 p.id='.$post_id) or error('Unable to fetch post info', __FILE__, __LINE__, $db->error());
+	if (!$db->num_rows($result))
+		message($lang_common['Bad request']);
+
+	$cur_post = $db->fetch_assoc($result);
+
+	if ($pun_config['o_censoring'] == '1')
+		$cur_post['subject'] = censor_words($cur_post['subject']);
+
+	$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_misc['Report post']);
+	$required_fields = array('req_reason' => $lang_misc['Reason']);
+	$focus_element = array('report', 'req_reason');
+	define('PUN_ACTIVE_PAGE', 'index');
+	require PUN_ROOT.'header.php';
+
+?>
+<div class="linkst">
+	<div class="inbox">
+		<ul class="crumbs">
+			<li><a href="index.php"><?php echo $lang_common['Index'] ?></a></li>
+			<li><span>»&#160;</span><a href="viewforum.php?id=<?php echo $cur_post['fid'] ?>"><?php echo pun_htmlspecialchars($cur_post['forum_name']) ?></a></li>
+			<li><span>»&#160;</span><a href="viewtopic.php?pid=<?php echo $post_id ?>#p<?php echo $post_id ?>"><?php echo pun_htmlspecialchars($cur_post['subject']) ?></a></li>
+			<li><span>»&#160;</span><strong><?php echo $lang_misc['Report post'] ?></strong></li>
+		</ul>
+	</div>
+</div>
+
+<div id="reportform" class="blockform">
+	<h2><span><?php echo $lang_misc['Report post'] ?></span></h2>
+	<div class="box">
+		<form id="report" method="post" action="misc.php?report=<?php echo $post_id ?>" onsubmit="this.submit.disabled=true;if(process_form(this)){return true;}else{this.submit.disabled=false;return false;}">
+			<div class="inform">
+				<fieldset>
+					<legend><?php echo $lang_misc['Reason desc'] ?></legend>
+					<div class="infldset txtarea">
+						<input type="hidden" name="form_sent" value="1" />
+						<label class="required"><strong><?php echo $lang_misc['Reason'] ?> <span><?php echo $lang_common['Required'] ?></span></strong><br /><textarea name="req_reason" rows="5" cols="60"></textarea><br /></label>
+					</div>
+				</fieldset>
+			</div>
+			<p class="buttons"><input type="submit" name="submit" value="<?php echo $lang_common['Submit'] ?>" accesskey="s" /> <a href="javascript:history.go(-1)"><?php echo $lang_common['Go back'] ?></a></p>
+		</form>
+	</div>
+</div>
+<?php
+
+	require PUN_ROOT.'footer.php';
+}
+
+
+else if ($action == 'subscribe')
+{
+	if ($pun_user['is_guest'])
+		message($lang_common['No permission']);
+
+	$topic_id = isset($_GET['tid']) ? intval($_GET['tid']) : 0;
+	$forum_id = isset($_GET['fid']) ? intval($_GET['fid']) : 0;
+	if ($topic_id < 1 && $forum_id < 1)
+		message($lang_common['Bad request']);
+
+	if ($topic_id)
+	{
+		if ($pun_config['o_topic_subscriptions'] != '1')
+			message($lang_common['No permission']);
+
+		// Make sure the user can view the topic
+		$result = $db->query('SELECT 1 FROM '.$db->prefix.'topics AS t 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.id='.$topic_id.' AND t.moved_to IS NULL') or error('Unable to fetch topic info', __FILE__, __LINE__, $db->error());
+		if (!$db->num_rows($result))
+			message($lang_common['Bad request']);
+
+		$result = $db->query('SELECT 1 FROM '.$db->prefix.'topic_subscriptions WHERE user_id='.$pun_user['id'].' AND topic_id='.$topic_id) or error('Unable to fetch subscription info', __FILE__, __LINE__, $db->error());
+		if ($db->num_rows($result))
+			message($lang_misc['Already subscribed topic']);
+
+		$db->query('INSERT INTO '.$db->prefix.'topic_subscriptions (user_id, topic_id) VALUES('.$pun_user['id'].' ,'.$topic_id.')') or error('Unable to add subscription', __FILE__, __LINE__, $db->error());
+
+		redirect('viewtopic.php?id='.$topic_id, $lang_misc['Subscribe redirect']);
+	}
+
+	if ($forum_id)
+	{
+		if ($pun_config['o_forum_subscriptions'] != '1')
+			message($lang_common['No permission']);
+
+		// Make sure the user can view the forum
+		$result = $db->query('SELECT 1 FROM '.$db->prefix.'forums AS f 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.id='.$forum_id) or error('Unable to fetch forum info', __FILE__, __LINE__, $db->error());
+		if (!$db->num_rows($result))
+			message($lang_common['Bad request']);
+
+		$result = $db->query('SELECT 1 FROM '.$db->prefix.'forum_subscriptions WHERE user_id='.$pun_user['id'].' AND forum_id='.$forum_id) or error('Unable to fetch subscription info', __FILE__, __LINE__, $db->error());
+		if ($db->num_rows($result))
+			message($lang_misc['Already subscribed forum']);
+
+		$db->query('INSERT INTO '.$db->prefix.'forum_subscriptions (user_id, forum_id) VALUES('.$pun_user['id'].' ,'.$forum_id.')') or error('Unable to add subscription', __FILE__, __LINE__, $db->error());
+
+		redirect('viewforum.php?id='.$forum_id, $lang_misc['Subscribe redirect']);
+	}
+}
+
+
+else if ($action == 'unsubscribe')
+{
+	if ($pun_user['is_guest'])
+		message($lang_common['No permission']);
+
+	$topic_id = isset($_GET['tid']) ? intval($_GET['tid']) : 0;
+	$forum_id = isset($_GET['fid']) ? intval($_GET['fid']) : 0;
+	if ($topic_id < 1 && $forum_id < 1)
+		message($lang_common['Bad request']);
+
+	if ($topic_id)
+	{
+		if ($pun_config['o_topic_subscriptions'] != '1')
+			message($lang_common['No permission']);
+
+		$result = $db->query('SELECT 1 FROM '.$db->prefix.'topic_subscriptions WHERE user_id='.$pun_user['id'].' AND topic_id='.$topic_id) or error('Unable to fetch subscription info', __FILE__, __LINE__, $db->error());
+		if (!$db->num_rows($result))
+			message($lang_misc['Not subscribed topic']);
+
+		$db->query('DELETE FROM '.$db->prefix.'topic_subscriptions WHERE user_id='.$pun_user['id'].' AND topic_id='.$topic_id) or error('Unable to remove subscription', __FILE__, __LINE__, $db->error());
+
+		redirect('viewtopic.php?id='.$topic_id, $lang_misc['Unsubscribe redirect']);
+	}
+
+	if ($forum_id)
+	{
+		if ($pun_config['o_forum_subscriptions'] != '1')
+			message($lang_common['No permission']);
+
+		$result = $db->query('SELECT 1 FROM '.$db->prefix.'forum_subscriptions WHERE user_id='.$pun_user['id'].' AND forum_id='.$forum_id) or error('Unable to fetch subscription info', __FILE__, __LINE__, $db->error());
+		if (!$db->num_rows($result))
+			message($lang_misc['Not subscribed forum']);
+
+		$db->query('DELETE FROM '.$db->prefix.'forum_subscriptions WHERE user_id='.$pun_user['id'].' AND forum_id='.$forum_id) or error('Unable to remove subscription', __FILE__, __LINE__, $db->error());
+
+		redirect('viewforum.php?id='.$forum_id, $lang_misc['Unsubscribe redirect']);
+	}
+}
+
+
+else
+	message($lang_common['Bad request']);
diff --git a/moderate.php b/moderate.php
new file mode 100644
index 0000000..4555e31
--- /dev/null
+++ b/moderate.php
@@ -0,0 +1,1004 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+define('PUN_ROOT', dirname(__FILE__).'/');
+require PUN_ROOT.'include/common.php';
+
+
+// This particular function doesn't require forum-based moderator access. It can be used
+// by all moderators and admins
+if (isset($_GET['get_host']))
+{
+	if (!$pun_user['is_admmod'])
+		message($lang_common['No permission'], false, '403 Forbidden');
+
+	// Is get_host an IP address or a post ID?
+	if (@preg_match('%^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$%', $_GET['get_host']) || @preg_match('%^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$%', $_GET['get_host']))
+		$ip = $_GET['get_host'];
+	else
+	{
+		$get_host = intval($_GET['get_host']);
+		if ($get_host < 1)
+			message($lang_common['Bad request']);
+
+		$result = $db->query('SELECT poster_ip FROM '.$db->prefix.'posts WHERE id='.$get_host) or error('Unable to fetch post IP address', __FILE__, __LINE__, $db->error());
+		if (!$db->num_rows($result))
+			message($lang_common['Bad request'], false, '404 Not Found');
+
+		$ip = $db->result($result);
+	}
+
+	// Load the misc.php language file
+	require PUN_ROOT.'lang/'.$pun_user['language'].'/misc.php';
+
+	message(sprintf($lang_misc['Host info 1'], $ip).'<br />'.sprintf($lang_misc['Host info 2'], @gethostbyaddr($ip)).'<br /><br /><a href="admin_users.php?show_users='.$ip.'">'.$lang_misc['Show more users'].'</a>');
+}
+
+
+// All other functions require moderator/admin access
+$fid = isset($_GET['fid']) ? intval($_GET['fid']) : 0;
+if ($fid < 1)
+	message($lang_common['Bad request'], false, '404 Not Found');
+
+$result = $db->query('SELECT moderators FROM '.$db->prefix.'forums WHERE id='.$fid) or error('Unable to fetch forum info', __FILE__, __LINE__, $db->error());
+
+$moderators = $db->result($result);
+$mods_array = ($moderators != '') ? unserialize($moderators) : array();
+
+if ($pun_user['g_id'] != PUN_ADMIN && ($pun_user['g_moderator'] == '0' || !array_key_exists($pun_user['username'], $mods_array)))
+	message($lang_common['No permission'], false, '403 Forbidden');
+
+// Get topic/forum tracking data
+if (!$pun_user['is_guest'])
+	$tracked_topics = get_tracked_topics();
+
+// Load the misc.php language file
+require PUN_ROOT.'lang/'.$pun_user['language'].'/misc.php';
+
+
+// All other topic moderation features require a topic ID in GET
+if (isset($_GET['tid']))
+{
+	$tid = intval($_GET['tid']);
+	if ($tid < 1)
+		message($lang_common['Bad request'], false, '404 Not Found');
+
+	// Fetch some info about the topic
+	$result = $db->query('SELECT t.subject, t.num_replies, t.first_post_id, f.id AS forum_id, forum_name FROM '.$db->prefix.'topics AS t INNER JOIN '.$db->prefix.'forums AS f ON f.id=t.forum_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.id='.$fid.' AND t.id='.$tid.' AND t.moved_to IS NULL') or error('Unable to fetch topic info', __FILE__, __LINE__, $db->error());
+	if (!$db->num_rows($result))
+		message($lang_common['Bad request'], false, '404 Not Found');
+
+	$cur_topic = $db->fetch_assoc($result);
+
+	// Delete one or more posts
+	if (isset($_POST['delete_posts']) || isset($_POST['delete_posts_comply']))
+	{
+		$posts = isset($_POST['posts']) ? $_POST['posts'] : array();
+		if (empty($posts))
+			message($lang_misc['No posts selected']);
+
+		if (isset($_POST['delete_posts_comply']))
+		{
+			confirm_referrer('moderate.php');
+
+			if (@preg_match('%[^0-9,]%', $posts))
+				message($lang_common['Bad request']);
+
+			// 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) != substr_count($posts, ',') + 1)
+				message($lang_common['Bad request']);
+
+			// Delete the posts
+			$db->query('DELETE FROM '.$db->prefix.'posts WHERE id IN('.$posts.')') or error('Unable to delete posts', __FILE__, __LINE__, $db->error());
+
+			require PUN_ROOT.'include/search_idx.php';
+			strip_search_index($posts);
+
+			// Get last_post, last_post_id, and last_poster for the topic after deletion
+			$result = $db->query('SELECT id, poster, posted FROM '.$db->prefix.'posts WHERE topic_id='.$tid.' ORDER BY id DESC LIMIT 1') or error('Unable to fetch post info', __FILE__, __LINE__, $db->error());
+			$last_post = $db->fetch_assoc($result);
+
+			// How many posts did we just delete?
+			$num_posts_deleted = substr_count($posts, ',') + 1;
+
+			// Update the topic
+			$db->query('UPDATE '.$db->prefix.'topics SET last_post='.$last_post['posted'].', last_post_id='.$last_post['id'].', last_poster=\''.$db->escape($last_post['poster']).'\', num_replies=num_replies-'.$num_posts_deleted.' WHERE id='.$tid) or error('Unable to update topic', __FILE__, __LINE__, $db->error());
+
+			update_forum($fid);
+
+			redirect('viewtopic.php?id='.$tid, $lang_misc['Delete posts redirect']);
+		}
+
+
+		$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_misc['Moderate']);
+		define('PUN_ACTIVE_PAGE', 'index');
+		require PUN_ROOT.'header.php';
+
+?>
+<div class="blockform">
+	<h2><span><?php echo $lang_misc['Delete posts'] ?></span></h2>
+	<div class="box">
+		<form method="post" action="moderate.php?fid=<?php echo $fid ?>&amp;tid=<?php echo $tid ?>">
+			<div class="inform">
+				<fieldset>
+					<legend><?php echo $lang_misc['Confirm delete legend'] ?></legend>
+					<div class="infldset">
+						<input type="hidden" name="posts" value="<?php echo implode(',', array_map('intval', array_keys($posts))) ?>" />
+						<p><?php echo $lang_misc['Delete posts comply'] ?></p>
+					</div>
+				</fieldset>
+			</div>
+			<p class="buttons"><input type="submit" name="delete_posts_comply" value="<?php echo $lang_misc['Delete'] ?>" /> <a href="javascript:history.go(-1)"><?php echo $lang_common['Go back'] ?></a></p>
+		</form>
+	</div>
+</div>
+<?php
+
+		require PUN_ROOT.'footer.php';
+	}
+	else if (isset($_POST['split_posts']) || isset($_POST['split_posts_comply']))
+	{
+		$posts = isset($_POST['posts']) ? $_POST['posts'] : array();
+		if (empty($posts))
+			message($lang_misc['No posts selected']);
+
+		if (isset($_POST['split_posts_comply']))
+		{
+			confirm_referrer('moderate.php');
+
+			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';
+
+			// Check subject
+			$new_subject = isset($_POST['new_subject']) ? pun_trim($_POST['new_subject']) : '';
+
+			if ($new_subject == '')
+				message($lang_post['No subject']);
+			else if (pun_strlen($new_subject) > 70)
+				message($lang_post['Too long subject']);
+
+			// Get data from the new first post
+			$result = $db->query('SELECT p.id, p.poster, p.posted FROM '.$db->prefix.'posts AS p WHERE id IN('.$posts.') ORDER BY p.id ASC LIMIT 1') or error('Unable to get first post', __FILE__, __LINE__, $db->error());
+			$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'].', '.$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
+			$db->query('UPDATE '.$db->prefix.'posts SET topic_id='.$new_tid.' WHERE id IN('.$posts.')') or error('Unable to move posts into new topic', __FILE__, __LINE__, $db->error());
+
+			// Apply every subscription to both topics
+			$db->query('INSERT INTO '.$db->prefix.'topic_subscriptions (user_id, topic_id) SELECT user_id, '.$new_tid.' FROM '.$db->prefix.'topic_subscriptions WHERE topic_id='.$tid) or error('Unable to copy existing subscriptions', __FILE__, __LINE__, $db->error());
+
+			// Get last_post, last_post_id, and last_poster from the topic and update it
+			$result = $db->query('SELECT id, poster, posted FROM '.$db->prefix.'posts WHERE topic_id='.$tid.' ORDER BY id DESC LIMIT 1') or error('Unable to fetch post info', __FILE__, __LINE__, $db->error());
+			$last_post_data = $db->fetch_assoc($result);
+			$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_replies-'.$num_posts_splitted.' WHERE id='.$tid) or error('Unable to update topic', __FILE__, __LINE__, $db->error());
+
+			// Get last_post, last_post_id, and last_poster from the new topic and update it
+			$result = $db->query('SELECT id, poster, posted FROM '.$db->prefix.'posts WHERE topic_id='.$new_tid.' ORDER BY id DESC LIMIT 1') or error('Unable to fetch post info', __FILE__, __LINE__, $db->error());
+			$last_post_data = $db->fetch_assoc($result);
+			$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');
+		require PUN_ROOT.'header.php';
+
+?>
+<div class="blockform">
+	<h2><span><?php echo $lang_misc['Split posts'] ?></span></h2>
+	<div class="box">
+		<form id="subject" method="post" action="moderate.php?fid=<?php echo $fid ?>&amp;tid=<?php echo $tid ?>">
+			<div class="inform">
+				<fieldset>
+					<legend><?php echo $lang_misc['Confirm split legend'] ?></legend>
+					<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>
+			</div>
+			<p class="buttons"><input type="submit" name="split_posts_comply" value="<?php echo $lang_misc['Split'] ?>" /> <a href="javascript:history.go(-1)"><?php echo $lang_common['Go back'] ?></a></p>
+		</form>
+	</div>
+</div>
+<?php
+
+		require PUN_ROOT.'footer.php';
+	}
+
+
+	// Show the moderate posts view
+
+	// Load the viewtopic.php language file
+	require PUN_ROOT.'lang/'.$pun_user['language'].'/topic.php';
+
+	// Used to disable the Move and Delete buttons if there are no replies to this topic
+	$button_status = ($cur_topic['num_replies'] == 0) ? ' disabled="disabled"' : '';
+
+
+	// Determine the post offset (based on $_GET['p'])
+	$num_pages = ceil(($cur_topic['num_replies'] + 1) / $pun_user['disp_posts']);
+
+	$p = (!isset($_GET['p']) || $_GET['p'] <= 1 || $_GET['p'] > $num_pages) ? 1 : intval($_GET['p']);
+	$start_from = $pun_user['disp_posts'] * ($p - 1);
+
+	// Generate paging links
+	$paging_links = '<span class="pages-label">'.$lang_common['Pages'].' </span>'.paginate($num_pages, $p, 'moderate.php?fid='.$fid.'&amp;tid='.$tid);
+
+
+	if ($pun_config['o_censoring'] == '1')
+		$cur_topic['subject'] = censor_words($cur_topic['subject']);
+
+
+	$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), pun_htmlspecialchars($cur_topic['forum_name']), pun_htmlspecialchars($cur_topic['subject']));
+	define('PUN_ACTIVE_PAGE', 'index');
+	require PUN_ROOT.'header.php';
+
+?>
+<div class="linkst">
+	<div class="inbox crumbsplus">
+		<ul class="crumbs">
+			<li><a href="index.php"><?php echo $lang_common['Index'] ?></a></li>
+			<li><span>»&#160;</span><a href="viewforum.php?id=<?php echo $fid ?>"><?php echo pun_htmlspecialchars($cur_topic['forum_name']) ?></a></li>
+			<li><span>»&#160;</span><a href="viewtopic.php?id=<?php echo $tid ?>"><?php echo pun_htmlspecialchars($cur_topic['subject']) ?></a></li>
+			<li><span>»&#160;</span><strong><?php echo $lang_misc['Moderate'] ?></strong></li>
+		</ul>
+		<div class="pagepost">
+			<p class="pagelink conl"><?php echo $paging_links ?></p>
+		</div>
+		<div class="clearer"></div>
+	</div>
+</div>
+
+<form method="post" action="moderate.php?fid=<?php echo $fid ?>&amp;tid=<?php echo $tid ?>">
+<?php
+
+	require PUN_ROOT.'include/parser.php';
+
+	$post_count = 0; // Keep track of post numbers
+
+	// Retrieve a list of post IDs, LIMIT is (really) expensive so we only fetch the IDs here then later fetch the remaining data
+	$result = $db->query('SELECT id FROM '.$db->prefix.'posts WHERE topic_id='.$tid.' ORDER BY id LIMIT '.$start_from.','.$pun_user['disp_posts']) or error('Unable to fetch post IDs', __FILE__, __LINE__, $db->error());
+
+	$post_ids = array();
+	for ($i = 0;$cur_post_id = $db->result($result, $i);$i++)
+		$post_ids[] = $cur_post_id;
+
+	// Retrieve the posts (and their respective poster)
+	$result = $db->query('SELECT u.title, u.num_posts, g.g_id, g.g_user_title, p.id, p.poster, p.poster_id, p.message, p.hide_smilies, p.posted, p.edited, p.edited_by FROM '.$db->prefix.'posts AS p INNER JOIN '.$db->prefix.'users AS u ON u.id=p.poster_id INNER JOIN '.$db->prefix.'groups AS g ON g.g_id=u.group_id WHERE p.id IN ('.implode(',', $post_ids).') ORDER BY p.id', true) or error('Unable to fetch post info', __FILE__, __LINE__, $db->error());
+
+	while ($cur_post = $db->fetch_assoc($result))
+	{
+		$post_count++;
+
+		// If the poster is a registered user
+		if ($cur_post['poster_id'] > 1)
+		{
+			if ($pun_user['g_view_users'] == '1')
+				$poster = '<a href="profile.php?id='.$cur_post['poster_id'].'">'.pun_htmlspecialchars($cur_post['poster']).'</a>';
+			else
+				$poster = pun_htmlspecialchars($cur_post['poster']);
+
+			// get_title() requires that an element 'username' be present in the array
+			$cur_post['username'] = $cur_post['poster'];
+			$user_title = get_title($cur_post);
+
+			if ($pun_config['o_censoring'] == '1')
+				$user_title = censor_words($user_title);
+		}
+		// If the poster is a guest (or a user that has been deleted)
+		else
+		{
+			$poster = pun_htmlspecialchars($cur_post['poster']);
+			$user_title = $lang_topic['Guest'];
+		}
+
+		// Perform the main parsing of the message (BBCode, smilies, censor words etc)
+		$cur_post['message'] = parse_message($cur_post['message'], $cur_post['hide_smilies']);
+
+?>
+
+<div id="p<?php echo $cur_post['id'] ?>" class="blockpost<?php if($cur_post['id'] == $cur_topic['first_post_id']) echo ' firstpost' ?><?php echo ($post_count % 2 == 0) ? ' roweven' : ' rowodd' ?><?php if ($post_count == 1) echo ' blockpost1' ?>">
+	<h2><span><span class="conr">#<?php echo ($start_from + $post_count) ?></span> <a href="viewtopic.php?pid=<?php echo $cur_post['id'].'#p'.$cur_post['id'] ?>"><?php echo format_time($cur_post['posted']) ?></a></span></h2>
+	<div class="box">
+		<div class="inbox">
+			<div class="postbody">
+				<div class="postleft">
+					<dl>
+						<dt><strong><?php echo $poster ?></strong></dt>
+						<dd class="usertitle"><strong><?php echo $user_title ?></strong></dd>
+					</dl>
+				</div>
+				<div class="postright">
+					<h3 class="nosize"><?php echo $lang_common['Message'] ?></h3>
+					<div class="postmsg">
+						<?php echo $cur_post['message']."\n" ?>
+<?php if ($cur_post['edited'] != '') echo "\t\t\t\t\t\t".'<p class="postedit"><em>'.$lang_topic['Last edit'].' '.pun_htmlspecialchars($cur_post['edited_by']).' ('.format_time($cur_post['edited']).')</em></p>'."\n"; ?>
+					</div>
+				</div>
+			</div>
+		</div>
+		<div class="inbox">
+			<div class="postfoot clearb">
+				<div class="postfootright"><?php echo ($cur_post['id'] != $cur_topic['first_post_id']) ? '<p class="multidelete"><label><strong>'.$lang_misc['Select'].'</strong>&#160;<input type="checkbox" name="posts['.$cur_post['id'].']" value="1" /></label></p>' : '<p>'.$lang_misc['Cannot select first'].'</p>' ?></div>
+			</div>
+		</div>
+	</div>
+</div>
+
+<?php
+
+	}
+
+?>
+<div class="postlinksb">
+	<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>»&#160;</span><a href="viewforum.php?id=<?php echo $fid ?>"><?php echo pun_htmlspecialchars($cur_topic['forum_name']) ?></a></li>
+			<li><span>»&#160;</span><a href="viewtopic.php?id=<?php echo $tid ?>"><?php echo pun_htmlspecialchars($cur_topic['subject']) ?></a></li>
+			<li><span>»&#160;</span><strong><?php echo $lang_misc['Moderate'] ?></strong></li>
+		</ul>
+		<div class="clearer"></div>
+	</div>
+</div>
+</form>
+<?php
+
+	require PUN_ROOT.'footer.php';
+}
+
+
+// Move one or more topics
+if (isset($_REQUEST['move_topics']) || isset($_POST['move_topics_to']))
+{
+	if (isset($_POST['move_topics_to']))
+	{
+		confirm_referrer('moderate.php');
+
+		if (@preg_match('%[^0-9,]%', $_POST['topics']))
+			message($lang_common['Bad request']);
+
+		$topics = explode(',', $_POST['topics']);
+		$move_to_forum = isset($_POST['move_to_forum']) ? intval($_POST['move_to_forum']) : 0;
+		if (empty($topics) || $move_to_forum < 1)
+			message($lang_common['Bad request']);
+
+		// Verify that the topic IDs are valid
+		$result = $db->query('SELECT 1 FROM '.$db->prefix.'topics WHERE id IN('.implode(',',$topics).') AND forum_id='.$fid) or error('Unable to check topics', __FILE__, __LINE__, $db->error());
+
+		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());
+
+		// Move the topic(s)
+		$db->query('UPDATE '.$db->prefix.'topics SET forum_id='.$move_to_forum.' WHERE id IN('.implode(',',$topics).')') or error('Unable to move topics', __FILE__, __LINE__, $db->error());
+
+		// Should we create redirect topics?
+		if (isset($_POST['with_redirect']))
+		{
+			foreach ($topics as $cur_topic)
+			{
+				// Fetch info for the redirect topic
+				$result = $db->query('SELECT poster, subject, posted, last_post FROM '.$db->prefix.'topics WHERE id='.$cur_topic) or error('Unable to fetch topic info', __FILE__, __LINE__, $db->error());
+				$moved_to = $db->fetch_assoc($result);
+
+				// Create the redirect topic
+				$db->query('INSERT INTO '.$db->prefix.'topics (poster, subject, posted, last_post, moved_to, forum_id) VALUES(\''.$db->escape($moved_to['poster']).'\', \''.$db->escape($moved_to['subject']).'\', '.$moved_to['posted'].', '.$moved_to['last_post'].', '.$cur_topic.', '.$fid.')') or error('Unable to create redirect topic', __FILE__, __LINE__, $db->error());
+			}
+		}
+
+		update_forum($fid); // Update the forum FROM which the topic was moved
+		update_forum($move_to_forum); // Update the forum TO which the topic was moved
+
+		$redirect_msg = (count($topics) > 1) ? $lang_misc['Move topics redirect'] : $lang_misc['Move topic redirect'];
+		redirect('viewforum.php?id='.$move_to_forum, $redirect_msg);
+	}
+
+	if (isset($_POST['move_topics']))
+	{
+		$topics = isset($_POST['topics']) ? $_POST['topics'] : array();
+		if (empty($topics))
+			message($lang_misc['No topics selected']);
+
+		$topics = implode(',', array_map('intval', array_keys($topics)));
+		$action = 'multi';
+	}
+	else
+	{
+		$topics = intval($_GET['move_topics']);
+		if ($topics < 1)
+			message($lang_common['Bad request']);
+
+		$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.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']);
+
+	$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_misc['Moderate']);
+	define('PUN_ACTIVE_PAGE', 'index');
+	require PUN_ROOT.'header.php';
+
+?>
+<div class="blockform">
+	<h2><span><?php echo ($action == 'single') ? $lang_misc['Move topic'] : $lang_misc['Move topics'] ?></span></h2>
+	<div class="box">
+		<form method="post" action="moderate.php?fid=<?php echo $fid ?>">
+			<div class="inform">
+			<input type="hidden" name="topics" value="<?php echo $topics ?>" />
+				<fieldset>
+					<legend><?php echo $lang_misc['Move legend'] ?></legend>
+					<div class="infldset">
+						<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'];
+		}
+
+		if ($cur_forum['fid'] != $fid)
+			echo "\t\t\t\t\t\t\t\t".'<option value="'.$cur_forum['fid'].'">'.pun_htmlspecialchars($cur_forum['forum_name']).'</option>'."\n";
+	}
+
+?>
+							</optgroup>
+						</select>
+						<br /></label>
+						<div class="rbox">
+							<label><input type="checkbox" name="with_redirect" value="1"<?php if ($action == 'single') echo ' checked="checked"' ?> /><?php echo $lang_misc['Leave redirect'] ?><br /></label>
+						</div>
+					</div>
+				</fieldset>
+			</div>
+			<p class="buttons"><input type="submit" name="move_topics_to" value="<?php echo $lang_misc['Move'] ?>" /> <a href="javascript:history.go(-1)"><?php echo $lang_common['Go back'] ?></a></p>
+		</form>
+	</div>
+</div>
+<?php
+
+	require PUN_ROOT.'footer.php';
+}
+
+// Merge two or more topics
+else if (isset($_POST['merge_topics']) || isset($_POST['merge_topics_comply']))
+{
+	if (isset($_POST['merge_topics_comply']))
+	{
+		confirm_referrer('moderate.php');
+
+		if (@preg_match('%[^0-9,]%', $_POST['topics']))
+			message($lang_common['Bad request']);
+
+		$topics = explode(',', $_POST['topics']);
+		if (count($topics) < 2)
+			message($lang_misc['Not enough topics selected']);
+
+		// Verify that the topic IDs are valid (redirect links will point to the merged topic after the merge)
+		$result = $db->query('SELECT id FROM '.$db->prefix.'topics WHERE id IN('.implode(',', $topics).') AND forum_id='.$fid.' ORDER BY id ASC') or error('Unable to check topics', __FILE__, __LINE__, $db->error());
+		if ($db->num_rows($result) != count($topics))
+			message($lang_common['Bad request']);
+
+		// The topic that we are merging into is the one with the smallest ID
+		$merge_to_tid = $db->result($result);
+
+		// Make any redirect topics point to our new, merged topic
+		$query = 'UPDATE '.$db->prefix.'topics SET moved_to='.$merge_to_tid.' WHERE moved_to IN('.implode(',', $topics).')';
+
+		// Should we create redirect topics?
+		if (isset($_POST['with_redirect']))
+			$query .= ' OR (id IN('.implode(',', $topics).') AND id != '.$merge_to_tid.')';
+
+		$db->query($query) or error('Unable to make redirection topics', __FILE__, __LINE__, $db->error());
+
+		// Merge the posts into the topic
+		$db->query('UPDATE '.$db->prefix.'posts SET topic_id='.$merge_to_tid.' WHERE topic_id IN('.implode(',', $topics).')') or error('Unable to merge the posts into the topic', __FILE__, __LINE__, $db->error());
+
+		// Update any subscriptions
+		$result = $db->query('SELECT user_id FROM '.$db->prefix.'topic_subscriptions WHERE topic_id IN ('.implode(',', $topics).')') or error('Unable to fetch subscriptions of merged topics', __FILE__, __LINE__, $db->error());
+
+		$subscribed_users = array();
+		while ($cur_user_id = $db->result($result))
+			$subscribed_users[] = $cur_user_id;
+		$subscribed_users = array_unique($subscribed_users);
+
+		$db->query('DELETE FROM '.$db->prefix.'topic_subscriptions WHERE topic_id IN ('.implode(',', $topics).')') or error('Unable to delete subscriptions of merged topics', __FILE__, __LINE__, $db->error());
+
+		foreach ($subscribed_users as $cur_user_id)
+			$db->query('INSERT INTO '.$db->prefix.'topic_subscriptions (topic_id, user_id) VALUES ('.$merge_to_tid.', '.$cur_user_id.')') or error('Unable to re-enter subscriptions for merge topic', __FILE__, __LINE__, $db->error());
+
+		// Without redirection the old topics are removed
+		if (!isset($_POST['with_redirect']))
+			$db->query('DELETE FROM '.$db->prefix.'topics WHERE id IN('.implode(',', $topics).') AND id != '.$merge_to_tid) or error('Unable to delete old topics', __FILE__, __LINE__, $db->error());
+
+		// Count number of replies in the topic
+		$result = $db->query('SELECT COUNT(id) FROM '.$db->prefix.'posts WHERE topic_id='.$merge_to_tid) or error('Unable to fetch post count for topic', __FILE__, __LINE__, $db->error());
+		$num_replies = $db->result($result, 0) - 1;
+
+		// Get last_post, last_post_id and last_poster
+		$result = $db->query('SELECT posted, id, poster FROM '.$db->prefix.'posts WHERE topic_id='.$merge_to_tid.' ORDER BY id DESC LIMIT 1') or error('Unable to get last post info', __FILE__, __LINE__, $db->error());
+		list($last_post, $last_post_id, $last_poster) = $db->fetch_row($result);
+
+		// Update topic
+		$db->query('UPDATE '.$db->prefix.'topics SET num_replies='.$num_replies.', last_post='.$last_post.', last_post_id='.$last_post_id.', last_poster=\''.$db->escape($last_poster).'\' WHERE id='.$merge_to_tid) or error('Unable to update topic', __FILE__, __LINE__, $db->error());
+
+		// Update the forum FROM which the topic was moved and redirect
+		update_forum($fid);
+		redirect('viewforum.php?id='.$fid, $lang_misc['Merge topics redirect']);
+	}
+
+	$topics = isset($_POST['topics']) ? $_POST['topics'] : array();
+	if (count($topics) < 2)
+		message($lang_misc['Not enough topics selected']);
+
+	$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_misc['Moderate']);
+	define('PUN_ACTIVE_PAGE', 'index');
+	require PUN_ROOT.'header.php';
+
+?>
+<div class="blockform">
+	<h2><span><?php echo $lang_misc['Merge topics'] ?></span></h2>
+	<div class="box">
+		<form method="post" action="moderate.php?fid=<?php echo $fid ?>">
+			<input type="hidden" name="topics" value="<?php echo implode(',', array_map('intval', array_keys($topics))) ?>" />
+			<div class="inform">
+				<fieldset>
+					<legend><?php echo $lang_misc['Confirm merge legend'] ?></legend>
+					<div class="infldset">
+						<div class="rbox">
+							<label><input type="checkbox" name="with_redirect" value="1" /><?php echo $lang_misc['Leave redirect'] ?><br /></label>
+						</div>
+					</div>
+				</fieldset>
+			</div>
+			<p class="buttons"><input type="submit" name="merge_topics_comply" value="<?php echo $lang_misc['Merge'] ?>" /> <a href="javascript:history.go(-1)"><?php echo $lang_common['Go back'] ?></a></p>
+		</form>
+	</div>
+</div>
+<?php
+
+	require PUN_ROOT.'footer.php';
+}
+
+// Delete one or more topics
+else if (isset($_POST['delete_topics']) || isset($_POST['delete_topics_comply']))
+{
+	$topics = isset($_POST['topics']) ? $_POST['topics'] : array();
+	if (empty($topics))
+		message($lang_misc['No topics selected']);
+
+	if (isset($_POST['delete_topics_comply']))
+	{
+		confirm_referrer('moderate.php');
+
+		if (@preg_match('%[^0-9,]%', $topics))
+			message($lang_common['Bad request']);
+
+		require PUN_ROOT.'include/search_idx.php';
+
+		// Verify that the topic IDs are valid
+		$result = $db->query('SELECT 1 FROM '.$db->prefix.'topics WHERE id IN('.$topics.') AND forum_id='.$fid) or error('Unable to check topics', __FILE__, __LINE__, $db->error());
+
+		if ($db->num_rows($result) != substr_count($topics, ',') + 1)
+			message($lang_common['Bad request']);
+
+		// Delete the topics and any redirect topics
+		$db->query('DELETE FROM '.$db->prefix.'topics WHERE id IN('.$topics.') OR moved_to IN('.$topics.')') or error('Unable to delete topic', __FILE__, __LINE__, $db->error());
+
+		// Delete any subscriptions
+		$db->query('DELETE FROM '.$db->prefix.'topic_subscriptions WHERE topic_id IN('.$topics.')') or error('Unable to delete subscriptions', __FILE__, __LINE__, $db->error());
+
+		// Create a list of the post IDs in this topic and then strip the search index
+		$result = $db->query('SELECT id FROM '.$db->prefix.'posts WHERE topic_id IN('.$topics.')') or error('Unable to fetch posts', __FILE__, __LINE__, $db->error());
+
+		$post_ids = '';
+		while ($row = $db->fetch_row($result))
+			$post_ids .= ($post_ids != '') ? ','.$row[0] : $row[0];
+
+		// We have to check that we actually have a list of post IDs since we could be deleting just a redirect topic
+		if ($post_ids != '')
+			strip_search_index($post_ids);
+
+		// Delete posts
+		$db->query('DELETE FROM '.$db->prefix.'posts WHERE topic_id IN('.$topics.')') or error('Unable to delete posts', __FILE__, __LINE__, $db->error());
+
+		update_forum($fid);
+
+		redirect('viewforum.php?id='.$fid, $lang_misc['Delete topics redirect']);
+	}
+
+
+	$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang_misc['Moderate']);
+	define('PUN_ACTIVE_PAGE', 'index');
+	require PUN_ROOT.'header.php';
+
+?>
+<div class="blockform">
+	<h2><span><?php echo $lang_misc['Delete topics'] ?></span></h2>
+	<div class="box">
+		<form method="post" action="moderate.php?fid=<?php echo $fid ?>">
+			<input type="hidden" name="topics" value="<?php echo implode(',', array_map('intval', array_keys($topics))) ?>" />
+			<div class="inform">
+				<fieldset>
+					<legend><?php echo $lang_misc['Confirm delete legend'] ?></legend>
+					<div class="infldset">
+						<p><?php echo $lang_misc['Delete topics comply'] ?></p>
+					</div>
+				</fieldset>
+			</div>
+			<p class="buttons"><input type="submit" name="delete_topics_comply" value="<?php echo $lang_misc['Delete'] ?>" /><a href="javascript:history.go(-1)"><?php echo $lang_common['Go back'] ?></a></p>
+		</form>
+	</div>
+</div>
+<?php
+
+	require PUN_ROOT.'footer.php';
+}
+
+
+// Open or close one or more topics
+else if (isset($_REQUEST['open']) || isset($_REQUEST['close']))
+{
+	$action = (isset($_REQUEST['open'])) ? 0 : 1;
+
+	// There could be an array of topic IDs in $_POST
+	if (isset($_POST['open']) || isset($_POST['close']))
+	{
+		confirm_referrer('moderate.php');
+
+		$topics = isset($_POST['topics']) ? @array_map('intval', @array_keys($_POST['topics'])) : array();
+		if (empty($topics))
+			message($lang_misc['No topics selected']);
+
+		$db->query('UPDATE '.$db->prefix.'topics SET closed='.$action.' WHERE id IN('.implode(',', $topics).') AND forum_id='.$fid) or error('Unable to close topics', __FILE__, __LINE__, $db->error());
+
+		$redirect_msg = ($action) ? $lang_misc['Close topics redirect'] : $lang_misc['Open topics redirect'];
+		redirect('moderate.php?fid='.$fid, $redirect_msg);
+	}
+	// Or just one in $_GET
+	else
+	{
+		confirm_referrer('viewtopic.php');
+
+		$topic_id = ($action) ? intval($_GET['close']) : intval($_GET['open']);
+		if ($topic_id < 1)
+			message($lang_common['Bad request']);
+
+		$db->query('UPDATE '.$db->prefix.'topics SET closed='.$action.' WHERE id='.$topic_id.' AND forum_id='.$fid) or error('Unable to close topic', __FILE__, __LINE__, $db->error());
+
+		$redirect_msg = ($action) ? $lang_misc['Close topic redirect'] : $lang_misc['Open topic redirect'];
+		redirect('viewtopic.php?id='.$topic_id, $redirect_msg);
+	}
+}
+
+
+// Stick a topic
+else if (isset($_GET['stick']))
+{
+	confirm_referrer('viewtopic.php');
+
+	$stick = intval($_GET['stick']);
+	if ($stick < 1)
+		message($lang_common['Bad request']);
+
+	$db->query('UPDATE '.$db->prefix.'topics SET sticky=\'1\' WHERE id='.$stick.' AND forum_id='.$fid) or error('Unable to stick topic', __FILE__, __LINE__, $db->error());
+
+	redirect('viewtopic.php?id='.$stick, $lang_misc['Stick topic redirect']);
+}
+
+
+// Unstick a topic
+else if (isset($_GET['unstick']))
+{
+	confirm_referrer('viewtopic.php');
+
+	$unstick = intval($_GET['unstick']);
+	if ($unstick < 1)
+		message($lang_common['Bad request']);
+
+	$db->query('UPDATE '.$db->prefix.'topics SET sticky=\'0\' WHERE id='.$unstick.' AND forum_id='.$fid) or error('Unable to unstick topic', __FILE__, __LINE__, $db->error());
+
+	redirect('viewtopic.php?id='.$unstick, $lang_misc['Unstick topic redirect']);
+}
+
+
+// No specific forum moderation action was specified in the query string, so we'll display the moderator forum
+
+// Load the viewforum.php language file
+require PUN_ROOT.'lang/'.$pun_user['language'].'/forum.php';
+
+// Fetch some info about the forum
+$result = $db->query('SELECT f.forum_name, f.redirect_url, f.num_topics, f.sort_by FROM '.$db->prefix.'forums AS f 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.id='.$fid) or error('Unable to fetch forum info', __FILE__, __LINE__, $db->error());
+if (!$db->num_rows($result))
+	message($lang_common['Bad request'], false, '404 Not Found');
+
+$cur_forum = $db->fetch_assoc($result);
+
+// Is this a redirect forum? In that case, abort!
+if ($cur_forum['redirect_url'] != '')
+	message($lang_common['Bad request']);
+
+switch ($cur_forum['sort_by'])
+{
+	case 0:
+		$sort_by = 'last_post DESC';
+		break;
+	case 1:
+		$sort_by = 'posted DESC';
+		break;
+	case 2:
+		$sort_by = 'subject ASC';
+		break;
+	default:
+		$sort_by = 'last_post DESC';
+		break;
+}
+
+// Determine the topic offset (based on $_GET['p'])
+$num_pages = ceil($cur_forum['num_topics'] / $pun_user['disp_topics']);
+
+$p = (!isset($_GET['p']) || $_GET['p'] <= 1 || $_GET['p'] > $num_pages) ? 1 : intval($_GET['p']);
+$start_from = $pun_user['disp_topics'] * ($p - 1);
+
+// Generate paging links
+$paging_links = '<span class="pages-label">'.$lang_common['Pages'].' </span>'.paginate($num_pages, $p, 'moderate.php?fid='.$fid);
+
+$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), pun_htmlspecialchars($cur_forum['forum_name']));
+define('PUN_ACTIVE_PAGE', 'index');
+require PUN_ROOT.'header.php';
+
+?>
+<div class="linkst">
+	<div class="inbox crumbsplus">
+		<ul class="crumbs">
+			<li><a href="index.php"><?php echo $lang_common['Index'] ?></a></li>
+			<li><span>»&#160;</span><a href="viewforum.php?id=<?php echo $fid ?>"><?php echo pun_htmlspecialchars($cur_forum['forum_name']) ?></a></li>
+			<li><span>»&#160;</span><strong><?php echo $lang_misc['Moderate'] ?></strong></li>
+		</ul>
+		<div class="pagepost">
+			<p class="pagelink conl"><?php echo $paging_links ?></p>
+		</div>
+		<div class="clearer"></div>
+	</div>
+</div>
+
+<form method="post" action="moderate.php?fid=<?php echo $fid ?>">
+<div id="vf" class="blocktable">
+	<h2><span><?php echo pun_htmlspecialchars($cur_forum['forum_name']) ?></span></h2>
+	<div class="box">
+		<div class="inbox">
+			<table cellspacing="0">
+			<thead>
+				<tr>
+					<th class="tcl" scope="col"><?php echo $lang_common['Topic'] ?></th>
+					<th class="tc2" scope="col"><?php echo $lang_common['Replies'] ?></th>
+<?php if ($pun_config['o_topic_views'] == '1'): ?>					<th class="tc3" scope="col"><?php echo $lang_forum['Views'] ?></th>
+<?php endif; ?>					<th class="tcr"><?php echo $lang_common['Last post'] ?></th>
+					<th class="tcmod" scope="col"><?php echo $lang_misc['Select'] ?></th>
+				</tr>
+			</thead>
+			<tbody>
+<?php
+
+
+// Retrieve a list of topic IDs, LIMIT is (really) expensive so we only fetch the IDs here then later fetch the remaining data
+$result = $db->query('SELECT id FROM '.$db->prefix.'topics WHERE forum_id='.$fid.' ORDER BY sticky DESC, '.$sort_by.', id DESC LIMIT '.$start_from.', '.$pun_user['disp_topics']) or error('Unable to fetch topic IDs', __FILE__, __LINE__, $db->error());
+
+// If there are topics in this forum
+if ($db->num_rows($result))
+{
+	$topic_ids = array();
+	for ($i = 0;$cur_topic_id = $db->result($result, $i);$i++)
+		$topic_ids[] = $cur_topic_id;
+
+	// Select topics
+	$result = $db->query('SELECT id, poster, subject, posted, last_post, last_post_id, last_poster, num_views, num_replies, closed, sticky, moved_to FROM '.$db->prefix.'topics WHERE id IN('.implode(',', $topic_ids).') ORDER BY sticky DESC, '.$sort_by.', id DESC') or error('Unable to fetch topic list for forum', __FILE__, __LINE__, $db->error());
+
+	$button_status = '';
+	$topic_count = 0;
+	while ($cur_topic = $db->fetch_assoc($result))
+	{
+
+		++$topic_count;
+		$status_text = array();
+		$item_status = ($topic_count % 2 == 0) ? 'roweven' : 'rowodd';
+		$icon_type = 'icon';
+
+		if (is_null($cur_topic['moved_to']))
+		{
+			$last_post = '<a href="viewtopic.php?pid='.$cur_topic['last_post_id'].'#p'.$cur_topic['last_post_id'].'">'.format_time($cur_topic['last_post']).'</a> <span class="byuser">'.$lang_common['by'].' '.pun_htmlspecialchars($cur_topic['last_poster']).'</span>';
+			$ghost_topic = false;
+		}
+		else
+		{
+			$last_post = '- - -';
+			$ghost_topic = true;
+		}
+
+		if ($pun_config['o_censoring'] == '1')
+			$cur_topic['subject'] = censor_words($cur_topic['subject']);
+
+		if ($cur_topic['sticky'] == '1')
+		{
+			$item_status .= ' isticky';
+			$status_text[] = '<span class="stickytext">'.$lang_forum['Sticky'].'</span>';
+		}
+
+		if ($cur_topic['moved_to'] != 0)
+		{
+			$subject = '<a href="viewtopic.php?id='.$cur_topic['moved_to'].'">'.pun_htmlspecialchars($cur_topic['subject']).'</a> <span class="byuser">'.$lang_common['by'].' '.pun_htmlspecialchars($cur_topic['poster']).'</span>';
+			$status_text[] = '<span class="movedtext">'.$lang_forum['Moved'].'</span>';
+			$item_status .= ' imoved';
+		}
+		else if ($cur_topic['closed'] == '0')
+			$subject = '<a href="viewtopic.php?id='.$cur_topic['id'].'">'.pun_htmlspecialchars($cur_topic['subject']).'</a> <span class="byuser">'.$lang_common['by'].' '.pun_htmlspecialchars($cur_topic['poster']).'</span>';
+		else
+		{
+			$subject = '<a href="viewtopic.php?id='.$cur_topic['id'].'">'.pun_htmlspecialchars($cur_topic['subject']).'</a> <span class="byuser">'.$lang_common['by'].' '.pun_htmlspecialchars($cur_topic['poster']).'</span>';
+			$status_text[] = '<span class="closedtext">'.$lang_forum['Closed'].'</span>';
+			$item_status .= ' iclosed';
+		}
+
+		if (!$ghost_topic && $cur_topic['last_post'] > $pun_user['last_visit'] && (!isset($tracked_topics['topics'][$cur_topic['id']]) || $tracked_topics['topics'][$cur_topic['id']] < $cur_topic['last_post']) && (!isset($tracked_topics['forums'][$fid]) || $tracked_topics['forums'][$fid] < $cur_topic['last_post']))
+		{
+			$item_status .= ' inew';
+			$icon_type = 'icon icon-new';
+			$subject = '<strong>'.$subject.'</strong>';
+			$subject_new_posts = '<span class="newtext">[ <a href="viewtopic.php?id='.$cur_topic['id'].'&amp;action=new" title="'.$lang_common['New posts info'].'">'.$lang_common['New posts'].'</a> ]</span>';
+		}
+		else
+			$subject_new_posts = null;
+
+		// Insert the status text before the subject
+		$subject = implode(' ', $status_text).' '.$subject;
+
+		$num_pages_topic = ceil(($cur_topic['num_replies'] + 1) / $pun_user['disp_posts']);
+
+		if ($num_pages_topic > 1)
+			$subject_multipage = '<span class="pagestext">[ '.paginate($num_pages_topic, -1, 'viewtopic.php?id='.$cur_topic['id']).' ]</span>';
+		else
+			$subject_multipage = null;
+
+		// Should we show the "New posts" and/or the multipage links?
+		if (!empty($subject_new_posts) || !empty($subject_multipage))
+		{
+			$subject .= !empty($subject_new_posts) ? ' '.$subject_new_posts : '';
+			$subject .= !empty($subject_multipage) ? ' '.$subject_multipage : '';
+		}
+
+?>
+				<tr class="<?php echo $item_status ?>">
+					<td class="tcl">
+						<div class="<?php echo $icon_type ?>"><div class="nosize"><?php echo forum_number_format($topic_count + $start_from) ?></div></div>
+						<div class="tclcon">
+							<div>
+								<?php echo $subject."\n" ?>
+							</div>
+						</div>
+					</td>
+					<td class="tc2"><?php echo (!$ghost_topic) ? forum_number_format($cur_topic['num_replies']) : '-' ?></td>
+<?php if ($pun_config['o_topic_views'] == '1'): ?>					<td class="tc3"><?php echo (!$ghost_topic) ? forum_number_format($cur_topic['num_views']) : '-' ?></td>
+<?php endif; ?>					<td class="tcr"><?php echo $last_post ?></td>
+					<td class="tcmod"><input type="checkbox" name="topics[<?php echo $cur_topic['id'] ?>]" value="1" /></td>
+				</tr>
+<?php
+
+	}
+}
+else
+{
+	$colspan = ($pun_config['o_topic_views'] == '1') ? 5 : 4;
+	$button_status = ' disabled="disabled"';
+	echo "\t\t\t\t\t".'<tr><td class="tcl" colspan="'.$colspan.'">'.$lang_forum['Empty forum'].'</td></tr>'."\n";
+}
+
+?>
+			</tbody>
+			</table>
+		</div>
+	</div>
+</div>
+
+<div class="linksb">
+	<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>»&#160;</span><a href="viewforum.php?id=<?php echo $fid ?>"><?php echo pun_htmlspecialchars($cur_forum['forum_name']) ?></a></li>
+			<li><span>»&#160;</span><strong><?php echo $lang_misc['Moderate'] ?></strong></li>
+		</ul>
+		<div class="clearer"></div>
+	</div>
+</div>
+</form>
+<?php
+
+require PUN_ROOT.'footer.php';
diff --git a/plugins/index.html b/plugins/index.html
new file mode 100644
index 0000000..89337b2
--- /dev/null
+++ b/plugins/index.html
@@ -0,0 +1 @@
+<html><head><title>.</title></head><body>.</body></html>
diff --git a/post.php b/post.php
new file mode 100644
index 0000000..dc750e6
--- /dev/null
+++ b/post.php
@@ -0,0 +1,752 @@
+<?php
+
+/**
+ * Copyright (C) 2008-2012 FluxBB
+ * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
+ * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
+ */
+
+define('PUN_ROOT', dirname(__FILE__).'/');
+require PUN_ROOT.'include/common.php';
+
+
+if ($pun_user['g_read_board'] == '0')
+	message($lang_common['No view'], false, '403 Forbidden');
+
+
+$tid = isset($_GET['tid']) ? intval($_GET['tid']) : 0;
+$fid = isset($_GET['fid']) ? intval($_GET['fid']) : 0;
+if ($tid < 1 && $fid < 1 || $tid > 0 && $fid > 0)
+	message($lang_common['Bad request'], false, '404 Not Found');
+
+// Fetch some info about the topic and/or the forum
+if ($tid)
+	$result = $db->query('SELECT f.id, f.forum_name, f.moderators, f.redirect_url, fp.post_replies, fp.post_topics, t.subject, t.closed, s.user_id AS is_subscribed FROM '.$db->prefix.'topics AS t INNER JOIN '.$db->prefix.'forums AS f ON f.id=t.forum_id LEFT JOIN '.$db->prefix.'forum_perms AS fp ON (fp.forum_id=f.id AND fp.group_id='.$pun_user['g_id'].') LEFT JOIN '.$db->prefix.'topic_subscriptions AS s ON (t.id=s.topic_id AND s.user_id='.$pun_user['id'].') WHERE (fp.read_forum IS NULL OR fp.read_forum=1) AND t.id='.$tid) or error('Unable to fetch forum info', __FILE__, __LINE__, $db->error());
+else
+	$result = $db->query('SELECT f.id, f.forum_name, f.moderators, f.redirect_url, fp.post_replies, fp.post_topics FROM '.$db->prefix.'forums AS f 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.id='.$fid) or error('Unable to fetch forum info', __FILE__, __LINE__, $db->error());
+
+if (!$db->num_rows($result))
+	message($lang_common['Bad request'], false, '404 Not Found');
+
+$cur_posting = $db->fetch_assoc($result);
+$is_subscribed = $tid && $cur_posting['is_subscribed'];
+
+// Is someone trying to post into a redirect forum?
+if ($cur_posting['redirect_url'] != '')
+	message($lang_common['Bad request']);
+
+// Sort out who the moderators are and if we are currently a moderator (or an admin)
+$mods_array = ($cur_posting['moderators'] != '') ? unserialize($cur_posting['moderators']) : array();
+$is_admmod = ($pun_user['g_id'] == PUN_ADMIN || ($pun_user['g_moderator'] == '1' && array_key_exists($pun_user['username'], $mods_array))) ? true : false;
+
+if ($tid && $pun_config['o_censoring'] == '1')
+	$cur_posting['subject'] = censor_words($cur_posting['subject']);
+
+// Do we have permission to post?
+if ((($tid && (($cur_posting['post_replies'] == '' && $pun_user['g_post_replies'] == '0') || $cur_posting['post_replies'] == '0')) ||
+	($fid && (($cur_posting['post_topics'] == '' && $pun_user['g_post_topics'] == '0') || $cur_posting['post_topics'] == '0')) ||
+	(isset($cur_posting['closed']) && $cur_posting['closed'] == '1')) &&
+	!$is_admmod)
+	message($lang_common['No permission'], false, '403 Forbidden');
+
+// Load the post.php language file
+require PUN_ROOT.'lang/'.$pun_user['language'].'/post.php';
+
+// Start with a clean slate
+$errors = array();
+
+
+// Did someone just hit "Submit" or "Preview"?
+if (isset($_POST['form_sent']))
+{
+	// Flood protection
+	if (!isset($_POST['preview']) && $pun_user['last_post'] != '' && (time() - $pun_user['last_post']) < $pun_user['g_post_flood'])
+		$errors[] = $lang_post['Flood start'].' '.$pun_user['g_post_flood'].' '.$lang_post['flood end'];
+
+	// Make sure they got here from the site
+	confirm_referrer(array('post.php', 'viewtopic.php'));
+
+	// If it's a new topic
+	if ($fid)
+	{
+		$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'])
+			$errors[] = $lang_post['All caps subject'];
+	}
+
+	// If the user is logged in we get the username and email from $pun_user
+	if (!$pun_user['is_guest'])
+	{
+		$username = $pun_user['username'];
+		$email = $pun_user['email'];
+	}
+	// Otherwise it should be in $_POST
+	else
+	{
+		$username = pun_trim($_POST['req_username']);
+		$email = strtolower(pun_trim(($pun_config['p_force_guest_email'] == '1') ? $_POST['req_email'] : $_POST['email']));
+		$banned_email = false;
+
+		// Load the register.php/prof_reg.php language files
+		require PUN_ROOT.'lang/'.$pun_user['language'].'/prof_reg.php';
+		require PUN_ROOT.'lang/'.$pun_user['language'].'/register.php';
+
+		// It's a guest, so we have to validate the username
+		check_username($username);
+
+		if ($pun_config['p_force_guest_email'] == '1' || $email != '')
+		{
+			require PUN_ROOT.'include/email.php';
+			if (!is_valid_email($email))
+				$errors[] = $lang_common['Invalid email'];
+
+			// Check if it's a banned email address
+			// we should only check guests because members addresses are already verified
+			if ($pun_user['is_guest'] && is_banned_email($email))
+			{
+				if ($pun_config['p_allow_banned_email'] == '0')
+					$errors[] = $lang_prof_reg['Banned email'];
+
+				$banned_email = true; // Used later when we send an alert email
+			}
+		}
+	}
+
+	// Clean up message from POST
+	$orig_message = $message = pun_linebreaks(pun_trim($_POST['req_message']));
+
+	// Here we use strlen() not pun_strlen() as we want to limit the post to PUN_MAX_POSTSIZE bytes, not characters
+	if (strlen($message) > PUN_MAX_POSTSIZE)
+		$errors[] = sprintf($lang_post['Too long message'], forum_number_format(PUN_MAX_POSTSIZE));
+	else if ($pun_config['p_message_all_caps'] == '0' && is_all_uppercase($message) && !$pun_user['is_admmod'])
+		$errors[] = $lang_post['All caps message'];
+
+	// Validate BBCode syntax
+	if ($pun_config['p_message_bbcode'] == '1')
+	{
+		require PUN_ROOT.'include/parser.php';
+		$message = preparse_bbcode($message, $errors);
+	}
+
+	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';
+	$stick_topic = isset($_POST['stick_topic']) && $is_admmod ? '1' : '0';
+	
+	// Replace four-byte characters (MySQL cannot handle them)
+	$message = strip_bad_multibyte_chars($message);
+
+	$now = time();
+
+	// Did everything go according to plan?
+	if (empty($errors) && !isset($_POST['preview']))
+	{
+		require PUN_ROOT.'include/search_idx.php';
+
+		// If it's a reply
+		if ($tid)
+		{
+			if (!$pun_user['is_guest'])
+			{
+				$new_tid = $tid;
+
+				// Insert the new post
+				$db->query('INSERT INTO '.$db->prefix.'posts (poster, poster_id, poster_ip, message, hide_smilies, posted, topic_id) VALUES(\''.$db->escape($username).'\', '.$pun_user['id'].', \''.$db->escape(get_remote_address()).'\', \''.$db->escape($message).'\', '.$hide_smilies.', '.$now.', '.$tid.')') or error('Unable to create post', __FILE__, __LINE__, $db->error());
+				$new_pid = $db->insert_id();
+
+				// To subscribe or not to subscribe, that ...
+				if ($pun_config['o_topic_subscriptions'] == '1')
+				{
+					if ($subscribe && !$is_subscribed)
+						$db->query('INSERT INTO '.$db->prefix.'topic_subscriptions (user_id, topic_id) VALUES('.$pun_user['id'].' ,'.$tid.')') or error('Unable to add subscription', __FILE__, __LINE__, $db->error());
+					else if (!$subscribe && $is_subscribed)
+						$db->query('DELETE FROM '.$db->prefix.'topic_subscriptions WHERE user_id='.$pun_user['id'].' AND topic_id='.$tid) or error('Unable to remove subscription', __FILE__, __LINE__, $db->error());
+				}
+			}
+			else
+			{
+				// It's a guest. Insert the new post
+				$email_sql = ($pun_config['p_force_guest_email'] == '1' || $email != '') ? '\''.$db->escape($email).'\'' : 'NULL';
+				$db->query('INSERT INTO '.$db->prefix.'posts (poster, poster_ip, poster_email, message, hide_smilies, posted, topic_id) VALUES(\''.$db->escape($username).'\', \''.$db->escape(get_remote_address()).'\', '.$email_sql.', \''.$db->escape($message).'\', '.$hide_smilies.', '.$now.', '.$tid.')') or error('Unable to create post', __FILE__, __LINE__, $db->error());
+				$new_pid = $db->insert_id();
+			}
+
+			// Update topic
+			$db->query('UPDATE '.$db->prefix.'topics SET num_replies=num_replies+1, last_post='.$now.', last_post_id='.$new_pid.', last_poster=\''.$db->escape($username).'\' WHERE id='.$tid) or error('Unable to update topic', __FILE__, __LINE__, $db->error());
+
+			update_search_index('post', $new_pid, $message);
+
+			update_forum($cur_posting['id']);
+
+			// Should we send out notifications?
+			if ($pun_config['o_topic_subscriptions'] == '1')
+			{
+				// Get the post time for the previous post in this topic
+				$result = $db->query('SELECT posted FROM '.$db->prefix.'posts WHERE topic_id='.$tid.' ORDER BY id DESC LIMIT 1, 1') or error('Unable to fetch post info', __FILE__, __LINE__, $db->error());
+				$previous_post_time = $db->result($result);
+
+				// Get any subscribed users that should be notified (banned users are excluded)
+				$result = $db->query('SELECT u.id, u.email, u.notify_with_post, u.language FROM '.$db->prefix.'users AS u INNER JOIN '.$db->prefix.'topic_subscriptions AS s ON u.id=s.user_id LEFT JOIN '.$db->prefix.'forum_perms AS fp ON (fp.forum_id='.$cur_posting['id'].' AND fp.group_id=u.group_id) LEFT JOIN '.$db->prefix.'online AS o ON u.id=o.user_id LEFT JOIN '.$db->prefix.'bans AS b ON u.username=b.username WHERE b.username IS NULL AND COALESCE(o.logged, u.last_visit)>'.$previous_post_time.' AND (fp.read_forum IS NULL OR fp.read_forum=1) AND s.topic_id='.$tid.' AND u.id!='.$pun_user['id']) or error('Unable to fetch subscription info', __FILE__, __LINE__, $db->error());
+				if ($db->num_rows($result))
+				{
+					require_once PUN_ROOT.'include/email.php';
+
+					$notification_emails = array();
+
+					if ($pun_config['o_censoring'] == '1')
+						$cleaned_message = bbcode2email($censored_message, -1);
+					else
+						$cleaned_message = bbcode2email($message, -1);
+
+					// Loop through subscribed users and send emails
+					while ($cur_subscriber = $db->fetch_assoc($result))
+					{
+						// Is the subscription email for $cur_subscriber['language'] cached or not?
+						if (!isset($notification_emails[$cur_subscriber['language']]))
+						{
+							if (file_exists(PUN_ROOT.'lang/'.$cur_subscriber['language'].'/mail_templates/new_reply.tpl'))
+							{
+								// Load the "new reply" template
+								$mail_tpl = trim(file_get_contents(PUN_ROOT.'lang/'.$cur_subscriber['language'].'/mail_templates/new_reply.tpl'));
+
+								// Load the "new reply full" template (with post included)
+								$mail_tpl_full = trim(file_get_contents(PUN_ROOT.'lang/'.$cur_subscriber['language'].'/mail_templates/new_reply_full.tpl'));
+
+								// The first row contains the subject (it also starts with "Subject:")
+								$first_crlf = strpos($mail_tpl, "\n");
+								$mail_subject = trim(substr($mail_tpl, 8, $first_crlf-8));
+								$mail_message = trim(substr($mail_tpl, $first_crlf));
+
+								$first_crlf = strpos($mail_tpl_full, "\n");
+								$mail_subject_full = trim(substr($mail_tpl_full, 8, $first_crlf-8));
+								$mail_message_full = trim(substr($mail_tpl_full, $first_crlf));
+
+								$mail_subject = str_replace('<topic_subject>', $cur_posting['subject'], $mail_subject);
+								$mail_message = str_replace('<topic_subject>', $cur_posting['subject'], $mail_message);
+								$mail_message = str_replace('<replier>', $username, $mail_message);
+								$mail_message = str_replace('<post_url>', get_base_url().'/viewtopic.php?pid='.$new_pid.'#p'.$new_pid, $mail_message);
+								$mail_message = str_replace('<unsubscribe_url>', get_base_url().'/misc.php?action=unsubscribe&tid='.$tid, $mail_message);
+								$mail_message = str_replace('<board_mailer>', $pun_config['o_board_title'], $mail_message);
+
+								$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>', $cleaned_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'], $mail_message_full);
+
+								$notification_emails[$cur_subscriber['language']][0] = $mail_subject;
+								$notification_emails[$cur_subscriber['language']][1] = $mail_message;
+								$notification_emails[$cur_subscriber['language']][2] = $mail_subject_full;
+								$notification_emails[$cur_subscriber['language']][3] = $mail_message_full;
+
+								$mail_subject = $mail_message = $mail_subject_full = $mail_message_full = null;
+							}
+						}
+
+						// We have to double check here because the templates could be missing
+						if (isset($notification_emails[$cur_subscriber['language']]))
+						{
+							if ($cur_subscriber['notify_with_post'] == '0')
+								pun_mail($cur_subscriber['email'], $notification_emails[$cur_subscriber['language']][0], $notification_emails[$cur_subscriber['language']][1]);
+							else
+								pun_mail($cur_subscriber['email'], $notification_emails[$cur_subscriber['language']][2], $notification_emails[$cur_subscriber['language']][3]);
+						}
+					}
+
+					unset($cleaned_message);
+				}
+			}
+		}
+		// If it's a new topic
+		else if ($fid)
+		{
+			// Create the topic
+			$db->query('INSERT INTO '.$db->prefix.'topics (poster, subject, posted, last_post, last_poster, sticky, forum_id) VALUES(\''.$db->escape($username).'\', \''.$db->escape($subject).'\', '.$now.', '.$now.', \''.$db->escape($username).'\', '.$stick_topic.', '.$fid.')') or error('Unable to create topic', __FILE__, __LINE__, $db->error());
+			$new_tid = $db->insert_id();
+
+			if (!$pun_user['is_guest'])
+			{
+				// To subscribe or not to subscribe, that ...
+				if ($pun_config['o_topic_subscriptions'] == '1' && $subscribe)
+					$db->query('INSERT INTO '.$db->prefix.'topic_subscriptions (user_id, topic_id) VALUES('.$pun_user['id'].' ,'.$new_tid.')') or error('Unable to add subscription', __FILE__, __LINE__, $db->error());
+
+				// Create the post ("topic post")
+				$db->query('INSERT INTO '.$db->prefix.'posts (poster, poster_id, poster_ip, message, hide_smilies, posted, topic_id) VALUES(\''.$db->escape($username).'\', '.$pun_user['id'].', \''.$db->escape(get_remote_address()).'\', \''.$db->escape($message).'\', '.$hide_smilies.', '.$now.', '.$new_tid.')') or error('Unable to create post', __FILE__, __LINE__, $db->error());
+			}
+			else
+			{
+				// Create the post ("topic post")
+				$email_sql = ($pun_config['p_force_guest_email'] == '1' || $email != '') ? '\''.$db->escape($email).'\'' : 'NULL';
+				$db->query('INSERT INTO '.$db->prefix.'posts (poster, poster_ip, poster_email, message, hide_smilies, posted, topic_id) VALUES(\''.$db->escape($username).'\', \''.$db->escape(get_remote_address()).'\', '.$email_sql.', \''.$db->escape($message).'\', '.$hide_smilies.', '.$now.', '.$new_tid.')') or error('Unable to create post', __FILE__, __LINE__, $db->error());
+			}
+			$new_pid = $db->insert_id();
+
+			// Update the topic with last_post_id
+			$db->query('UPDATE '.$db->prefix.'topics SET last_post_id='.$new_pid.', first_post_id='.$new_pid.' WHERE id='.$new_tid) or error('Unable to update topic', __FILE__, __LINE__, $db->error());
+
+			update_search_index('post', $new_pid, $message, $subject);
+
+			update_forum($fid);
+
+			// Should we send out notifications?
+			if ($pun_config['o_forum_subscriptions'] == '1')
+			{
+				// Get any subscribed users that should be notified (banned users are excluded)
+				$result = $db->query('SELECT u.id, u.email, u.notify_with_post, u.language FROM '.$db->prefix.'users AS u INNER JOIN '.$db->prefix.'forum_subscriptions AS s ON u.id=s.user_id LEFT JOIN '.$db->prefix.'forum_perms AS fp ON (fp.forum_id='.$cur_posting['id'].' AND fp.group_id=u.group_id) LEFT JOIN '.$db->prefix.'bans AS b ON u.username=b.username WHERE b.username IS NULL AND (fp.read_forum IS NULL OR fp.read_forum=1) AND s.forum_id='.$cur_posting['id'].' AND u.id!='.$pun_user['id']) or error('Unable to fetch subscription info', __FILE__, __LINE__, $db->error());
+				if ($db->num_rows($result))
+				{
+					require_once PUN_ROOT.'include/email.php';
+
+					$notification_emails = array();
+
+					if ($pun_config['o_censoring'] == '1')
+						$cleaned_message = bbcode2email($censored_message, -1);
+					else
+						$cleaned_message = bbcode2email($message, -1);
+
+					// Loop through subscribed users and send emails
+					while ($cur_subscriber = $db->fetch_assoc($result))
+					{
+						// Is the subscription email for $cur_subscriber['language'] cached or not?
+						if (!isset($notification_emails[$cur_subscriber['language']]))
+						{
+							if (file_exists(PUN_ROOT.'lang/'.$cur_subscriber['language'].'/mail_templates/new_topic.tpl'))
+							{
+								// Load the "new topic" template
+								$mail_tpl = trim(file_get_contents(PUN_ROOT.'lang/'.$cur_subscriber['language'].'/mail_templates/new_topic.tpl'));
+
+								// Load the "new topic full" template (with post included)
+								$mail_tpl_full = trim(file_get_contents(PUN_ROOT.'lang/'.$cur_subscriber['language'].'/mail_templates/new_topic_full.tpl'));
+
+								// The first row contains the subject (it also starts with "Subject:")
+								$first_crlf = strpos($mail_tpl, "\n");
+								$mail_subject = trim(substr($mail_tpl, 8, $first_crlf-8));
+								$mail_message = trim(substr($mail_tpl, $first_crlf));
+
+								$first_crlf = strpos($mail_tpl_full, "\n");
+								$mail_subject_full = trim(substr($mail_tpl_full, 8, $first_crlf-8));
+								$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>', $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);
+								$mail_message = str_replace('<unsubscribe_url>', get_base_url().'/misc.php?action=unsubscribe&fid='.$cur_posting['id'], $mail_message);
+								$mail_message = str_replace('<board_mailer>', $pun_config['o_board_title'], $mail_message);
+
+								$mail_subject_full = str_replace('<forum_name>', $cur_posting['forum_name'], $mail_subject_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>', $cleaned_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'], $mail_message_full);
+
+								$notification_emails[$cur_subscriber['language']][0] = $mail_subject;
+								$notification_emails[$cur_subscriber['language']][1] = $mail_message;
+								$notification_emails[$cur_subscriber['language']][2] = $mail_subject_full;
+								$notification_emails[$cur_subscriber['language']][3] = $mail_message_full;
+
+								$mail_subject = $mail_message = $mail_subject_full = $mail_message_full = null;
+							}
+						}
+
+						// We have to double check here because the templates could be missing
+						if (isset($notification_emails[$cur_subscriber['language']]))
+						{
+							if ($cur_subscriber['notify_with_post'] == '0')
+								pun_mail($cur_subscriber['email'], $notification_emails[$cur_subscriber['language']][0], $notification_emails[$cur_subscriber['language']][1]);
+							else
+								pun_mail($cur_subscriber['email'], $notification_emails[$cur_subscriber['language']][2], $notification_emails[$cur_subscriber['language']][3]);
+						}
+					}
+
+					unset($cleaned_message);
+				}
+			}
+		}
+
+		// If we previously found out that the email was banned
+		if ($pun_user['is_guest'] && $banned_email && $pun_config['o_mailing_list'] != '')
+		{
+			// Load the "banned email post" template
+			$mail_tpl = trim(file_get_contents(PUN_ROOT.'lang/'.$pun_user['language'].'/mail_templates/banned_email_post.tpl'));
+
+			// The first row contains the subject
+			$first_crlf = strpos($mail_tpl, "\n");
+			$mail_subject = trim(substr($mail_tpl, 8, $first_crlf-8));
+			$mail_message = trim(substr($mail_tpl, $first_crlf));
+
+			$mail_message = str_replace('<username>', $username, $mail_message);
+			$mail_message = str_replace('<email>', $email, $mail_message);
+			$mail_message = str_replace('<post_url>', get_base_url().'/viewtopic.php?pid='.$new_pid.'#p'.$new_pid, $mail_message);
+			$mail_message = str_replace('<board_mailer>', $pun_config['o_board_title'], $mail_message);
+
+			pun_mail($pun_config['o_mailing_list'], $mail_subject, $mail_message);
+		}
+
+		// If the posting user is logged in, increment his/her post count
+		if (!$pun_user['is_guest'])
+		{
+			$db->query('UPDATE '.$db->prefix.'users SET num_posts=num_posts+1, last_post='.$now.' WHERE id='.$pun_user['id']) or error('Unable to update user', __FILE__, __LINE__, $db->error());
+
+			$tracked_topics = get_tracked_topics();
+			$tracked_topics['topics'][$new_tid] = time();
+			set_tracked_topics($tracked_topics);
+		}
+		else
+		{
+			$db->query('UPDATE '.$db->prefix.'online SET last_post='.$now.' WHERE ident=\''.$db->escape(get_remote_address()).'\'' ) or error('Unable to update user', __FILE__, __LINE__, $db->error());
+		}
+
+		redirect('viewtopic.php?pid='.$new_pid.'#p'.$new_pid, $lang_post['Post redirect']);
+	}
+}
+
+
+// If a topic ID was specified in the url (it's a reply)
+if ($tid)
+{
+	$action = $lang_post['Post a reply'];
+	$form = '<form id="post" method="post" action="post.php?action=post&amp;tid='.$tid.'" onsubmit="this.submit.disabled=true;if(process_form(this)){return true;}else{this.submit.disabled=false;return false;}">';
+
+	// If a quote ID was specified in the url
+	if (isset($_GET['qid']))
+	{
+		$qid = intval($_GET['qid']);
+		if ($qid < 1)
+			message($lang_common['Bad request'], false, '404 Not Found');
+
+		$result = $db->query('SELECT poster, message FROM '.$db->prefix.'posts WHERE id='.$qid.' AND topic_id='.$tid) or error('Unable to fetch quote info', __FILE__, __LINE__, $db->error());
+		if (!$db->num_rows($result))
+			message($lang_common['Bad request'], false, '404 Not Found');
+
+		list($q_poster, $q_message) = $db->fetch_row($result);
+
+		// If the message contains a code tag we have to split it up (text within [code][/code] shouldn't be touched)
+		if (strpos($q_message, '[code]') !== false && strpos($q_message, '[/code]') !== false)
+		{
+			list($inside, $outside) = split_text($q_message, '[code]', '[/code]');
+
+			$q_message = implode("\1", $outside);
+		}
+
+		// Remove [img] tags from quoted message
+		$q_message = preg_replace('%\[img(?:=(?:[^\[]*?))?\]((ht|f)tps?://)([^\s<"]*?)\[/img\]%U', '\1\3', $q_message);
+
+		// If we split up the message before we have to concatenate it together again (code tags)
+		if (isset($inside))
+		{
+			$outside = explode("\1", $q_message);
+			$q_message = '';
+
+			$num_tokens = count($outside);
+			for ($i = 0; $i < $num_tokens; ++$i)
+			{
+				$q_message .= $outside[$i];
+				if (isset($inside[$i]))
+					$q_message .= '[code]'.$inside[$i].'[/code]';
+			}
+
+			unset($inside);
+		}
+
+		if ($pun_config['o_censoring'] == '1')
+			$q_message = censor_words($q_message);
+
+		$q_message = pun_htmlspecialchars($q_message);
+
+		if ($pun_config['p_message_bbcode'] == '1')
+		{
+			// If username contains a square bracket, we add "" or '' around it (so we know when it starts and ends)
+			if (strpos($q_poster, '[') !== false || strpos($q_poster, ']') !== false)
+			{
+				if (strpos($q_poster, '\'') !== false)
+					$q_poster = '"'.$q_poster.'"';
+				else
+					$q_poster = '\''.$q_poster.'\'';
+			}
+			else
+			{
+				// Get the characters at the start and end of $q_poster
+				$ends = substr($q_poster, 0, 1).substr($q_poster, -1, 1);
+
+				// Deal with quoting "Username" or 'Username' (becomes '"Username"' or "'Username'")
+				if ($ends == '\'\'')
+					$q_poster = '"'.$q_poster.'"';
+				else if ($ends == '""')
+					$q_poster = '\''.$q_poster.'\'';
+			}
+
+			$quote = '[quote='.$q_poster.']'.$q_message.'[/quote]'."\n";
+		}
+		else
+			$quote = '> '.$q_poster.' '.$lang_common['wrote']."\n\n".'> '.$q_message."\n";
+	}
+}
+// If a forum ID was specified in the url (new topic)
+else if ($fid)
+{
+	$action = $lang_post['Post new topic'];
+	$form = '<form id="post" method="post" action="post.php?action=post&amp;fid='.$fid.'" onsubmit="return process_form(this)">';
+}
+else
+	message($lang_common['Bad request']);
+
+
+$page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $action);
+$required_fields = array('req_email' => $lang_common['Email'], 'req_subject' => $lang_common['Subject'], 'req_message' => $lang_common['Message']);
+$focus_element = array('post');
+
+if (!$pun_user['is_guest'])
+	$focus_element[] = ($fid) ? 'req_subject' : 'req_message';
+else
+{
+	$required_fields['req_username'] = $lang_post['Guest name'];
+	$focus_element[] = 'req_username';
+}
+
+define('PUN_ACTIVE_PAGE', 'index');
+require PUN_ROOT.'header.php';
+
+?>
+<div class="linkst">
+	<div class="inbox">
+		<ul class="crumbs">
+			<li><a href="index.php"><?php echo $lang_common['Index'] ?></a></li>
+			<li><span>»&#160;</span><a href="viewforum.php?id=<?php echo $cur_posting['id'] ?>"><?php echo pun_htmlspecialchars($cur_posting['forum_name']) ?></a></li>
+<?php if (isset($cur_posting['subject'])): ?>			<li><span>»&#160;</span><a href="viewtopic.php?id=<?php echo $tid ?>"><?php echo pun_htmlspecialchars($cur_posting['subject']) ?></a></li>
+<?php endif; ?>			<li><span>»&#160;</span><strong><?php echo $action ?></strong></li>
+		</ul>
+	</div>
+</div>
+
+<?php
+
+// If there are errors, we display them
+if (!empty($errors))
+{
+
+?>
+<div id="posterror" class="block">
+	<h2><span><?php echo $lang_post['Post errors'] ?></span></h2>
+	<div class="box">
+		<div class="inbox error-info">
+			<p><?php echo $lang_post['Post errors info'] ?></p>
+			<ul class="error-list">
+<?php
+
+	foreach ($errors as $cur_error)
+		echo "\t\t\t\t".'<li><strong>'.$cur_error.'</strong></li>'."\n";
+?>
+			</ul>
+		</div>
+	</div>
+</div>
+
+<?php
+
+}
+else if (isset($_POST['preview']))
+{
+	require_once PUN_ROOT.'include/parser.php';
+	$preview_message = parse_message($message, $hide_smilies);
+
+?>
+<div id="postpreview" class="blockpost">
+	<h2><span><?php echo $lang_post['Post preview'] ?></span></h2>
+	<div class="box">
+		<div class="inbox">
+			<div class="postbody">
+				<div class="postright">
+					<div class="postmsg">
+						<?php echo $preview_message."\n" ?>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</div>
+
+<?php
+
+}
+
+
+$cur_index = 1;
+
+?>
+<div id="postform" class="blockform">
+	<h2><span><?php echo $action ?></span></h2>
+	<div class="box">
+		<?php echo $form."\n" ?>
+			<div class="inform">
+				<fieldset>
+					<legend><?php echo $lang_common['Write message legend'] ?></legend>
+					<div class="infldset txtarea">
+						<input type="hidden" name="form_sent" value="1" />
+<?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
+
+}
+
+if ($fid): ?>
+						<label class="required"><strong><?php echo $lang_common['Subject'] ?> <span><?php echo $lang_common['Required'] ?></span></strong><br /><input class="longinput" type="text" name="req_subject" value="<?php if (isset($_POST['req_subject'])) echo pun_htmlspecialchars($subject); ?>" size="80" maxlength="70" tabindex="<?php echo $cur_index++ ?>" /><br /></label>
+<?php endif; ?>						<label class="required"><strong><?php echo $lang_common['Message'] ?> <span><?php echo $lang_common['Required'] ?></span></strong><br />
+						<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_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>
+<?php
+
+$checkboxes = array();
+if ($is_admmod)
+	$checkboxes[] = '<label><input type="checkbox" name="stick_topic" value="1" tabindex="'.($cur_index++).'"'.(isset($_POST['stick_topic']) ? ' checked="checked"' : '').' />'.$lang_common['Stick topic'].'<br /></label>';
+
+if (!$pun_user['is_guest'])
+{
+	if ($pun_config['o_smilies'] == '1')
+		$checkboxes[] = '<label><input type="checkbox" name="hide_smilies" value="1" tabindex="'.($cur_index++).'"'.(isset($_POST['hide_smilies']) ? ' checked="checked"' : '').' />'.$lang_post['Hide smilies'].'<br /></label>';
+
+	if ($pun_config['o_topic_subscriptions'] == '1')
+	{
+		$subscr_checked = false;
+
+		// If it's a preview
+		if (isset($_POST['preview']))
+			$subscr_checked = isset($_POST['subscribe']) ? true : false;
+		// If auto subscribed
+		else if ($pun_user['auto_notify'])
+			$subscr_checked = true;
+		// If already subscribed to the topic
+		else if ($is_subscribed)
+			$subscr_checked = true;
+
+		$checkboxes[] = '<label><input type="checkbox" name="subscribe" value="1" tabindex="'.($cur_index++).'"'.($subscr_checked ? ' checked="checked"' : '').' />'.($is_subscribed ? $lang_post['Stay subscribed'] : $lang_post['Subscribe']).'<br /></label>';
+	}
+}
+else if ($pun_config['o_smilies'] == '1')
+	$checkboxes[] = '<label><input type="checkbox" name="hide_smilies" value="1" tabindex="'.($cur_index++).'"'.(isset($_POST['hide_smilies']) ? ' checked="checked"' : '').' />'.$lang_post['Hide smilies'].'<br /></label>';
+
+if (!empty($checkboxes))
+{
+
+?>
+			</div>
+			<div class="inform">
+				<fieldset>
+					<legend><?php echo $lang_common['Options'] ?></legend>
+					<div class="infldset">
+						<div class="rbox">
+							<?php echo implode("\n\t\t\t\t\t\t\t", $checkboxes)."\n" ?>
+						</div>
+					</div>
+				</fieldset>
+<?php
+
+}
+
+?>
+			</div>
+			<p class="buttons"><input type="submit" name="submit" value="<?php echo $lang_common['Submit'] ?>" tabindex="<?php echo $cur_index++ ?>" accesskey="s" /> <input type="submit" name="preview" value="<?php echo $lang_post['Preview'] ?>" tabindex="<?php echo $cur_index++ ?>" accesskey="p" /> <a href="javascript:history.go(-1)"><?php echo $lang_common['Go back'] ?></a></p>
+		</form>
+	</div>
+</div>
+
+<?php
+
+// Check to see if the topic review is to be displayed
+if ($tid && $pun_config['o_topic_review'] != '0')
+{
+	require_once PUN_ROOT.'include/parser.php';
+
+	$result = $db->query('SELECT poster, message, hide_smilies, posted FROM '.$db->prefix.'posts WHERE topic_id='.$tid.' ORDER BY id DESC LIMIT '.$pun_config['o_topic_review']) or error('Unable to fetch topic review', __FILE__, __LINE__, $db->error());
+
+?>
+
+<div id="postreview">
+	<h2><span><?php echo $lang_post['Topic review'] ?></span></h2>
+<?php
+
+	// Set background switching on
+	$post_count = 0;
+
+	while ($cur_post = $db->fetch_assoc($result))
+	{
+		$post_count++;
+
+		$cur_post['message'] = parse_message($cur_post['message'], $cur_post['hide_smilies']);
+
+?>
+	<div class="blockpost">
+		<div class="box<?php echo ($post_count % 2 == 0) ? ' roweven' : ' rowodd' ?>">
+			<div class="inbox">
+				<div class="postbody">
+					<div class="postleft">
+						<dl>
