plugins/admin/UserManager.php
author Dan
Fri, 30 Apr 2010 22:15:03 -0400
changeset 1248 3914c9a95879
parent 1238 56b94bd46968
child 1267 31ff2e5351b0
permissions -rw-r--r--
Merged (accidental split)

<?php

/*
 * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
 * Copyright (C) 2006-2009 Dan Fuhry
 *
 * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
 */

function page_Admin_UserManager()
{
	global $db, $session, $paths, $template, $plugins; // Common objects
	global $lang;
	if ( $session->auth_level < USER_LEVEL_ADMIN || $session->user_level < USER_LEVEL_ADMIN )
	{
		$login_link = makeUrlNS('Special', 'Login/' . $paths->nslist['Special'] . 'Administration', 'level=' . USER_LEVEL_ADMIN, true);
		echo '<h3>' . $lang->get('adm_err_not_auth_title') . '</h3>';
		echo '<p>' . $lang->get('adm_err_not_auth_body', array( 'login_link' => $login_link )) . '</p>';
		return;
	}
	
	require_once(ENANO_ROOT . '/includes/math.php');
	require_once(ENANO_ROOT . '/includes/diffiehellman.php');
	
	$GLOBALS['dh_supported'] = $dh_supported;
	
	//die('<pre>' . htmlspecialchars(print_r($_POST, true)) . '</pre>');
	
	if ( isset($_POST['action']['save']) )
	{
		#
		# BEGIN VALIDATION
		#
		
		$errors = array();
		
		if ( defined('ENANO_DEMO_MODE') )
		{
			$errors[] = $lang->get('acpum_err_nosave_demo');
		}
		
		$user_id = intval($_POST['user_id']);
		if ( empty($user_id) || $user_id == 1 )
			$errors[] = 'Invalid user ID.';
		
		if ( isset($_POST['delete_account']) && count($errors) < 1 )
		{
			$q = $db->sql_query('DELETE FROM '.table_prefix."users_extra WHERE user_id=$user_id;");
			if ( !$q )
				$db->_die();
			$q = $db->sql_query('DELETE FROM '.table_prefix."users WHERE user_id=$user_id;");
			if ( !$q )
				$db->_die();
			$q = $db->sql_query('DELETE FROM '.table_prefix."session_keys WHERE user_id=$user_id;");
			if ( !$q )
				$db->_die();
			echo '<div class="info-box">' . $lang->get('acpum_msg_delete_success') . '</div>';
			
			// deleting own account?
			if ( $user_id === $session->user_id )
			{
				// cute little hack to boot them out of the admin panel
				echo '<script type="text/javascript">
					addOnloadHook(function()
					{
						setTimeout(function()
						{
							eraseCookie("sid");
							ENANO_SID = false;
							auth_level = USER_LEVEL_MEMBER;
							window.location = makeUrlNS("Special", "Login");
						}, 3000);
					});
				</script>';
			}
		}
		else
		{
			if ( $session->user_id == $user_id )
			{
				$username = $session->username;
				$password = false;
				$email = $session->email;
				$real_name = $session->real_name;
			}
			else
			{
				$username = $_POST['username'];
				if ( !preg_match('#^'.$session->valid_username.'$#', $username) )
					$errors[] = $lang->get('acpum_err_illegal_username');
				
				$password = false;
				if ( $_POST['changing_pw'] == 'yes' )
				{
					$password = $session->get_aes_post('new_password');
				}
				
				$email = $_POST['email'];
				if ( !preg_match('/^(?:[\w\d]+\.?)+@((?:(?:[\w\d]\-?)+\.)+\w{2,4}|localhost)$/', $email) )
					$errors[] = $lang->get('acpum_err_illegal_email');
				
				$real_name = $_POST['real_name'];
			}
			
			$signature = RenderMan::preprocess_text($_POST['signature'], true, false);
			
			$user_level = intval($_POST['user_level']);
			if ( $user_level < USER_LEVEL_MEMBER || $user_level > USER_LEVEL_ADMIN )
				$errors[] = 'Invalid user level';
			
			$user_rank = $_POST['user_rank'];
			if ( $user_rank !== 'NULL' )
			{
				$user_rank = intval($user_rank);
				if ( !$user_rank )
					$errors[] = 'Invalid user rank';
			}
			
			$imaddr_aim = htmlspecialchars($_POST['imaddr_aim']);
			$imaddr_msn = htmlspecialchars($_POST['imaddr_msn']);
			$imaddr_yahoo = htmlspecialchars($_POST['imaddr_yahoo']);
			$imaddr_xmpp = htmlspecialchars($_POST['imaddr_xmpp']);
			$homepage = htmlspecialchars($_POST['homepage']);
			$location = htmlspecialchars($_POST['location']);
			$occupation = htmlspecialchars($_POST['occupation']);
			$hobbies = htmlspecialchars($_POST['hobbies']);
			$email_public = ( isset($_POST['email_public']) ) ? '1' : '0';
			$user_title = htmlspecialchars($_POST['user_title']);
			
			if ( !preg_match('/@([a-z0-9-]+)(\.([a-z0-9-\.]+))?/', $imaddr_msn) && !empty($imaddr_msn) )
			{
				$imaddr_msn = "$imaddr_msn@hotmail.com";
			}
			
			if ( !preg_match('#^https?://#', $homepage) )
			{
				$homepage = "http://$homepage";
			}
			
			if ( !preg_match('/^http:\/\/([a-z0-9-.]+)([A-z0-9@#\$%\&:;<>,\.\?=\+\(\)\[\]_\/\\\\]*?)$/i', $homepage) )
			{
				$homepage = '';
			}
			
			// true for quiet operation
			list(, , $avatar_post_fail) = avatar_post($user_id, true);
			
			if ( count($errors) < 1 && !$avatar_post_fail )
			{
				$q = $db->sql_query('SELECT u.user_level, u.user_has_avatar, u.avatar_type, u.username FROM '.table_prefix.'users AS u WHERE u.user_id = ' . $user_id . ';');
				if ( !$q )
					$db->_die();
				
				if ( $db->numrows() < 1 )
				{
					echo 'Couldn\'t select user data: no rows returned';
				}
				
				$row = $db->fetchrow();
				$existing_level =& $row['user_level'];
				$avi_type =& $row['avatar_type'];
				$has_avi = ( $row['user_has_avatar'] == 1 );
				$old_username = $row['username'];
				$db->free_result();
				
				$to_update_users = array();
				if ( $user_id != $session->user_id )
				{
					$to_update_users['username'] = $username;
					if ( $password )
					{
						$session->set_password($user_id, $password);
					}
					$to_update_users['email'] = $email;
					$to_update_users['real_name'] = $real_name;
				}
				$to_update_users['signature'] = $signature;
				$to_update_users['user_level'] = $user_level;
				$to_update_users['user_rank'] = $user_rank;
				$to_update_users['user_title'] = $user_title;
				
				if ( $user_rank > 0 )
				{
					$to_update_users['user_rank_userset'] = '0';
				}
				
				if ( isset($_POST['account_active']) )
				{
					$to_update_users['account_active'] = "1";
				}
				else
				{
					$to_update_users['account_active'] = "0";
					$to_update_users['activation_key'] = sha1($session->dss_rand());
				}
				
				if ( count($errors) < 1 )
				{
					$to_update_users_extra = array();
					$to_update_users_extra['user_aim'] = $imaddr_aim;
					$to_update_users_extra['user_msn'] = $imaddr_msn;
					$to_update_users_extra['user_yahoo'] = $imaddr_yahoo;
					$to_update_users_extra['user_xmpp'] = $imaddr_xmpp;
					$to_update_users_extra['user_homepage'] = $homepage;
					$to_update_users_extra['user_location'] = $location;
					$to_update_users_extra['user_job'] = $occupation;
					$to_update_users_extra['user_hobbies'] = $hobbies;
					$to_update_users_extra['email_public'] = ( $email_public ) ? '1' : '0';
					
					$update_sql = '';
					
					foreach ( $to_update_users as $key => $unused_crap )
					{
						$value =& $to_update_users[$key];
						if ( $value !== 'NULL' )
							$value = "'" . $db->escape($value) . "'";
 
						$update_sql .= ( empty($update_sql) ? '' : ',' ) . "$key=$value";
					}
					
					$update_sql = 'UPDATE ' . table_prefix . "users SET $update_sql WHERE user_id=$user_id;";
					
					$update_sql_extra = '';
					
					foreach ( $to_update_users_extra as $key => $unused_crap )
					{
						$value =& $to_update_users_extra[$key];
						$value = $db->escape($value);
						$update_sql_extra .= ( empty($update_sql_extra) ? '' : ',' ) . "$key='$value'";
					}
					
					$update_sql_extra = 'UPDATE '.table_prefix."users_extra SET $update_sql_extra WHERE user_id=$user_id;";
					
					if ( !$db->sql_query($update_sql) )
						$db->_die();
					
					if ( !$db->sql_query($update_sql_extra) )
						$db->_die();
					
					// If the username was changed, we need to update their user page as well
					if ( $old_username != $username )
					{
						$page = new PageProcessor($old_username, 'User');
						if ( $page->exists() )
						{
							// they have a user page, rename it
							$old_urlname = $db->escape(sanitize_page_id($old_username));
							$new_urlname = $db->escape(sanitize_page_id($username));
							$sql = array(
											'UPDATE ' . table_prefix . "pages      SET urlname = '$new_urlname' WHERE urlname = '$old_urlname' AND namespace = 'User';",
											// Change the page's title ONLY if it exactly matches the old username
											'UPDATE ' . table_prefix . "pages      SET name = '" . $db->escape($username) . "' WHERE urlname = '$new_urlname' AND name = '" . $db->escape($old_username) . "' AND namespace = 'User';",
											'UPDATE ' . table_prefix . "logs       SET page_id = '$new_urlname' WHERE page_id = '$old_urlname' AND namespace = 'User';",
											'UPDATE ' . table_prefix . "tags       SET page_id = '$new_urlname' WHERE page_id = '$old_urlname' AND namespace = 'User';",
											'UPDATE ' . table_prefix . "comments   SET page_id = '$new_urlname' WHERE page_id = '$old_urlname' AND namespace = 'User';",
											'UPDATE ' . table_prefix . "page_text  SET page_id = '$new_urlname' WHERE page_id = '$old_urlname' AND namespace = 'User';",
											'UPDATE ' . table_prefix . "categories SET page_id = '$new_urlname' WHERE page_id = '$old_urlname' AND namespace = 'User';"
										);
							foreach ( $sql as $q )
							{
								if ( !$db->sql_query($q) )
									$db->_die('UserManager renaming user page post-username change');
							}
						}
					}
					
					if ( $existing_level != $user_level )
					{
						// We need to update group memberships
						if ( $existing_level == USER_LEVEL_ADMIN ) 
						{
							$q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author,author_uid,page_text) VALUES(\'security\',\'u_from_admin\',' . time() . ', \'' . $db->escape($_SERVER['REMOTE_ADDR']) . '\', ' . $session->user_id . ', \'' . $db->escape($session->username) . '\', \'' . $db->escape($username) . '\');');
							if ( !$q )
								$db->_die();
							$session->remove_user_from_group($user_id, GROUP_ID_ADMIN);
						}
						else if ( $existing_level == USER_LEVEL_MOD ) 
						{
							$q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author,author_uid,page_text) VALUES(\'security\',\'u_from_mod\',' . time() . ', \'' . $db->escape($_SERVER['REMOTE_ADDR']) . '\', ' . $session->user_id . ', \'' . $db->escape($session->username) . '\', \'' . $db->escape($username) . '\');');
							if ( !$q )
								$db->_die();
							$session->remove_user_from_group($user_id, GROUP_ID_MOD);
						}
						
						if ( $user_level == USER_LEVEL_ADMIN )
						{
							$q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author,author_uid,page_text) VALUES(\'security\',\'u_to_admin\',' . time() . ', \'' . $db->escape($_SERVER['REMOTE_ADDR']) . '\', ' . $session->user_id . ', \'' . $db->escape($session->username) . '\', \'' . $db->escape($username) . '\');');
							if ( !$q )
								$db->_die();
							$session->add_user_to_group($user_id, GROUP_ID_ADMIN, false);
						}
						else if ( $user_level == USER_LEVEL_MOD )
						{
							$q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author,author_uid,page_text) VALUES(\'security\',\'u_to_mod\',' . time() . ', \'' . $db->escape($_SERVER['REMOTE_ADDR']) . '\', ' . $session->user_id . ', \'' . $db->escape($session->username) . '\', \'' . $db->escape($username) . '\');');
							if ( !$q )
								$db->_die();
							$session->add_user_to_group($user_id, GROUP_ID_MOD, false);
						}
					}
					
					// user level updated, regenerate the ranks cache
					generate_cache_userranks();
					
					echo '<div class="info-box">' . $lang->get('acpum_msg_save_success') . '</div>';
				}
			}
		}
		
		if ( count($errors) > 0 || @$avatar_post_fail )
		{
			if ( count($errors) > 0 )
			{
				echo '<div class="error-box">
								<b>' . $lang->get('acpum_err_validation_fail') . '</b>
								<ul>
									<li>' . implode("</li>\n        <li>", $errors) . '</li>
								</ul>
							</div>';
			}
			$form = new Admin_UserManager_SmartForm();
			$form->user_id = $user_id;
			$form->username = $username;
			$form->email = $email;
			$form->real_name = $real_name;
			$form->signature = $signature;
			$form->user_level = $user_level;
			$form->user_rank = $user_rank;
			$form->user_title = $user_title;
			$form->im = array(
					'aim' => $imaddr_aim,
					'yahoo' => $imaddr_yahoo,
					'msn' => $imaddr_msn,
					'xmpp' => $imaddr_xmpp
				);
			$form->contact = array(
					'homepage' => $homepage,
					'location' => $location,
					'job' => $occupation,
					'hobbies' => $hobbies
				);
			$form->email_public = ( isset($_POST['email_public']) );
			$form->account_active = ( isset($_POST['account_active']) );
			// This is SAFE. The smartform calls is_valid_ip() on this value, thus preventing XSS
			// attempts from making it into the form HTML. Badly coded templates may still be
			// affected, but if have_reg_ip is checked for, then you're fine.
			$form->reg_ip_addr = $_POST['user_registration_ip'];
			echo $form->render();
			return false;
		}
		
		#
		# END VALIDATION
		#
	}
	else if ( isset($_POST['action']['go']) || ( isset($_GET['src']) && $_GET['src'] == 'get' ) || ($pathsuser = $paths->getParam(0)) )
	{
		if ( isset($_GET['user']) )
		{
			$username =& $_GET['user'];
		}
		else if ( isset($_GET['username']) )
		{
			$username =& $_GET['username'];
		}
		else if ( isset($_POST['username']) )
		{
			$username =& $_POST['username'];
		}
		else if ( $pathsuser )
		{
			$username = str_replace('_', ' ', dirtify_page_id($pathsuser));
		}
		else
		{
			echo 'No username provided';
			return false;
		}
		$q = $db->sql_query('SELECT u.user_id AS authoritative_uid, u.username, u.email, u.real_name, u.signature, u.account_active, u.user_level, u.user_rank, u.user_title, u.user_has_avatar, u.avatar_type, u.user_registration_ip, x.* FROM '.table_prefix.'users AS u
 													LEFT JOIN '.table_prefix.'users_extra AS x
 														ON ( u.user_id = x.user_id OR x.user_id IS NULL )
 													WHERE ( ' . ENANO_SQLFUNC_LOWERCASE . '(u.username) = \'' . $db->escape(strtolower($username)) . '\' OR u.username = \'' . $db->escape($username) . '\' ) AND u.user_id != 1;');
		if ( !$q )
			$db->_die();
		
		if ( $db->numrows() < 1 )
		{
			echo '<div class="error-box">' . $lang->get('acpum_err_bad_username') . '</div>';
		}
		else
		{
			$row = $db->fetchrow();
			$row['user_id'] = $row['authoritative_uid'];
			$form = new Admin_UserManager_SmartForm();
			$form->user_id   = $row['user_id'];
			$form->username  = $row['username'];
			$form->email     = $row['email'];
			$form->real_name = $row['real_name'];
			$form->signature = $row['signature'];
			$form->user_level= $row['user_level'];
			$form->user_rank = $row['user_rank'];
			$form->user_title= $row['user_title'];
			$form->account_active = ( $row['account_active'] == 1 );
			$form->email_public   = ( $row['email_public'] == 1 );
			$form->has_avatar     = ( $row['user_has_avatar'] == 1 );
			$form->avi_type       = $row['avatar_type'];
			$form->im = array(
					'aim' => $row['user_aim'],
					'yahoo' => $row['user_yahoo'],
					'msn' => $row['user_msn'],
					'xmpp' => $row['user_xmpp']
				);
			$form->contact = array(
					'homepage' => $row['user_homepage'],
					'location' => $row['user_location'],
					'job'      => $row['user_job'],
					'hobbies'  => $row['user_hobbies'],
				);
			$form->email_public = ( $row['email_public'] == 1 );
			$form->reg_ip_addr = ( $row['user_registration_ip'] ) ? $row['user_registration_ip'] : '';
			$html = $form->render();
			if ( !$html )
			{
				echo 'Internal error: form processor returned false';
			}
			else
			{
				echo $html;
			}
			return true;
		}
	}
	else if ( isset($_POST['action']['clear_sessions']) )
	{
		if ( defined('ENANO_DEMO_MODE') )
		{
			echo '<div class="error-box">' . $lang->get('acpum_err_sessionclear_demo') . '</div>';
		}
		else
		{
			// Get the current session information so the user doesn't get logged out
			$aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
			$sk = md5($session->sid_super);
			$qb = $db->sql_query('SELECT session_key,salt,auth_level,source_ip,time FROM '.table_prefix.'session_keys WHERE session_key=\''.$sk.'\' AND user_id='.$session->user_id.' AND auth_level='.USER_LEVEL_ADMIN);
			if ( !$qb )
			{
				die('Error selecting session key info block B: '.$db->get_error());
			}
			if ( $db->numrows($qb) < 1 )
			{
				die('Error: cannot read admin session info block B, aborting table clear process');
			}
			$qa = $db->sql_query('SELECT session_key,salt,auth_level,source_ip,time FROM '.table_prefix.'session_keys WHERE session_key=\''.md5($session->sid).'\' AND user_id='.$session->user_id.' AND auth_level='.USER_LEVEL_MEMBER);
			if ( !$qa )
			{
				die('Error selecting session key info block A: '.$db->get_error());
			}
			if ( $db->numrows($qa) < 1 )
			{
				die('Error: cannot read user session info block A, aborting table clear process');
			}
			$ra = $db->fetchrow($qa);
			$rb = $db->fetchrow($qb);
			$db->free_result($qa);
			$db->free_result($qb);
			
			$db->sql_query('DELETE FROM '.table_prefix.'session_keys;');
			$db->sql_query('INSERT INTO '.table_prefix.'session_keys( session_key,salt,user_id,auth_level,source_ip,time ) VALUES( \''.$ra['session_key'].'\', \'' . $db->escape($ra['salt']) . '\', \''.$session->user_id.'\', \''.$ra['auth_level'].'\', \''.$ra['source_ip'].'\', '.$ra['time'].' ),( \''.$rb['session_key'].'\', \'' . $db->escape($rb['salt']) . '\', \''.$session->user_id.'\', \''.$rb['auth_level'].'\', \''.$rb['source_ip'].'\', '.$rb['time'].' )');
			
			echo '<div class="info-box">' . $lang->get('acpum_msg_sessionclear_success') . '</div>';
		}
	}
	echo '<form action="' . makeUrlNS('Special', 'Administration', 'module=' . $paths->cpage['module'], true) . '" method="post" enctype="multipart/form-data" onsubmit="if ( !submitAuthorized ) return false;">';
	echo '<h3>' . $lang->get('acpum_heading_main') . '</h3>';
	echo '<p>' . $lang->get('acpum_hint_intro') . '</p>';
	echo '<table border="0">
					<tr>
						<td><b>' . $lang->get('acpum_field_search_user') . '</b><br />
								<small>' . $lang->get('acpum_field_search_user_hint') . '</small>
								</td>
						<td style="width: 10px;"></td>
						<td>' . $template->username_field('username') . '</td>
						<td>
							<input type="submit" name="action[go]" value="' . $lang->get('acpum_btn_search_user_go') . ' &raquo;" />
						</td>
					</tr>
				</table>';
	echo '<h3>' . $lang->get('acpum_heading_clear_sessions') . '</h3>';
	echo '<p>' . $lang->get('acpum_hint_clear_sessions') . '</p>';
	echo '<p><input type="submit" name="action[clear_sessions]" value="' . $lang->get('acpum_btn_clear_sessions') . '" /></p>';
	echo '</form>';
	
	if(isset($_GET['action']) && isset($_GET['user']))
	{
		switch($_GET['action'])
		{
			case "activate":
				$e = $db->sql_query('SELECT activation_key FROM '.table_prefix.'users WHERE username=\'' . $db->escape($_GET['user']) . '\'');
				if ( $e )
				{
					// attempt to activate the account
					$row = $db->fetchrow();
					$db->free_result();
					if ( $session->activate_account($_GET['user'], $row['activation_key']) )
					{
						echo '<div class="info-box">' . $lang->get('acpum_msg_activate_success', array('username' => htmlspecialchars($_GET['user']))) . '</div>';
						$db->sql_query('DELETE FROM '.table_prefix.'logs WHERE time_id=' . $db->escape($_GET['logid']));
					}
					else
					{
						echo '<div class="warning-box">' . $lang->get('acpum_err_activate_fail', array('username' => htmlspecialchars($_GET['user']))) . '</div>';
					}
				}
				else
				{
					echo '<div class="error-box">Error activating account: '.$db->get_error().'</div>';
				}
				break;
			case "sendemail":
				if ( $session->send_activation_mail($_GET['user'] ) )
				{
					echo '<div class="info-box">' . $lang->get('acpum_msg_activate_email_success', array('username' => htmlspecialchars($_GET['user']))) . '</div>';
					$db->sql_query('DELETE FROM '.table_prefix.'logs WHERE time_id=' . $db->escape($_GET['logid']));
				}
				else
				{
					echo '<div class="error-box">' . $lang->get('acpum_err_activate_email_fail', array('username' => htmlspecialchars($_GET['user']))) . '</div>';
				}
				break;
			case "deny":
				$e = $db->sql_query('DELETE FROM '.table_prefix.'logs WHERE log_type=\'admin\' AND action=\'activ_req\' AND time_id=\'' . $db->escape($_GET['logid']) . '\';');
				if ( !$e )
				{
					echo '<div class="error-box">Error during row deletion: '.$db->get_error().'</div>';
				}
				else
				{
					echo '<div class="info-box">' . $lang->get('acpum_msg_activate_deny_success', array('username' => htmlspecialchars($_GET['user']))) . '</div>';
				}
				break;
		}
	}
	$q = $db->sql_query('SELECT l.log_type, l.action, l.time_id, l.date_string, l.author, l.edit_summary, u.user_coppa FROM '.table_prefix.'logs AS l
 												LEFT JOIN '.table_prefix.'users AS u
 													ON ( u.username = l.edit_summary OR u.username IS NULL )
 												WHERE log_type=\'admin\' AND action=\'activ_req\' ORDER BY time_id DESC;');
	if($q)
	{
		if($db->numrows() > 0)
		{
			$n = $db->numrows();
			$str = ( $n == 1 ) ?
				$lang->get('acpum_heading_activation_one') :
				$lang->get('acpum_heading_activation_plural', array('count' => strval($n)));
				
			echo '<h3>' . $str . '</h3>';
				
			echo '<div class="tblholder">
							<table border="0" cellspacing="1" cellpadding="4" width="100%">
								<tr>
									<th>' . $lang->get('acpum_col_activate_timestamp') . '</th>
									<th>' . $lang->get('acpum_col_activate_requestedby') . '</th>
									<th>' . $lang->get('acpum_col_activate_requestedfor') . '</th>
									<th>' . $lang->get('acpum_col_activate_coppauser') . '</th>
									<th colspan="3">' . $lang->get('acpum_col_activate_actions') . '</th>
								</tr>';
			$cls = 'row2';
			while($row = $db->fetchrow())
			{
				if($cls == 'row2') $cls = 'row1';
				else $cls = 'row2';
				$coppa = ( $row['user_coppa'] == '1' ) ? '<b>' . $lang->get('acpum_coppauser_yes') . '</b>' : $lang->get('acpum_coppauser_no');
				echo '<tr>
								<td class="'.$cls.'">'.enano_date(ED_DATE | ED_TIME, $row['time_id']).'</td>
								<td class="'.$cls.'">'.$row['author'].'</td>
								<td class="'.$cls.'">'.$row['edit_summary'].'</td>
								<td style="text-align: center;" class="' . $cls . '">' . $coppa . '</td>
								<td class="'.$cls.'" style="text-align: center;">
									<a href="'.makeUrlNS('Special', 'Administration', 'module='.$paths->nslist['Admin'].'UserManager&action=activate&user='.rawurlencode($row['edit_summary']).'&logid='.$row['time_id'], true).'">' . $lang->get('acpum_btn_activate_now') . '</a>
								</td>
								<td class="'.$cls.'" style="text-align: center;">
									<a href="'.makeUrlNS('Special', 'Administration', 'module='.$paths->nslist['Admin'].'UserManager&action=sendemail&user='.rawurlencode($row['edit_summary']).'&logid='.$row['time_id'], true).'">' . $lang->get('acpum_btn_send_email') . '</a>
								</td>
								<td class="'.$cls.'" style="text-align: center;">
									<a href="'.makeUrlNS('Special', 'Administration', 'module='.$paths->nslist['Admin'].'UserManager&action=deny&user='.rawurlencode($row['edit_summary']).'&logid='.$row['time_id'], true).'">' . $lang->get('acpum_btn_activate_deny') . '</a>
								</td>
							</tr>';
			}
			echo '</table>';
			echo '</div>';
		}
		$db->free_result();
	}
	
	acp_usermanager_lockouts();
}

