From a9b7f9b1bc88b196a165ebb1bb74d01081d1e6fd Mon Sep 17 00:00:00 2001
From: Michael Foster
Date: Sat, 21 Sep 2013 12:51:23 +1000
Subject: [PATCH] begin implementation of in-built ban appealing
---
inc/config.php | 33 ++++++++++---
inc/functions.php | 34 ++++++++++---
inc/mod/pages.php | 110 +++++++++++++++++++++++------------------
mod.php | 1 +
post.php | 41 +++++++++++++++
stylesheets/style.css | 6 +++
templates/banned.html | 56 ++++++++++++++++++---
templates/mod/ban_appeals.html | 106 +++++++++++++++++++++++++++++++++++++++
templates/mod/dashboard.html | 3 ++
9 files changed, 324 insertions(+), 66 deletions(-)
create mode 100644 templates/mod/ban_appeals.html
diff --git a/inc/config.php b/inc/config.php
index 84de9cfb..2bdd3131 100644
--- a/inc/config.php
+++ b/inc/config.php
@@ -523,9 +523,31 @@
// pure-PHP geolocation library.
$config['country_flags'] = false;
+/*
+* ====================
+* Ban settings
+* ====================
+*/
+
// Require users to see the ban page at least once for a ban even if it has since expired.
$config['require_ban_view'] = true;
+ // Show the post the user was banned for on the "You are banned" page.
+ $config['ban_show_post'] = false;
+
+ // Optional HTML to append to "You are banned" pages. For example, you could include instructions and/or
+ // a link to an email address or IRC chat room to appeal the ban.
+ $config['ban_page_extra'] = '';
+
+ // Allow users to appeal bans through Tinyboard.
+ $config['ban_appeals'] = false;
+
+ // Do not allow users to appeal bans that are shorter than this length (in seconds).
+ $config['ban_appeals_min_length'] = 60 * 60 * 6; // 6 hours
+
+ // How many ban appeals can be made for a single ban?
+ $config['ban_appeals_max'] = 1;
+
/*
* ====================
* Markup settings
@@ -821,13 +843,6 @@
// Automatically remove unnecessary whitespace when compiling HTML files from templates.
$config['minify_html'] = true;
- // Show the post the user was banned for on the "You are banned" page.
- $config['ban_show_post'] = false;
-
- // Optional HTML to append to "You are banned" pages. For example, you could include instructions and/or
- // a link to an email address or IRC chat room to appeal the ban.
- $config['ban_page_extra'] = '';
-
// Display flags (when available). This config option has no effect unless poster flags are enabled (see
// $config['country_flags']). Disable this if you want all previously-assigned flags to be hidden.
$config['display_flags'] = true;
@@ -1322,6 +1337,10 @@
$config['mod']['debug_sql'] = DISABLED;
// Edit the current configuration (via web interface)
$config['mod']['edit_config'] = ADMIN;
+ // View ban appeals
+ $config['mod']['view_ban_appeals'] = MOD;
+ // Accept and deny ban appeals
+ $config['mod']['ban_appeals'] = MOD;
// Config editor permissions
$config['mod']['config'] = array();
diff --git a/inc/functions.php b/inc/functions.php
index cfde4640..078911ea 100644
--- a/inc/functions.php
+++ b/inc/functions.php
@@ -625,11 +625,16 @@ function displayBan($ban) {
$ban['ip'] = $_SERVER['REMOTE_ADDR'];
if ($ban['post'] && isset($ban['post']['board'], $ban['post']['id'])) {
- openBoard($ban['post']['board']);
-
- $query = query(sprintf("SELECT `thumb`, `file` FROM ``posts_%s`` WHERE `id` = " . (int)$ban['post']['id'], $board['uri']));
- if ($_post = $query->fetch(PDO::FETCH_ASSOC)) {
- $ban['post'] = array_merge($ban['post'], $_post);
+ if (openBoard($ban['post']['board'])) {
+
+ $query = query(sprintf("SELECT `thumb`, `file` FROM ``posts_%s`` WHERE `id` = " .
+ (int)$ban['post']['id'], $board['uri']));
+ if ($_post = $query->fetch(PDO::FETCH_ASSOC)) {
+ $ban['post'] = array_merge($ban['post'], $_post);
+ } else {
+ $ban['post']['file'] = 'deleted';
+ $ban['post']['thumb'] = false;
+ }
} else {
$ban['post']['file'] = 'deleted';
$ban['post']['thumb'] = false;
@@ -641,6 +646,21 @@ function displayBan($ban) {
$post = new Thread($ban['post'], null, false, false);
}
}
+
+ $denied_appeals = array();
+ $pending_appeal = false;
+
+ if ($config['ban_appeals']) {
+ $query = query("SELECT `time`, `denied` FROM `ban_appeals` WHERE `ban_id` = " . (int)$ban['id']) or error(db_error());
+ while ($ban_appeal = $query->fetch(PDO::FETCH_ASSOC)) {
+ if ($ban_appeal['denied']) {
+ $denied_appeals[] = $ban_appeal['time'];
+ } else {
+ $pending_appeal = $ban_appeal['time'];
+ }
+ }
+ }
+
// Show banned page and exit
die(
Element('page.html', array(
@@ -651,7 +671,9 @@ function displayBan($ban) {
'config' => $config,
'ban' => $ban,
'board' => $board,
- 'post' => isset($post) ? $post->build(true) : false
+ 'post' => isset($post) ? $post->build(true) : false,
+ 'denied_appeals' => $denied_appeals,
+ 'pending_appeal' => $pending_appeal
)
))
));
diff --git a/inc/mod/pages.php b/inc/mod/pages.php
index 525a3d16..bae00a0f 100644
--- a/inc/mod/pages.php
+++ b/inc/mod/pages.php
@@ -197,37 +197,7 @@ function mod_search($type, $search_query_escaped, $page_no = 1) {
// Form a series of LIKE clauses for the query.
// This gets a little complicated.
- // Escape "escape" character
- $query = str_replace('!', '!!', $query);
-
- // Escape SQL wildcard
- $query = str_replace('%', '!%', $query);
-
- // Use asterisk as wildcard instead
- $query = str_replace('*', '%', $query);
-
- $query = str_replace('`', '!`', $query);
-
- // Array of phrases to match
- $match = array();
-
- // Exact phrases ("like this")
- if (preg_match_all('/"(.+?)"/', $query, $exact_phrases)) {
- $exact_phrases = $exact_phrases[1];
- foreach ($exact_phrases as $phrase) {
- $query = str_replace("\"{$phrase}\"", '', $query);
- $match[] = $pdo->quote($phrase);
- }
- }
-
- // Non-exact phrases (ie. plain keywords)
- $keywords = explode(' ', $query);
- foreach ($keywords as $word) {
- if (empty($word))
- continue;
- $match[] = $pdo->quote($word);
- }
-
+
// Which `field` to search?
if ($type == 'posts')
$sql_field = array('body_nomarkup', 'filename', 'subject', 'filehash', 'ip', 'name', 'trip');
@@ -238,22 +208,6 @@ function mod_search($type, $search_query_escaped, $page_no = 1) {
if ($type == 'log')
$sql_field = 'text';
- // Build the "LIKE 'this' AND LIKE 'that'" etc. part of the SQL query
- $sql_like = '';
- foreach ($match as $phrase) {
- if (!empty($sql_like))
- $sql_like .= ' AND ';
- $phrase = preg_replace('/^\'(.+)\'$/', '\'%$1%\'', $phrase);
- if (is_array($sql_field)) {
- foreach ($sql_field as $field) {
- $sql_like .= '`' . $field . '` LIKE ' . $phrase . ' ESCAPE \'!\' OR';
- }
- $sql_like = preg_replace('/ OR$/', '', $sql_like);
- } else {
- $sql_like .= '`' . $sql_field . '` LIKE ' . $phrase . ' ESCAPE \'!\'';
- }
- }
-
// Compile SQL query
@@ -884,6 +838,68 @@ function mod_bans($page_no = 1) {
mod_page(_('Ban list'), 'mod/ban_list.html', array('bans' => $bans, 'count' => Bans::count()));
}
+function mod_ban_appeals() {
+ global $config, $board;
+
+ if (!hasPermission($config['mod']['view_ban_appeals']))
+ error($config['error']['noaccess']);
+
+ // Remove stale ban appeals
+ query("DELETE FROM ``ban_appeals`` WHERE NOT EXISTS (SELECT 1 FROM ``bans`` WHERE `ban_id` = ``bans``.`id`)")
+ or error(db_error());
+
+ if (isset($_POST['appeal_id']) && (isset($_POST['unban']) || isset($_POST['deny']))) {
+ if (!hasPermission($config['mod']['ban_appeals']))
+ error($config['error']['noaccess']);
+ if (isset($_POST['unban'])) {
+ $query = query("SELECT `ban_id` FROM ``ban_appeals`` WHERE `id` = " .
+ (int)$_POST['appeal_id']) or error(db_error());
+ if ($ban_id = $query->fetchColumn()) {
+ Bans::delete($ban_id);
+ query("DELETE FROM ``ban_appeals`` WHERE `id` = " . (int)$_POST['appeal_id']) or error(db_error());
+ }
+ } else {
+ query("UPDATE ``ban_appeals`` SET `denied` = 1 WHERE `id` = " . (int)$_POST['appeal_id']) or error(db_error());
+ }
+
+ header('Location: ?/ban-appeals', true, $config['redirect_http']);
+ return;
+ }
+
+ $query = query("SELECT *, ``ban_appeals``.`id` AS `id` FROM ``ban_appeals``
+ LEFT JOIN ``bans`` ON `ban_id` = ``bans``.`id`
+ WHERE `denied` != 1 ORDER BY `time`") or error(db_error());
+ $ban_appeals = $query->fetchAll(PDO::FETCH_ASSOC);
+ foreach ($ban_appeals as &$ban) {
+ if ($ban['post'])
+ $ban['post'] = json_decode($ban['post'], true);
+ $ban['mask'] = Bans::range_to_string(array($ban['ipstart'], $ban['ipend']));
+
+ if ($ban['post'] && isset($ban['post']['board'], $ban['post']['id'])) {
+ if (openBoard($ban['post']['board'])) {
+ $query = query(sprintf("SELECT `thumb`, `file` FROM ``posts_%s`` WHERE `id` = " .
+ (int)$ban['post']['id'], $board['uri']));
+ if ($_post = $query->fetch(PDO::FETCH_ASSOC)) {
+ $ban['post'] = array_merge($ban['post'], $_post);
+ } else {
+ $ban['post']['file'] = 'deleted';
+ $ban['post']['thumb'] = false;
+ }
+ } else {
+ $ban['post']['file'] = 'deleted';
+ $ban['post']['thumb'] = false;
+ }
+
+ if ($ban['post']['thread']) {
+ $ban['post'] = new Post($ban['post']);
+ } else {
+ $ban['post'] = new Thread($ban['post'], null, false, false);
+ }
+ }
+ }
+
+ mod_page(_('Ban appeals'), 'mod/ban_appeals.html', array('ban_appeals' => $ban_appeals));
+}
function mod_lock($board, $unlock, $post) {
global $config;
diff --git a/mod.php b/mod.php
index 6ef4644d..ca6d971e 100644
--- a/mod.php
+++ b/mod.php
@@ -59,6 +59,7 @@ $pages = array(
'/IP/([\w.:]+)/remove_note/(\d+)' => 'ip_remove_note', // remove note from ip address
'/bans' => 'bans', // ban list
'/bans/(\d+)' => 'bans', // ban list
+ '/ban-appeals' => 'ban_appeals', // view ban appeals
'/search' => 'search_redirect', // search
'/search/(posts|IP_notes|bans|log)/(.+)/(\d+)' => 'search', // search
diff --git a/post.php b/post.php
index 1328bed3..b1528870 100644
--- a/post.php
+++ b/post.php
@@ -763,6 +763,47 @@ if (isset($_POST['delete'])) {
'id' => $id
));
}
+} elseif (isset($_POST['appeal'])) {
+ if (!isset($_POST['ban_id']))
+ error($config['error']['bot']);
+
+ $ban_id = (int)$_POST['ban_id'];
+
+ $bans = Bans::find($_SERVER['REMOTE_ADDR']);
+ foreach ($bans as $_ban) {
+ if ($_ban['id'] == $ban_id) {
+ $ban = $_ban;
+ break;
+ }
+ }
+
+ if (!isset($ban)) {
+ error(_("That ban doesn't exist or is not for you."));
+ }
+
+ if ($ban['expires'] && $ban['expires'] - $ban['created'] <= $config['ban_appeals_min_length']) {
+ error(_("You cannot appeal a ban of this length."));
+ }
+
+ $query = query("SELECT `denied` FROM ``ban_appeals`` WHERE `ban_id` = $ban_id") or error(db_error());
+ $ban_appeals = $query->fetchAll(PDO::FETCH_COLUMN);
+
+ if (count($ban_appeals) >= $config['ban_appeals_max']) {
+ error(_("You cannot appeal this ban again."));
+ }
+
+ foreach ($ban_appeals as $is_denied) {
+ if (!$is_denied)
+ error(_("There is already a pending appeal for this ban."));
+ }
+
+ $query = prepare("INSERT INTO ``ban_appeals`` VALUES (NULL, :ban_id, :time, :message, 0)");
+ $query->bindValue(':ban_id', $ban_id, PDO::PARAM_INT);
+ $query->bindValue(':time', time(), PDO::PARAM_INT);
+ $query->bindValue(':message', $_POST['appeal']);
+ $query->execute() or error(db_error($query));
+
+ displayBan($ban);
} else {
if (!file_exists($config['has_installed'])) {
header('Location: install.php', true, $config['redirect_http']);
diff --git a/stylesheets/style.css b/stylesheets/style.css
index ef12a5f5..31b866ef 100644
--- a/stylesheets/style.css
+++ b/stylesheets/style.css
@@ -424,4 +424,10 @@ table.mod.config-editor input[type="text"] {
p.intro.thread-hidden {
margin: 0px;
padding: 0px;
+}
+form.ban-appeal {
+ margin: 9px 20px;
+}
+form.ban-appeal textarea {
+ display: block;
}
\ No newline at end of file
diff --git a/templates/banned.html b/templates/banned.html
index 79648156..666521a4 100644
--- a/templates/banned.html
+++ b/templates/banned.html
@@ -77,16 +77,60 @@
{% trans %}Your IP address is{% endtrans %} {{ ban.ip }}.
- {% if post %}
+ {% if config.ban_page_extra %}
+ {{ config.ban_page_extra }}
+ {% endif %}
+
+ {% if post and config.ban_show_post %}
- You were banned for the following post on {{ board.url }}:
+ {% trans %}You were banned for the following post on {% endtrans %}{{ board.url }}:
{{ post }}
{% endif %}
- {% if config.ban_page_extra %}
- {{ config.ban_page_extra }}
+ {% if config.ban_appeals %}
+
+ {% if pending_appeal %}
+
+ {% trans %}You submitted an appeal for this ban on{% endtrans %}
+ {{ pending_appeal|date(config.ban_date) }}. {% trans %}It is still pending{% endtrans %}.
+
+ {% elseif denied_appeals|length >= config.ban_appeals_max %}
+ {% if denied_appeals|length == 1 %}
+
+ {% trans %}You appealed this ban on{% endtrans %}
+ {{ denied_appeals[0]|date(config.ban_date) }}
+ {% trans %}and it was denied. You may not appeal this ban again.{% endtrans %}
+
+ {% else %}
+ {% trans %}You have submitted the maximum number of ban appeals allowed. You may not appeal this ban again.{% endtrans %}
+ {% endif %}
+ {% else %}
+ {% if denied_appeals|length %}
+ {% if denied_appeals|length == 1 %}
+
+ {% trans %}You appealed this ban on{% endtrans %}
+ {{ denied_appeals[0]|date(config.ban_date) }}
+ {% trans %}and it was denied.{% endtrans %}
+
+ {% trans %}You may appeal this ban again. Please enter your reasoning below.{% endtrans %}
+ {% else %}
+
+ {% trans %}You last appealed this ban on{% endtrans %}
+ {{ denied_appeals[denied_appeals|length - 1]|date(config.ban_date) }}
+ {% trans %}and it was denied.{% endtrans %}
+
+ {% trans %}You may appeal this ban again. Please enter your reasoning below.{% endtrans %}
+ {% endif %}
+ {% else %}
+ {% trans %}You may appeal this ban. Please enter your reasoning below.{% endtrans %}
+ {% endif %}
+
+ {% endif %}
{% endif %}
-{% endfilter %}
-
+{% endfilter %}
\ No newline at end of file
diff --git a/templates/mod/ban_appeals.html b/templates/mod/ban_appeals.html
new file mode 100644
index 00000000..f43b7db5
--- /dev/null
+++ b/templates/mod/ban_appeals.html
@@ -0,0 +1,106 @@
+{% for ban in ban_appeals %}
+
+
+
+
+{% endfor %}
\ No newline at end of file
diff --git a/templates/mod/dashboard.html b/templates/mod/dashboard.html
index e2f7507d..916f0c03 100644
--- a/templates/mod/dashboard.html
+++ b/templates/mod/dashboard.html
@@ -86,6 +86,9 @@
{% if mod|hasPermission(config.mod.view_banlist) %}
{% trans 'Ban list' %}
{% endif %}
+ {% if config.ban_appeals and mod|hasPermission(config.mod.view_ban_appeals) %}
+ {% trans 'Ban appeals' %}
+ {% endif %}
{% if mod|hasPermission(config.mod.manageusers) %}
{% trans 'Manage users' %}
{% elseif mod|hasPermission(config.mod.change_password) %}