diff --git a/classes/autoenable.class.php b/classes/autoenable.class.php new file mode 100644 index 00000000..94d6945c --- /dev/null +++ b/classes/autoenable.class.php @@ -0,0 +1,355 @@ +This may be because a request is already pending for your username, or because a recent request was denied.

You are encouraged to discuss this with staff by visiting %s on %s"; + + // The default request received message + const RECEIVED_MESSAGE = "Your request to re-enable your account has been received. You can expect a reply message in your email within 48 hours.
If you do not receive an email after 48 hours have passed, please visit us on IRC for assistance."; + + /** + * Handle a new enable request + * + * @param string $Username The user's username + * @param string $Email The user's email address + * @return string The output + */ + public static function new_request($Username, $Email) { + if (empty($Username)) { + header("Location: login.php"); + die(); + } + + // Get the user's ID + G::$DB->query(" + SELECT um.ID + FROM users_main AS um + JOIN users_info ui ON ui.UserID = um.ID + WHERE um.Username = '$Username' + AND um.Enabled = '2'"); + + if (G::$DB->has_results()) { + // Make sure the user can make another request + list($UserID) = G::$DB->next_record(); + G::$DB->query(" + SELECT 1 FROM users_enable_requests + WHERE UserID = '$UserID' + AND ( + ( + Timestamp > NOW() - INTERVAL 1 WEEK + AND HandledTimestamp IS NULL + ) + OR + ( + Timestamp > NOW() - INTERVAL 2 MONTH + AND + (Outcome = '".self::DENIED."' + OR Outcome = '".self::DISCARDED."') + ) + )"); + } + + $IP = $_SERVER['REMOTE_ADDR']; + + if (G::$DB->has_results() || !isset($UserID)) { + // User already has/had a pending activation request or username is invalid + $Output = sprintf(self::REJECTED_MESSAGE, BOT_DISABLED_CHAN, BOT_SERVER); + if (isset($UserID)) { + Tools::update_user_notes($UserID, sqltime() . " - Enable request rejected from $IP\n\n"); + } + } else { + // New disable activation request + $UserAgent = db_string($_SERVER['HTTP_USER_AGENT']); + + G::$DB->query(" + INSERT INTO users_enable_requests + (UserID, Email, IP, UserAgent, Timestamp) + VALUES ('$UserID', '$Email', '$IP', '$UserAgent', '".sqltime()."')"); + + // Cache the number of requests for the modbar + G::$Cache->increment_value(self::CACHE_KEY_NAME); + setcookie('username', '', time() - 60 * 60, '/', '', false); + $Output = self::RECEIVED_MESSAGE; + Tools::update_user_notes($UserID, sqltime() . " - Enable request " . G::$DB->inserted_id() . " received from $IP\n\n"); + } + + return $Output; + } + + /* + * Handle requests + * + * @param int|int[] $IDs An array of IDs, or a single ID + * @param int $Status The status to mark the requests as + * @param string $Comment The staff member comment + */ + public static function handle_requests($IDs, $Status, $Comment) { + if ($Status != self::APPROVED && $Status != self::DENIED && $Status != self::DISCARDED) { + error(404); + } + + $UserInfo = array(); + $IDs = (!is_array($IDs)) ? [$IDs] : $IDs; + + if (count($IDs) == 0) { + error(404); + } + + foreach ($IDs as $ID) { + if (!is_number($ID)) { + error(404); + } + } + + G::$DB->query("SELECT Email, ID, UserID + FROM users_enable_requests + WHERE ID IN (".implode(',', $IDs).") + AND Outcome IS NULL"); + $Results = G::$DB->to_array(false, MYSQLI_NUM); + + if ($Status != self::DISCARDED) { + // Prepare email + require(SERVER_ROOT . '/classes/templates.class.php'); + $TPL = NEW TEMPLATE; + if ($Status == self::APPROVED) { + $TPL->open(SERVER_ROOT . '/templates/enable_request_accepted.tpl'); + $TPL->set('SITE_URL', NONSSL_SITE_URL); + } else { + $TPL->open(SERVER_ROOT . '/templates/enable_request_denied.tpl'); + } + + $TPL->set('SITE_NAME', SITE_NAME); + + foreach ($Results as $Result) { + list($Email, $ID, $UserID) = $Result; + $UserInfo[] = array($ID, $UserID); + + if ($Status == self::APPROVED) { + // Generate token + $Token = db_string(Users::make_secret()); + G::$DB->query(" + UPDATE users_enable_requests + SET Token = '$Token' + WHERE ID = '$ID'"); + $TPL->set('TOKEN', $Token); + } + + // Send email + $Subject = "Your enable request for " . SITE_NAME . " has been "; + $Subject .= ($Status == self::APPROVED) ? 'approved' : 'denied'; + + Misc::send_email($Email, $Subject, $TPL->get(), 'noreply'); + } + } else { + foreach ($Results as $Result) { + list(, $ID, $UserID) = $Result; + $UserInfo[] = array($ID, $UserID); + } + } + + // User notes stuff + G::$DB->query(" + SELECT Username + FROM users_main + WHERE ID = '" . G::$LoggedUser['ID'] . "'"); + list($StaffUser) = G::$DB->next_record(); + + foreach ($UserInfo as $User) { + list($ID, $UserID) = $User; + $BaseComment = sqltime() . " - Enable request $ID " . strtolower(self::get_outcome_string($Status)) . ' by [user]'.$StaffUser.'[/user]'; + $BaseComment .= (!empty($Comment)) ? "\nReason: $Comment\n\n" : "\n\n"; + Tools::update_user_notes($UserID, $BaseComment); + } + + // Update database values and decrement cache + G::$DB->query(" + UPDATE users_enable_requests + SET HandledTimestamp = '".sqltime()."', + CheckedBy = '".G::$LoggedUser['ID']."', + Outcome = '$Status' + WHERE ID IN (".implode(',', $IDs).")"); + G::$Cache->decrement_value(self::CACHE_KEY_NAME, count($IDs)); + } + + /** + * Unresolve a discarded request + * + * @param int $ID The request ID + */ + public static function unresolve_request($ID) { + $ID = (int) $ID; + + if (empty($ID)) { + error(404); + } + + G::$DB->query(" + SELECT UserID + FROM users_enable_requests + WHERE Outcome = '" . self::DISCARDED . "' + AND ID = '$ID'"); + + if (!G::$DB->has_results()) { + error(404); + } else { + list($UserID) = G::$DB->next_record(); + } + + G::$DB->query(" + SELECT Username + FROM users_main + WHERE ID = '" . G::$LoggedUser['ID'] . "'"); + list($StaffUser) = G::$DB->next_record(); + + Tools::update_user_notes($UserID, sqltime() . " - Enable request $ID unresolved by [user]" . $StaffUser . '[/user]' . "\n\n"); + G::$DB->query(" + UPDATE users_enable_requests + SET Outcome = NULL, HandledTimestamp = NULL, CheckedBy = NULL + WHERE ID = '$ID'"); + G::$Cache->increment_value(self::CACHE_KEY_NAME); + } + + /** + * Get the corresponding outcome string for a numerical value + * + * @param int $Outcome The outcome integer + * @return string The formatted output string + */ + public static function get_outcome_string($Outcome) { + if ($Outcome == self::APPROVED) { + $String = "Approved"; + } else if ($Outcome == self::DENIED) { + $String = "Rejected"; + } else if ($Outcome == self::DISCARDED) { + $String = "Discarded"; + } else { + $String = "---"; + } + + return $String; + } + + /** + * Handle a user's request to enable an account + * + * @param string $Token The token + * @return string The error output, or an empty string + */ + public static function handle_token($Token) { + $Token = db_string($Token); + G::$DB->query(" + SELECT UserID, HandledTimestamp + FROM users_enable_requests + WHERE Token = '$Token'"); + + if (G::$DB->has_results()) { + list($UserID, $Timestamp) = G::$DB->next_record(); + G::$DB->query("UPDATE users_enable_requests SET Token = NULL WHERE Token = '$Token'"); + if ($Timestamp < time_minus(3600 * 48)) { + // Old request + Tools::update_user_notes($UserID, sqltime() . " - Tried to use an expired enable token from ".$_SERVER['REMOTE_ADDR']."\n\n"); + $Err = "Token has expired. Please visit ".BOT_DISABLED_CHAN." on ".BOT_SERVER." to discuss this with staff."; + } else { + // Good request, decrement cache value and enable account + G::$Cache->decrement_value(AutoEnable::CACHE_KEY_NAME); + G::$DB->query("UPDATE users_main SET Enabled = '1' WHERE ID = '$UserID'"); + G::$DB->query("UPDATE users_info SET BanReason = '0' WHERE UserID = '$UserID'"); + $Err = "Your account has been enabled. You may now log in."; + } + } else { + $Err = "Invalid token."; + } + + return $Err; + } + + /** + * Build the search query, from the searchbox inputs + * + * @param int $UserID The user ID + * @param string $IP The IP + * @param string $SubmittedTimestamp The timestamp representing when the request was submitted + * @param int $HandledUserID The ID of the user that handled the request + * @param string $HandledTimestamp The timestamp representing when the request was handled + * @param int $OutcomeSearch The outcome of the request + * @param boolean $Checked Should checked requests be included? + * @return array The WHERE conditions for the query + */ + public static function build_search_query($Username, $IP, $SubmittedBetween, $SubmittedTimestamp1, $SubmittedTimestamp2, $HandledUsername, $HandledBetween, $HandledTimestamp1, $HandledTimestamp2, $OutcomeSearch, $Checked) { + $Where = array(); + + if (!empty($Username)) { + $Where[] = "um1.Username = '$Username'"; + } + + if (!empty($IP)) { + $Where[] = "uer.IP = '$IP'"; + } + + if (!empty($SubmittedTimestamp1)) { + switch($SubmittedBetween) { + case 'on': + $Where[] = "DATE(uer.Timestamp) = DATE('$SubmittedTimestamp1')"; + break; + case 'before': + $Where[] = "DATE(uer.Timestamp) < DATE('$SubmittedTimestamp1')"; + break; + case 'after': + $Where[] = "DATE(uer.Timestamp) > DATE('$SubmittedTimestamp1')"; + break; + case 'between': + if (!empty($SubmittedTimestamp2)) { + $Where[] = "DATE(uer.Timestamp) BETWEEN DATE('$SubmittedTimestamp1') AND DATE('$SubmittedTimestamp2')"; + } + break; + default: + break; + } + } + + if (!empty($HandledTimestamp1)) { + switch($HandledBetween) { + case 'on': + $Where[] = "DATE(uer.HandledTimestamp) = DATE('$HandledTimestamp1')"; + break; + case 'before': + $Where[] = "DATE(uer.HandledTimestamp) < DATE('$HandledTimestamp1')"; + break; + case 'after': + $Where[] = "DATE(uer.HandledTimestamp) > DATE('$HandledTimestamp1')"; + break; + case 'between': + if (!empty($HandledTimestamp2)) { + $Where[] = "DATE(uer.HandledTimestamp) BETWEEN DATE('$HandledTimestamp1') AND DATE('$HandledTimestamp2')"; + } + break; + default: + break; + } + } + + if (!empty($HandledUsername)) { + $Where[] = "um2.Username = '$HandledUsername'"; + } + + if (!empty($OutcomeSearch)) { + $Where[] = "uer.Outcome = '$OutcomeSearch'"; + } + + if ($Checked) { + // This is to skip the if statement in enable_requests.php + $Where[] = "(uer.Outcome IS NULL OR uer.Outcome IS NOT NULL)"; + } + + return $Where; + } +} diff --git a/classes/config.template b/classes/config.template index e5623846..aa43f29b 100644 --- a/classes/config.template +++ b/classes/config.template @@ -69,6 +69,9 @@ define('STARTING_INVITES', 0); //# of invites to give to newly registered users define('BLOCK_TOR', false); //Set to true to block Tor users define('BLOCK_OPERA_MINI', false); //Set to true to block Opera Mini proxy define('DONOR_INVITES', 2); +if (!defined('FEATURE_EMAIL_REENABLE')) { + define('FEATURE_EMAIL_REENABLE', true); +} // User class IDs needed for automatic promotions. Found in the 'permissions' table // Name of class Class ID (NOT level) diff --git a/design/privateheader.php b/design/privateheader.php index 73d0587c..10fed2fb 100644 --- a/design/privateheader.php +++ b/design/privateheader.php @@ -496,6 +496,18 @@ if (check_perms('admin_reports')) { } +if (check_perms('users_mod') && FEATURE_EMAIL_REENABLE) { + $NumEnableRequests = G::$Cache->get_value(AutoEnable::CACHE_KEY_NAME); + if ($NumEnableRequests === false) { + G::$DB->query("SELECT COUNT(1) FROM users_enable_requests WHERE Outcome IS NULL"); + list($NumEnableRequests) = G::$DB->next_record(); + G::$Cache->cache_value(AutoEnable::CACHE_KEY_NAME, $NumEnableRequests); + } + + if ($NumEnableRequests > 0) { + $ModBar[] = '' . $NumEnableRequests . " Enable requests"; + } +} ?> diff --git a/enable.php b/enable.php new file mode 100644 index 00000000..3e83e9a2 --- /dev/null +++ b/enable.php @@ -0,0 +1 @@ +
Back"; +} +if ((empty($_POST['submit']) || empty($_POST['username'])) && !isset($Output)) { ?>