/**
 * Smart form class for the user manager.
 * @package Enano
 * @subpackage Administration
 */

class Admin_UserManager_SmartForm
{
	
	/**
 	* Universally Unique Identifier (UUID) for this editor instance. Used to unique-itize Javascript functions and whatnot.
 	* @var string
 	*/
	
	var $uuid = '';
	
	/**
 	* User ID that we're editing.
 	* @var int
 	*/
	
	var $user_id = 0;
	
	/**
 	* Username
 	* @var string
 	*/
	
	var $username = '';
	
	/**
 	* E-mail address
 	* @var string
 	*/
	
	var $email = '';
	
	/**
 	* Real name
 	* @var string
 	*/
	
	var $real_name = '';
	
	/**
 	* Signature
 	* @var string
 	*/
	
	var $signature = '';
	
	/**
 	* IM contact information
 	* @var array
 	*/
 	
	var $im = array();
	
	/**
 	* Real-life contact info
 	* @var array
 	*/
	
	var $contact = array();
	
	/**
 	* User level
 	* @var int
 	*/
	
	var $user_level = USER_LEVEL_MEMBER;
	
	/**
 	* User-specific user rank
 	* @var int
 	*/
	
	var $user_rank = NULL;
	
	/**
 	* User's custom title
 	* @var int
 	*/
	
	var $user_title = '';
	
	/**
 	* Account activated
 	* @var bool
 	*/
	
	var $account_active = true;
	
	/**
 	* Email public switch
 	* @var bool
 	*/
	
	var $email_public = false;
	
	/**
 	* Whether the user has an avatar or not.
 	* @var bool
 	*/
	
	var $has_avatar = false;
	
	/**
 	* The type of avatar the user has. One of "jpg", "png", or "gif".
 	* @var string
 	*/
	
	var $avi_type = 'png';
	
	/**
 	* The IP address of the user during registration
 	* @var string
 	*/
	
	var $reg_ip_addr = '';
	
	/**
 	* Constructor.
 	*/
	
	function Admin_UserManager_SmartForm()
	{
		$this->uuid = md5( mt_rand() . microtime() );
	}
	
	/**
 	* Renders and returns the finished form.
 	* @return string
 	*/
	
	function render()
	{
		global $db, $session, $paths, $template, $plugins; // Common objects
		global $lang;
		global $dh_supported;
		if ( file_exists( ENANO_ROOT . "/themes/$template->theme/admin_usermanager_form.tpl" ) )
		{
			$parser = $template->makeParser('admin_usermanager_form.tpl');
		}
		else
		{
			$tpl_code = <<<EOF
			<!-- Start of user edit form -->
			
				<script type="text/javascript">
					function userform_{UUID}_chpasswd()
					{
						var link = document.getElementById('userform_{UUID}_pwlink');
						var form = document.getElementById('userform_{UUID}_pwform');
						domOpacity(link, 100, 0, 500);
						domObjChangeOpac(0, form);
						setTimeout("var link = document.getElementById('userform_{UUID}_pwlink'); var form = document.getElementById('userform_{UUID}_pwform'); link.style.display = 'none'; form.style.display = 'block'; domOpacity(form, 0, 100, 500);", 550);
						<!-- BEGINNOT same_user -->document.forms['useredit_{UUID}'].changing_pw.value = 'yes';<!-- END same_user -->
					}
					
					function userform_{UUID}_chpasswd_cancel()
					{
						var link = document.getElementById('userform_{UUID}_pwlink');
						var form = document.getElementById('userform_{UUID}_pwform');
						domOpacity(form, 100, 0, 500);
						domObjChangeOpac(0, link);
						setTimeout("var link = document.getElementById('userform_{UUID}_pwlink'); var form = document.getElementById('userform_{UUID}_pwform'); form.style.display = 'none'; link.style.display = 'block'; domOpacity(link, 0, 100, 500);", 550);
						<!-- BEGINNOT same_user -->document.forms['useredit_{UUID}'].changing_pw.value = 'no';<!-- END same_user -->
					}
					
					function userform_{UUID}_validate()
					{
						var form = document.forms['useredit_{UUID}'];
						<!-- BEGINNOT same_user -->
						if ( form.changing_pw.value == 'yes' )
						{
							return runEncryption(true);
						}
						<!-- END same_user -->
						return true;
					}
				</script>
			
				<form action="{FORM_ACTION}" method="post" name="useredit_{UUID}" enctype="multipart/form-data" onsubmit="return userform_{UUID}_validate();">
				
					<input name="user_id" value="{USER_ID}" type="hidden" />
				
					<div class="tblholder">
						<table border="0" cellspacing="1" cellpadding="4">
						
							<!-- Heading -->
						
							<tr>
								<th colspan="2">
									{lang:acpum_heading_editing_user} {USERNAME}
								</th>
							</tr>
							
							<!-- Basic options (stored in enano_users) -->
							
								<tr>
									<th colspan="2" class="subhead">
										{lang:acpum_heading_basic_options}
									</th>
								</tr>
								
								<tr>
									<td class="row2" style="width: 25%;">
										{lang:acpum_field_username}<br />
										<small>{lang:acpum_field_username_hint}</small>
									</td>
									<td class="row1" style="width: 75%;">
										<input type="text" name="username" value="{USERNAME}" size="40" <!-- BEGIN same_user -->disabled="disabled" <!-- END same_user -->/>
										<!-- BEGIN same_user --><small>{lang:acpum_msg_same_user_username}</small><!-- END same_user -->
									</td>
								</tr>
								
								<tr>
									<td class="row2">
										{lang:acpum_field_password}
										<!-- BEGIN password_meter -->
										<br />
										<small>{lang:acpum_field_password_hint}</small>
										<!-- END password_meter -->
									</td>
									<td class="row1">
										<div id="userform_{UUID}_pwlink">
											<b>{lang:acpum_msg_password_unchanged}</b> <a href="#" onclick="userform_{UUID}_chpasswd(); return false;">{lang:acpum_btn_reset_password}</a>
										</div>
										<div id="userform_{UUID}_pwform" style="display: none;">
											<!-- BEGIN same_user -->
												{lang:acpum_msg_same_user_password} <a href="#" onclick="userform_{UUID}_chpasswd_cancel(); return false;">{lang:etc_cancel}</a>
											<!-- BEGINELSE same_user -->
											<input type="hidden" name="changing_pw" value="no" />
											{AES_FORM}
											<table border="0" style="background-color: transparent;" cellspacing="0" cellpadding="0">
												<tr>
													<td colspan="2">
														<b>{lang:acpum_field_password_title}</b>
													</td>
												</tr>
												<tr>
													<td>{lang:acpum_field_newpassword}</td>
													<td>
													<!-- BEGIN password_meter -->
														<input type="password" name="new_password" value="" onkeyup="password_score_field(this);" /><span class="password-checker" style="font-weight: bold; color: #A0A0A0"> Waiting for l10n init</span>
													<!-- BEGINELSE password_meter -->
														<input type="password" name="new_password" value="" />
													<!-- END password_meter -->
													<!-- BEGIN password_meter -->
														<div id="pwmeter" style="margin: 4px 0; height: 8px;"></div>
													<!-- END password_meter -->
													</td>
												</tr>
												<tr>
													<td>{lang:acpum_field_newpassword_confirm}</td>
													<td><input type="password" name="new_password_confirm" value="" /></td>
												</tr>
												<tr>
													<td colspan="2">
														<a href="#" onclick="userform_{UUID}_chpasswd_cancel(); return false;">{lang:etc_cancel}</a>
													</td>
												</tr>
											</table>
											<!-- END same_user -->
										</div>
									</td>
								</tr>
								
								<tr>
									<td class="row2" style="width: 25%;">
										{lang:acpum_field_email}
									</td>
									<td class="row1" style="width: 75%;">
										<input type="text" name="email" value="{EMAIL}" size="40" <!-- BEGIN same_user -->disabled="disabled" <!-- END same_user -->/>
										<!-- BEGIN same_user --><small>{lang:acpum_msg_same_user_email}</small><!-- END same_user -->
									</td>
								</tr>
								
								<tr>
									<td class="row2" style="width: 25%;">
										{lang:acpum_field_realname}
									</td>
									<td class="row1" style="width: 75%;">
										<input type="text" name="real_name" value="{REAL_NAME}" size="40" <!-- BEGIN same_user -->disabled="disabled" <!-- END same_user -->/>
										<!-- BEGIN same_user --><small>{lang:acpum_msg_same_user_realname}</small><!-- END same_user -->
									</td>
								</tr>
								
								<tr>
									<td class="row2" style="width: 25%;">
										{lang:acpum_field_signature}
									</td>
									<td class="row1" style="width: 75%;">
										{SIGNATURE_FIELD}
									</td>
								</tr>
								
								<tr>
									<td class="row2" style="width: 25%;">
										{lang:acpum_field_usertitle}<br />
										<small>
											{lang:acpum_field_usertitle_hint}
										</small>
									</td>
									<td class="row1" style="width: 75%;">
										<input type="text" name="user_title" value="{USER_TITLE}" />
									</td>
								</tr>
								
								
								
							<!-- / Basic options -->
							
							<!-- Extended options (anything in enano_users_extra) -->
							
								<tr>
									<th class="subhead" colspan="2">
										{lang:acpum_heading_imcontact}
									</th>
								<tr>
									<td class="row2">{lang:acpum_field_aim}</td>
									<td class="row1"><input type="text" name="imaddr_aim" value="{IM_AIM}" size="30" /></td>
								</tr>
								<tr>
									<td class="row2">{lang:acpum_field_wlm}<br /><small>{lang:acpum_field_wlm_hint}</small></td>
									<td class="row1"><input type="text" name="imaddr_msn" value="{IM_WLM}" size="30" /></td>
								</tr>
								<tr>
									<td class="row2">{lang:acpum_field_yim}</td>
									<td class="row1"><input type="text" name="imaddr_yahoo" value="{IM_YAHOO}" size="30" /></td>
								</tr>
								<tr>
									<td class="row2">{lang:acpum_field_xmpp}</td>
									<td class="row1"><input type="text" name="imaddr_xmpp" value="{IM_XMPP}" size="30" /></td>
								</tr>
								<tr>
									<th class="subhead" colspan="2">
										{lang:acpum_heading_contact_extra}
									</th>
								</tr>
								<tr>
									<td class="row2">{lang:acpum_field_homepage}<br /><small>{lang:acpum_field_homepage_hint}</small></td>
									<td class="row1"><input type="text" name="homepage" value="{HOMEPAGE}" size="30" /></td>
								</tr>
								<tr>
									<td class="row2">{lang:acpum_field_location}</td>
									<td class="row1"><input type="text" name="location" value="{LOCATION}" size="30" /></td>
								</tr>
								<tr>
									<td class="row2">{lang:acpum_field_job}</td>
									<td class="row1"><input type="text" name="occupation" value="{JOB}" size="30" /></td>
								</tr>
								<tr>
									<td class="row2">{lang:acpum_field_hobbies}</td>
									<td class="row1"><input type="text" name="hobbies" value="{HOBBIES}" size="30" /></td>
								</tr>
								<tr>
									<td class="row2"><label for="chk_email_public_{UUID}">{lang:acpum_field_email_public}</label><br /><small>{lang:acpum_field_email_public_hint}</small></td>
									<td class="row1"><input type="checkbox" id="chk_email_public_{UUID}" name="email_public" <!-- BEGIN email_public -->checked="checked" <!-- END email_public -->size="30" /></td>
								</tr>
							
							<!-- / Extended options -->
							
							<!-- Avatar settings -->
							
								<tr>
									<th class="subhead" colspan="2">
										{lang:acpum_avatar_heading}
									</th>
								</tr>
								
								<tr>
									<td class="row2">
										{lang:usercp_avatar_label_current}
									</td>
									<td class="row1">
										<!-- BEGIN user_has_avatar -->
											<img alt="{AVATAR_ALT}" src="{AVATAR_SRC}" />
										<!-- BEGINELSE user_has_avatar -->
											{lang:acpum_avatar_image_none}
										<!-- END user_has_avatar -->
									</td>
								</tr>
								
								<tr>
									<td class="row2">
										{lang:acpum_avatar_lbl_change}
									</td>
									<td class="row1" id="avatar_upload_btns_{UUID}">
										<script type="text/javascript">
											function admincp_users_avatar_set_{UUID}(elParent)
											{
												$('td#avatar_upload_btns_{UUID} > div:visible').hide('blind');
												switch(elParent.value)
												{
													case 'set_http':
														$('#avatar_upload_http_{UUID}').show('blind');
														break;
													case 'set_file':
														$('#avatar_upload_file_{UUID}').show('blind');
														break;
													case 'set_gravatar':
														$('#avatar_upload_gravatar_{UUID}').show('blind');
														break;
												}
											}
										</script>
										<label><input onclick="admincp_users_avatar_set_{UUID}(this);" type="radio" name="avatar_action" value="keep" checked="checked" /> {lang:acpum_avatar_lbl_keep}</label><br />
										<label><input onclick="admincp_users_avatar_set_{UUID}(this);" type="radio" name="avatar_action" value="remove" /> {lang:acpum_avatar_lbl_remove}</label><br />
										<label><input onclick="admincp_users_avatar_set_{UUID}(this);" type="radio" name="avatar_action" value="set_http" /> {lang:acpum_avatar_lbl_set_http}</label><br />
											<div id="avatar_upload_http_{UUID}" style="display: none; margin: 10px 0 0 2.2em;">
												{lang:usercp_avatar_lbl_url} <input type="text" name="avatar_http_url" size="40" value="http://" /><br />
												<small>{lang:usercp_avatar_lbl_url_desc} {lang:usercp_avatar_limits}</small>
											</div>
										<label><input onclick="admincp_users_avatar_set_{UUID}(this);" type="radio" name="avatar_action" value="set_file" /> {lang:acpum_avatar_lbl_set_file}</label><br />
											<div id="avatar_upload_file_{UUID}" style="display: none; margin: 10px 0 0 2.2em;">
												{lang:usercp_avatar_lbl_file} <input type="file" name="avatar_file" size="40" value="http://" /><br />
												<small>{lang:usercp_avatar_lbl_file_desc} {lang:usercp_avatar_limits}</small>
											</div>
										<label><input onclick="admincp_users_avatar_set_{UUID}(this);" type="radio" name="avatar_action" value="set_gravatar" /> {lang:acpum_avatar_lbl_set_gravatar} <img alt=" " src="{GRAVATAR_URL}" /></label><br />
											<div id="avatar_upload_gravatar_{UUID}"></div>
									</td>
								</tr>
								
							<!-- / Avatar settings -->
							
							<!-- Administrator-only options -->
							
								<tr>
									<th class="subhead" colspan="2">
										{lang:acpum_heading_adminonly}
									</th>
								</tr>
								
								<tr>
									<td class="row2">{lang:acpum_field_active_title}<br />
 																	<small>{lang:acpum_field_active_hint}</small>
 																	</td>
									<td class="row1"><label><input type="checkbox" name="account_active" <!-- BEGIN account_active -->checked="checked" <!-- END account_active -->/> {lang:acpum_field_active}</label></td>
								</tr>
								
								<tr>
									<td class="row2">
										{lang:acpum_field_userlevel}<br />
										<small>{lang:acpum_field_userlevel_hint}</small>
									</td>
									<td class="row1">
										<select name="user_level">
											<option value="{USER_LEVEL_MEMBER}"<!-- BEGIN ul_member --> selected="selected"<!-- END ul_member -->>{lang:userfuncs_ml_level_member}</option>
											<option value="{USER_LEVEL_MOD}"<!-- BEGIN ul_mod --> selected="selected"<!-- END ul_mod -->>{lang:userfuncs_ml_level_mod}</option>
											<option value="{USER_LEVEL_ADMIN}"<!-- BEGIN ul_admin --> selected="selected"<!-- END ul_admin -->>{lang:userfuncs_ml_level_admin}</option>
										</select>
									</td>
								</tr>
								
								<tr>
									<td class="row2">
										{lang:acpum_field_userrank}<br />
										<small>{lang:acpum_field_userrank_hint}</small>
									</td>
									<td class="row1">
										<select name="user_rank">
											{RANK_LIST}
										</select>
									</td>
								</tr>
								
								<!-- BEGIN have_reg_ip -->
								<tr>
									<td class="row2">
										{lang:acpum_field_reg_ip}
									</td>
									<td class="row1">
										{REG_IP_ADDR}
										<input type="hidden" name="user_registration_ip" value="{REG_IP_ADDR}" />
									</td>
								</tr>
								<!-- BEGINELSE have_reg_ip -->
								<input type="hidden" name="user_registration_ip" value="" />
								<!-- END have_reg_ip -->
								
								<tr>
									<td class="row2">
										{lang:acpum_field_deleteaccount_title}
									</td>
									<td class="row1">
									<label><input type="checkbox" name="delete_account" onclick="var d = (this.checked) ? 'block' : 'none'; document.getElementById('delete_blurb_{UUID}').style.display = d;" /> {lang:acpum_field_deleteaccount}</label>
										<div id="delete_blurb_{UUID}" style="display: none;">
											<!-- BEGIN same_user -->
											<!-- Obnoxious I know, but it's needed. -->
											<p><b>{lang:acpum_msg_delete_own_account}</b></p>
											<!-- END same_user -->
											<p><small>{lang:acpum_field_deleteaccount_hint}</small></p>
										</div>
									</td>
								</tr>
								</tr>
							
							<!-- Save button -->
							<tr>
								<th colspan="2">
									<input type="submit" name="action[save]" value="{lang:acpum_btn_save}" style="font-weight: bold;" />
									<input type="submit" name="action[noop]" value="{lang:etc_cancel}" style="font-weight: normal;" />
								</th>
							</tr>
						
						</table>
					</div>
				
				</form>
				
				<!-- BEGINNOT same_user -->
				<script type="text/javascript">
				addOnloadHook(function() {
					password_score_field(document.forms['useredit_{UUID}'].new_password);
				});
				</script>
				<!-- END same_user -->
				
				{AES_JAVASCRIPT}
			<!-- Conclusion of user edit form -->
EOF;
			$parser = $template->makeParserText($tpl_code);
		}
		
		$this->username = htmlspecialchars($this->username);
		$this->email = htmlspecialchars($this->email);
		$this->user_id = intval($this->user_id);
		$this->real_name = htmlspecialchars($this->real_name);
		$this->signature = htmlspecialchars($this->signature);
		$this->user_level = intval($this->user_level);
		
		$im_aim   = ( isset($this->im['aim']) )   ? $this->im['aim']   : false;
		$im_yahoo = ( isset($this->im['yahoo']) ) ? $this->im['yahoo'] : false;
		$im_msn   = ( isset($this->im['msn']) )   ? $this->im['msn']   : false;
		$im_xmpp  = ( isset($this->im['xmpp']) )  ? $this->im['xmpp']  : false;
		
		$homepage = ( isset($this->contact['homepage']) ) ? $this->contact['homepage'] : false;
		$location = ( isset($this->contact['location']) ) ? $this->contact['location'] : false;
		$job = ( isset($this->contact['job']) ) ? $this->contact['job'] : false;
		$hobbies = ( isset($this->contact['hobbies']) ) ? $this->contact['hobbies'] : false;
		
		if ( empty($this->username) )
		{
			// @error One or more required parameters not set
			return 'Admin_UserManager_SmartForm::render: Invalid parameter ($form->username)';
		}
		
		if ( empty($this->user_id) )
		{
			// @error One or more required parameters not set
			return 'Admin_UserManager_SmartForm::render: Invalid parameter ($form->user_id)';
		}
		
		if ( empty($this->email) )
		{
			// @error One or more required parameters not set
			return 'Admin_UserManager_SmartForm::render: Invalid parameter ($form->email)';
		}
		
		$form_action = makeUrlNS('Special', 'Administration', 'module=' . $paths->cpage['module'], true);
		$aes_javascript = $session->aes_javascript("useredit_$this->uuid", 'new_password');
		
		// build rank list
		$q = $db->sql_query('SELECT rank_id, rank_title FROM ' . table_prefix . 'ranks');
		if ( !$q )
			$db->_die();
		$rank_list = '<option value="NULL"' . ( $this->user_rank === NULL ? ' selected="selected"' : '' ) . '>--</option>' . "\n";
		while ( $row = $db->fetchrow() )
		{
			$rank_list .= '<option value="' . $row['rank_id'] . '"' . ( $row['rank_id'] == $this->user_rank ? ' selected="selected"' : '' ) . '>' . htmlspecialchars($lang->get($row['rank_title'])) . '</option>' . "\n";
		}
		
		$parser->assign_vars(array(
				'UUID' => $this->uuid,
				'USERNAME' => $this->username,
				'EMAIL' => $this->email,
				'USER_ID' => $this->user_id,
				'AES_FORM' => $session->generate_aes_form(),
				'REAL_NAME' => $this->real_name,
				'SIGNATURE_FIELD' => $template->tinymce_textarea('signature', $this->signature, 10, 50),
				'USER_TITLE' => $this->user_title,
				'USER_LEVEL_MEMBER' => USER_LEVEL_CHPREF,
				'USER_LEVEL_MOD' => USER_LEVEL_MOD,
				'USER_LEVEL_ADMIN' => USER_LEVEL_ADMIN,
				'AES_JAVASCRIPT' => $aes_javascript,
				'IM_AIM' => $im_aim,
				'IM_YAHOO' => $im_yahoo,
				'IM_WLM' => $im_msn,
				'IM_XMPP' => $im_xmpp,
				'HOMEPAGE' => $homepage,
				'LOCATION' => $location,
				'JOB' => $job,
				'HOBBIES' => $hobbies,
				'FORM_ACTION' => $form_action,
				'REG_IP_ADDR' => $this->reg_ip_addr,
				'RANK_LIST' => $rank_list,
				'GRAVATAR_URL' => make_gravatar_url($this->email, 16)
			));
		
		if ( $this->has_avatar )
		{
			$parser->assign_vars(array(
					'AVATAR_SRC' => make_avatar_url($this->user_id, $this->avi_type),
					'AVATAR_ALT' => $lang->get('usercp_avatar_image_alt', array('username' => $this->username), $this->email)
				));
		}
		
		$parser->assign_bool(array(
				'password_meter' => ( getConfig('pw_strength_enable') == '1' ),
				'ul_member' => ( $this->user_level == USER_LEVEL_CHPREF ),
				'ul_mod' => ( $this->user_level == USER_LEVEL_MOD ),
				'ul_admin' => ( $this->user_level == USER_LEVEL_ADMIN ),
				'account_active' => ( $this->account_active === true ),
				'email_public' => ( $this->email_public === true ),
				'same_user' => ( $this->user_id == $session->user_id ),
				'user_has_avatar' => ( $this->has_avatar ),
				'have_reg_ip' => ( intval(@strlen($this->reg_ip_addr)) > 0 && is_valid_ip($this->reg_ip_addr) )
			));
		
		$parsed = $parser->run();
		return $parsed;
	}
	
}