Your account has been disabled.
-This is either due to inactivity or rule violation(s).
-To discuss this with staff, come to our IRC network at:
+This is either due to inactivity or rule violation(s).

+ +If you believe your account was in good standing and was disabled for inactivity, you may request it be re-enabled via email using the form below.
+Please note that you will need access to the email account associated with your account at What.CD for this to work;
+if you do not, please see the section after this form.

+
+ + +


+ +If you are unsure why your account is disabled, or you wish to discuss this with staff, come to our IRC network at:
And join

Be honest. At this point, lying will get you nowhere.


@@ -40,7 +60,7 @@ Please use your username. username. diff --git a/sections/login/index.php b/sections/login/index.php index 2e80bf3d..526d2ab2 100644 --- a/sections/login/index.php +++ b/sections/login/index.php @@ -330,7 +330,9 @@ else { log_attempt($UserID); if ($Enabled == 2) { - header('location:login.php?action=disabled'); + // Save the username in a cookie for the disabled page + setcookie('username', db_string($_POST['username']), time() + 60 * 60, '/', '', false); + header('Location: login.php?action=disabled'); } elseif ($Enabled == 0) { $Err = 'Your account has not been confirmed.
Please check your email.'; } diff --git a/sections/tools/index.php b/sections/tools/index.php index 7994e27f..a0261c0b 100644 --- a/sections/tools/index.php +++ b/sections/tools/index.php @@ -77,6 +77,19 @@ switch ($_REQUEST['action']) { include(SERVER_ROOT.'/sections/tools/managers/whitelist_alter.php'); break; + case 'enable_requests': + include(SERVER_ROOT.'/sections/tools/managers/enable_requests.php'); + break; + case 'ajax_take_enable_request': + if (FEATURE_EMAIL_REENABLE) { + include(SERVER_ROOT.'/sections/tools/managers/ajax_take_enable_request.php'); + } else { + // Prevent post requests to the ajax page + header("Location: tools.php"); + die(); + } + break; + case 'login_watch': include(SERVER_ROOT.'/sections/tools/managers/login_watch.php'); break; diff --git a/sections/tools/managers/ajax_take_enable_request.php b/sections/tools/managers/ajax_take_enable_request.php new file mode 100644 index 00000000..1d6c4f3c --- /dev/null +++ b/sections/tools/managers/ajax_take_enable_request.php @@ -0,0 +1,48 @@ + "success")); + +function json_error($Message) { + echo json_encode(array("status" => $Message)); + die(); +} diff --git a/sections/tools/managers/enable_requests.php b/sections/tools/managers/enable_requests.php new file mode 100644 index 00000000..be6213ed --- /dev/null +++ b/sections/tools/managers/enable_requests.php @@ -0,0 +1,315 @@ + 'uer.Timestamp', + 'outcome' => 'uer.Outcome', + 'handled_timestamp' => 'uer.HandledTimestamp'); + +$Where = []; +$Joins = []; + +// Default orderings +$OrderBy = "uer.Timestamp"; +$OrderWay = "DESC"; + +// Build query for different views +if ($_GET['view'] == 'perfect') { + $Where[] = "um.Email = uer.Email"; + $Joins[] = "JOIN users_main um ON um.ID = uer.UserID"; + $Where[] = "uer.IP = (SELECT IP FROM users_history_ips uhi1 WHERE uhi1.StartTime = (SELECT MAX(StartTime) FROM users_history_ips uhi2 WHERE uhi2.UserID = uer.UserID ORDER BY StartTime DESC LIMIT 1))"; + $Where[] = "(SELECT 1 FROM users_history_ips uhi WHERE uhi.IP = uer.IP AND uhi.UserID != uer.UserID) IS NULL"; + $Where[] = "ui.BanReason = '3'"; +} else if ($_GET['view'] == 'minus_ip') { + $Where[] = "um.Email = uer.Email"; + $Joins[] = "JOIN users_main um ON um.ID = uer.UserID"; + $Where[] = "ui.BanReason = '3'"; +} else if ($_GET['view'] == 'invalid_email') { + $Joins[] = "JOIN users_main um ON um.ID = uer.UserID"; + $Where[] = "um.Email != uer.Email"; +} else if ($_GET['view'] == 'ip_overlap') { + $Joins[] = "JOIN users_history_ips uhi ON uhi.IP = uer.IP AND uhi.UserID != uer.UserID"; +} else if ($_GET['view'] == 'manual_disable') { + $Where[] = "ui.BanReason != '3'"; +} else { + $Joins[] = ''; +} +// End views + +// Build query further based on search +if (isset($_GET['search'])) { + $Username = db_string($_GET['username']); + $IP = db_string($_GET['ip']); + $SubmittedBetween = db_string($_GET['submitted_between']); + $SubmittedTimestamp1 = db_string($_GET['submitted_timestamp1']); + $SubmittedTimestamp2 = db_string($_GET['submitted_timestamp2']); + $HandledUsername = db_string($_GET['handled_username']); + $HandledBetween = db_string($_GET['handled_between']); + $HandledTimestamp1 = db_string($_GET['handled_timestamp1']); + $HandledTimestamp2 = db_string($_GET['handled_timestamp2']); + $OutcomeSearch = (int) $_GET['outcome_search']; + $Checked = (isset($_GET['show_checked'])); + + if (array_key_exists($_GET['order'], $OrderBys)) { + $OrderBy = $OrderBys[$_GET['order']]; + } + + if ($_GET['way'] == "asc" || $_GET['way'] == "desc") { + $OrderWay = $_GET['way']; + } + + if (!empty($Username)) { + $Joins[] = "JOIN users_main um1 ON um1.ID = uer.UserID"; + } + + if (!empty($HandledUsername)) { + $Joins[] = "JOIN users_main um2 ON um2.ID = uer.CheckedBy"; + } + + $Where = array_merge($Where, AutoEnable::build_search_query($Username, + $IP, $SubmittedBetween, $SubmittedTimestamp1, $SubmittedTimestamp2, $HandledUsername, + $HandledBetween, $HandledTimestamp1, $HandledTimestamp2, $OutcomeSearch, $Checked)); +} +// End search queries + +$ShowChecked = $Checked || !empty($HandledUsername) || !empty($HandledTimestamp1) || !empty($OutcomeSearch); + +if (!$ShowChecked || count($Where) == 0) { + // If no search is entered, add this to the query to only show unchecked requests + $Where[] = 'Outcome IS NULL'; +} + +$QueryID = $DB->query(" + SELECT SQL_CALC_FOUND_ROWS + uer.ID, + uer.UserID, + uer.Email, + uer.IP, + uer.UserAgent, + uer.Timestamp, + ui.BanReason, + uer.CheckedBy, + uer.HandledTimestamp, + uer.Outcome + FROM users_enable_requests AS uer + JOIN users_info ui ON ui.UserID = uer.UserID + ".implode(' ', $Joins)." + WHERE + ".implode(' AND ', $Where)." + ORDER BY $OrderBy $OrderWay + LIMIT $Limit"); + +$DB->query("SELECT FOUND_ROWS()"); +list($NumResults) = $DB->next_record(); +$DB->set_query_id($QueryID); +?> + +
+

Auto-Enable Requests

+
+
+ Main + Perfect + Perfect Minus IP + Invalid Email + IP Overlap + Manual Disable + Search + Scores +

+
+ + + + + +query(" + SELECT COUNT(CheckedBy), CheckedBy + FROM users_enable_requests + WHERE CheckedBy IS NOT NULL + GROUP BY CheckedBy + ORDER BY COUNT(CheckedBy) DESC + LIMIT 50"); + while (list($Checked, $UserID) = $DB->next_record()) { ?> + + + + +set_query_id($QueryID); ?> + +
> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Username
IP Address
Submitted Timestamp +   + + /> +
Handled By Username
Handled Timestamp +   + + /> +
Outcome + +
Include Checked />
Order By +   + +
+
+
+ 0) { ?> + + + + + + + + + + + + + + + + + next_record()) { + $Row = $Row === 'a' ? 'b' : 'a'; +?> + + + + + + + + + + + + + + + + + + + + + +
UsernameEmail AddressIP AddressUser AgentAgeBan ReasonCommentSubmitOutcome
+ + + + Inactivity' : 'Other'?> + + + + + + Unresolve + +
+ +
+ + + +
+ +

No new pending auto enable requests

+