function acp_usermanager_lockouts($homewrap = false)
{
	global $db, $session, $paths, $template, $plugins; // Common objects
	global $lang;
	
	// Locked out users
	
	if ( !empty($_GET['clear_lockout']) && is_valid_ip($_GET['clear_lockout']) )
	{
		$ip = $db->escape($_GET['clear_lockout']);
		$q = $db->sql_query('DELETE FROM ' . table_prefix . "lockout WHERE ipaddr = '$ip' AND timestamp > ( " . time() . " - (" . getConfig('lockout_duration', 15) . "*60) );");
		if ( !$q )
			$db->_die();
		
		echo '<div class="info-box">' . $lang->get('acphome_msg_lockout_clear_success', array('ip' => htmlspecialchars($ip))) . '</div>';
	}
	
	$q = $db->sql_query('SELECT COUNT(id) AS fail_count, ipaddr, username, timestamp FROM ' . table_prefix . "lockout AS l\n"
								. "  WHERE timestamp > ( " . time() . " - " . intval(getConfig('lockout_duration', 15)) . "*60 ) GROUP BY ipaddr, username, timestamp ORDER BY COUNT(id) DESC, timestamp DESC;");
	if ( !$q )
		$db->_die();
	
	if ( $db->numrows() > 0 )
	{
		if ( $homewrap )
			echo '<div class="acphome-box notice">';
		echo '<h3>' . $lang->get('acphome_msg_users_locked_out') . '</h3>';
		echo '<p>' . $lang->get('acphome_msg_users_locked_out_hint') . '</p>';
		
		?>
		<div class="tblholder" style="margin-bottom: 10px;">
		<table width="100%" cellspacing="1" cellpadding="4">
			<tr>
				<th><?php echo $lang->get('acphome_th_locked_out_ip'); ?></th>
				<th><?php echo $lang->get('acphome_th_locked_out_username'); ?></th>
				<th><?php echo $lang->get('acphome_th_locked_out_status'); ?></th>
				<th><?php echo $lang->get('acphome_th_locked_out_time'); ?></th>
				<th></th>
			</tr>
		<?php
		
		while ( $row = $db->fetchrow() )
		{
			echo '<tr>';
			echo '<td class="row1">' . htmlspecialchars($row['ipaddr']) . '</td>';
			echo '<td class="row2">' . htmlspecialchars($row['username']) . '</td>';
			// status
			echo '<td class="row1" style="text-align: center;">' .
						( $row['fail_count'] >= getConfig('lockout_threshold', 5)
								? '<b>' . $lang->get('acphome_lbl_locked_out_banned') . '</b>'
								: $lang->get('acphome_lbl_locked_out_warned', array('fail_count' => $row['fail_count']))
						)
						. '</td>';
			// time left
			if ( $row['fail_count'] >= getConfig('lockout_threshold', 5) )
			{
				$expire_time = $row['timestamp'] + ( getConfig('lockout_duration', 15) * 60 );
				$time_left = round(($expire_time - time()) / 60);
				$minutes = $time_left == 1 ? $lang->get('etc_unit_minute') : $lang->get('etc_unit_minutes');
				echo '<td class="row2" style="text-align: center;">' . "$time_left $minutes" . '</td>';
			}
			else
			{
				echo '<td class="row2" style="text-align: center;">&ndash;</td>';
			}
			// action
			$btn_text = $row['fail_count'] >= getConfig('lockout_threshold', 5) ? $lang->get('acphome_btn_lockout_unblock') : $lang->get('acphome_btn_lockout_clear');
			echo '<td class="row1" style="text-align: center;"><a href="#" onclick="ajaxPage(\'' . $paths->nslist['Admin'] . 'UserManager\', \'clear_lockout=' . htmlspecialchars($row['ipaddr']) . '\'); return false;">' . $btn_text . '</a></td>';
			echo '</tr>';
		}
		echo '</table>';
		echo '</div>';
		if ( $homewrap )
			echo '</div>';
	}
	
	$db->free_result();
}