From 7d8432d5f01ce526ce814e21fa65f53516a7854e Mon Sep 17 00:00:00 2001 From: fallenPineapple Date: Tue, 2 May 2017 04:03:14 +0200 Subject: [PATCH] Implementing Warning System to Issue Warnings for Posts Auto-tagging was omitted as warnings are visible anyway. Co-Authored-By: Discomrade --- inc/bans.php | 80 ++++++++++++++++++++++++++- inc/config.php | 19 +++++++ inc/functions.php | 90 ++++++++++++++++++++++++++++++- inc/mod/pages.php | 64 ++++++++++++++++++++++ install.php | 15 +++++- install.sql | 20 ++++++- mod.php | 1 + stylesheets/style.css | 7 +++ templates/mod/warning_form.html | 62 +++++++++++++++++++++ templates/post/post_controls.html | 3 ++ templates/post_reply.html | 3 ++ templates/post_thread.html | 3 ++ templates/warning.html | 38 +++++++++++++ 13 files changed, 401 insertions(+), 4 deletions(-) create mode 100644 templates/mod/warning_form.html create mode 100644 templates/warning.html diff --git a/inc/bans.php b/inc/bans.php index 84d30cbf..57194d75 100644 --- a/inc/bans.php +++ b/inc/bans.php @@ -145,7 +145,32 @@ class Bans { return $ban_list; } - + + static public function findWarning($ip, $get_mod_info = false) { + global $config; + + $query = prepare('SELECT ``warnings``.*' . ($get_mod_info ? ', `username`' : '') . ' FROM ``warnings`` + ' . ($get_mod_info ? 'LEFT JOIN ``mods`` ON ``mods``.`id` = `creator`' : '') . ' + WHERE `ip` = :ip'); + + $query->bindValue(':ip', inet_pton($ip)); + $query->execute() or error(db_error($query)); + + $warning_list = array(); + + while ($warning = $query->fetch(PDO::FETCH_ASSOC)) { + if ($warning['seen']) { + // self::deleteWarning($warning['id']); + } else { + if ($warning['post']) + $warning['post'] = json_decode($warning['post'], true); + $warning_list[] = $warning; + } + } + + return $warning_list; + } + static public function stream_json($out = false, $filter_ips = false, $filter_staff = false, $board_access = false) { $query = query("SELECT ``bans``.*, `username` FROM ``bans`` LEFT JOIN ``mods`` ON ``mods``.`id` = `creator` @@ -207,6 +232,10 @@ class Bans { rebuildThemes('bans'); } + static public function seenWarning($warning_id) { + $query = query("UPDATE ``warnings`` SET `seen` = 1 WHERE `id` = " . (int)$warning_id) or error(db_error()); + } + static public function purge() { $query = query("DELETE FROM ``bans`` WHERE `expires` IS NOT NULL AND `expires` < " . time() . " AND `seen` = 1") or error(db_error()); rebuildThemes('bans'); @@ -310,4 +339,53 @@ class Bans { return $pdo->lastInsertId(); } + + static public function new_warning($cloaked_mask, $reason, $warning_board = false, $mod_id = false, $post = false) { + $mask = uncloak_mask($cloaked_mask); + + global $mod, $pdo, $board, $config; + + if ($mod_id === false) { + $mod_id = isset($mod['id']) ? $mod['id'] : -1; + } + + $range = self::parse_range($mask); + $mask = self::range_to_string($range); + $cloaked_mask = cloak_mask($mask); + + $query = prepare("INSERT INTO ``warnings`` VALUES (NULL, :ip, :time, :board, :mod, :reason, 0, :post)"); + + $query->bindValue(':ip', $range[0]); + $query->bindValue(':mod', $mod_id); + $query->bindValue(':time', time()); + + if ($reason !== '') { + $reason = escape_markup_modifiers($reason); + markup($reason); + $query->bindValue(':reason', $reason); + } else + $query->bindValue(':reason', null, PDO::PARAM_NULL); + + if ($warning_board) + $query->bindValue(':board', $warning_board); + else + $query->bindValue(':board', null, PDO::PARAM_NULL); + + if ($post) { + $post['board'] = $board['uri']; + $query->bindValue(':post', json_encode($post)); + } else + $query->bindValue(':post', null, PDO::PARAM_NULL); + + $query->execute() or error(db_error($query)); + + if (isset($mod['id']) && $mod['id'] == $mod_id) { + modLog('Issued a new warning for ' . + (filter_var($mask, FILTER_VALIDATE_IP) !== false ? "$cloaked_mask" : $cloaked_mask) . + ' (#' . $pdo->lastInsertId() . ')' . + ' with ' . ($reason ? 'reason: ' . utf8tohtml($reason) . '' : 'no reason')); + } + + return $pdo->lastInsertId(); + } } diff --git a/inc/config.php b/inc/config.php index a0bdbf7d..0ecfc7b9 100644 --- a/inc/config.php +++ b/inc/config.php @@ -675,6 +675,15 @@ // Show moderator name on ban page. $config['show_modname'] = false; + // Show the post the user was issued warning for on the "You were issued a warning" page. + $config['warning_show_post'] = &$config['ban_show_post']; + + // Optional HTML to append to "You were issued a warning. For example, you could include instructions and/or + // a link to an email address or IRC chat room to appeal the ban. + $config['warning_page_extra'] = ''; + + + /* * ==================== * Markup settings @@ -1377,6 +1386,7 @@ // Mod links (full HTML). $config['mod']['link_delete'] = '[D]'; + $config['mod']['link_warning'] = '[W]'; $config['mod']['link_ban'] = '[B]'; $config['mod']['link_bandelete'] = '[B&D]'; $config['mod']['link_deletefile'] = '[F]'; @@ -1451,6 +1461,13 @@ // HTML to append to post bodies for public bans messages (where "%s" is the message). $config['mod']['ban_message'] = '(%s)'; + // Check public warning message by default. + $config['mod']['check_warning_message'] = false; + // Default public warning message + $config['mod']['default_warning_message'] = _('User was warned for this post'); + // HTML to append to post bodies for public warning messages (where "%s" is the message). + $config['mod']['warning_message'] = '(%s)'; + // When moving a thread to another board and choosing to keep a "shadow thread", an automated post (with // a capcode) will be made, linking to the new location for the thread. "%s" will be replaced with a // standard cross-board post citation (>>>/board/xxx) @@ -1521,6 +1538,8 @@ $config['mod']['show_ip'] = MOD; // Delete a post $config['mod']['delete'] = JANITOR; + // Warn a user for a post + $config['mod']['warning'] = JANITOR; // Ban a user for a post $config['mod']['ban'] = MOD; // Ban and delete (one click; instant) diff --git a/inc/functions.php b/inc/functions.php index a5cb9f07..a82dcd64 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -909,10 +909,15 @@ function checkBan($board = false) { if (!isset($_SERVER['REMOTE_ADDR'])) { // Server misconfiguration return; - } + } if (event('check-ban', $board)) return true; + + + // Check for Warnings + checkWarning($board); + $ips = array(); @@ -960,6 +965,89 @@ function checkBan($board = false) { cache::set('purged_bans_last', time()); } + + + +function displayWarning($warning) { + global $config, $board; + + if (!$warning['seen']) { + Bans::seenWarning($warning['id']); + } + + $warning['ip'] = $_SERVER['REMOTE_ADDR']; + + if ($warning['post'] && isset($warning['post']['board'], $warning['post']['id'])) { + if (openBoard($warning['post']['board'])) { + $query = query(sprintf("SELECT `files` FROM ``posts_%s`` WHERE `id` = " . + (int)$warning['post']['id'], $board['uri'])); + if ($_post = $query->fetch(PDO::FETCH_ASSOC)) { + $warning['post'] = array_merge($warning['post'], $_post); + } + } + if ($warning['post']['thread']) { + $post = new Post($warning['post']); + } else { + $post = new Thread($warning['post'], null, false, false); + } + } + + // Show warning page and exit + die( + Element('page.html', array( + 'title' => _('Warning Issued!'), + 'config' => $config, + 'boardlist' => createBoardlist(isset($mod) ? $mod : false), + 'body' => Element('warning.html', array( + 'config' => $config, + 'warning' => $warning, + 'board' => $board, + 'post' => isset($post) ? $post->build(true) : false + ) + )) + )); +} + + + +function checkWarning($board = false) { + global $config; + + if (!isset($_SERVER['REMOTE_ADDR'])) { + // Server misconfiguration + return; + } + + + if (event('check-warning', $board)) + return true; + + $ips = array(); + $ips[] = $_SERVER['REMOTE_ADDR']; + + if ($config['proxy_check'] && isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $ips = array_merge($ips, explode(", ", $_SERVER['HTTP_X_FORWARDED_FOR'])); + } + + + foreach ($ips as $ip) { + $warnings = Bans::findWarning($ip, $config['show_modname']); + + foreach ($warnings as &$warning) { + if (!isset($_POST['json_response'])) { + displayWarning($warning); + } else { + header('Content-Type: text/json'); + die(json_encode(array('error' => true, 'banned' => true))); + } + } + } +} + + + + + function threadLocked($id) { global $board; diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 07766fff..0666fd57 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -1630,6 +1630,70 @@ function mod_ban_post($board, $delete, $post, $token = false) { mod_page(_('New ban'), 'mod/ban_form.html', $args); } +function mod_warning_post($board, $post, $token = false) { + global $config, $mod; + + if (!openBoard($board)) + error($config['error']['noboard']); + + if (!hasPermission($config['mod']['warning'], $board)) + error($config['error']['noaccess']); + + $security_token = make_secure_link_token($board . '/warning/' . $post); + + $query = prepare(sprintf('SELECT ' . ($config['warning_show_post'] ? '*' : '`ip`, `thread`') . + ' FROM ``posts_%s`` WHERE `id` = :id', $board)); + $query->bindValue(':id', $post); + $query->execute() or error(db_error($query)); + if (!$_post = $query->fetch(PDO::FETCH_ASSOC)) + error($config['error']['404']); + + $thread = $_post['thread']; + $ip = $_post['ip']; + + if (isset($_POST['new_warning'], $_POST['reason'])) { + require_once 'inc/mod/ban.php'; + + if (isset($_POST['ip'])) + $ip = $_POST['ip']; + + Bans::new_warning($_POST['ip'], $_POST['reason'], $board, + false, $config['warning_show_post'] ? $_post : false); + + if (isset($_POST['public_message'], $_POST['message'])) { + // public warning message + $_POST['message'] = strtolower(preg_replace('/[\r\n]/', '', $_POST['message'])); + $query = prepare(sprintf('UPDATE ``posts_%s`` SET `body_nomarkup` = CONCAT(`body_nomarkup`, :body_nomarkup) WHERE `id` = :id', $board)); + $query->bindValue(':id', $post); + $query->bindValue(':body_nomarkup', sprintf("\n%s", utf8tohtml($_POST['message']))); + $query->execute() or error(db_error($query)); + rebuildPost($post); + + modLog("Attached a public warning message to post #{$post}: " . utf8tohtml($_POST['message'])); + buildThread($thread ? $thread : $post); + buildIndex(); + } + if(isset($_POST['thread'])) { + // Redirect to thread + header('Location: ?/' . sprintf($config['board_path'], $board) . $config['dir']['res'] . str_replace('%d', $_POST['thread'], $config['file_page']), true, $config['redirect_http']); + } else { + // Redirect to board index + header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']); + } + } + + $args = array( + 'ip' => $ip, + 'hide_ip' => !hasPermission($config['mod']['show_ip'], $board), + 'post' => $post, + 'board' => $board, + 'boards' => listBoards(), + 'token' => $security_token + ); + + mod_page(_('New warning'), 'mod/warning_form.html', $args); +} + function mod_edit_post($board, $edit_raw_html, $postID) { global $config, $mod; diff --git a/install.php b/install.php index 4e86923b..09aa1671 100644 --- a/install.php +++ b/install.php @@ -636,7 +636,7 @@ if (file_exists($config['has_installed'])) { PRIMARY KEY (`cookie`,`extra`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;') or error(db_error()); case '5.1.4': - query('CREATE TABLE ``filehashes`` ( + query('CREATE TABLE IF NOT EXISTS ``filehashes`` ( `id` int(11) NOT NULL AUTO_INCREMENT, `board` varchar(58) NOT NULL, `thread` int(11) NOT NULL, @@ -647,6 +647,19 @@ if (file_exists($config['has_installed'])) { KEY `post_id` (`post`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; ') or error(db_error()); + query('CREATE TABLE IF NOT EXISTS ``warnings`` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `ip` varbinary(16) NOT NULL, + `created` int(10) UNSIGNED NOT NULL, + `board` varchar(58) DEFAULT NULL, + `creator` int(10) NOT NULL, + `reason` text, + `seen` tinyint(1) NOT NULL, + `post` blob, + PRIMARY KEY (`id`), + KEY `ipstart` (`ip`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; + ') or error(db_error()); case false: // TODO: enhance Tinyboard -> vichan upgrade path. query("CREATE TABLE IF NOT EXISTS ``search_queries`` ( `ip` varchar(39) NOT NULL, `time` int(11) NOT NULL, `query` text NOT NULL) ENGINE=MyISAM DEFAULT CHARSET=utf8;") or error(db_error()); diff --git a/install.sql b/install.sql index 7ef1a072..b2037bee 100644 --- a/install.sql +++ b/install.sql @@ -315,6 +315,25 @@ CREATE TABLE IF NOT EXISTS `ban_appeals` ( KEY `ban_id` (`ban_id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ; +-- ---------------------------------------------------------- + +-- +-- Table structure for table `warnings` +-- + +CREATE TABLE IF NOT EXISTS `warnings` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `ip` varbinary(16) NOT NULL, + `created` int(10) UNSIGNED NOT NULL, + `board` varchar(58) DEFAULT NULL, + `creator` int(10) NOT NULL, + `reason` text, + `seen` tinyint(1) NOT NULL, + `post` blob, + PRIMARY KEY (`id`), + KEY `ipstart` (`ip`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; + -- -------------------------------------------------------- -- @@ -383,4 +402,3 @@ CREATE TABLE IF NOT EXISTS `telegrams` ( /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; - diff --git a/mod.php b/mod.php index 946d9c5b..a63566b6 100644 --- a/mod.php +++ b/mod.php @@ -73,6 +73,7 @@ $pages = array( '/search/(posts|IP_notes|bans|log)/(.+)/(\d+)' => 'search', // search '/search/(posts|IP_notes|bans|log)/(.+)' => 'search', // search + '/(\%b)/warning/(\d+)' => 'secure_POST warning_post', // issue warning for post '/(\%b)/ban(&delete)?/(\d+)' => 'secure_POST ban_post', // ban poster '/(\%b)/move/(\d+)' => 'secure_POST move', // move thread '/(\%b)/move_reply/(\d+)' => 'secure_POST move_reply', // move reply diff --git a/stylesheets/style.css b/stylesheets/style.css index 075f7ea6..c0304a60 100644 --- a/stylesheets/style.css +++ b/stylesheets/style.css @@ -514,6 +514,13 @@ span.public_ban { margin-top: 15px; } +span.public_warning { + display: block; + color: steelblue; + font-weight: bold; + margin-top: 15px; +} + span.toolong { display: block; margin-top: 15px; diff --git a/templates/mod/warning_form.html b/templates/mod/warning_form.html new file mode 100644 index 00000000..f96215fc --- /dev/null +++ b/templates/mod/warning_form.html @@ -0,0 +1,62 @@ +{% if post and board %} + {% set action = '?/' ~ board ~ '/warning/' ~ post %} +{% else %} + {% set action = '?/warning' %} +{% endif %} + +
+ + {% if redirect %} + + {% endif %} + {% if post and board %} + + {% endif %} + + + + + + + + + + + + {% if post and board %} + + + + + {% endif %} + + + + +
+ + + {% if not hide_ip %} + + {% else %} + {% trans 'hidden' %} + {% endif %} +
+ + + +
+ + + + + ({% trans 'public; attached to post' %}) + +
+
+ diff --git a/templates/post/post_controls.html b/templates/post/post_controls.html index 60dc6fc4..ab351d00 100644 --- a/templates/post/post_controls.html +++ b/templates/post/post_controls.html @@ -10,6 +10,9 @@ {% if mod|hasPermission(config.mod.deletebyip_global, board.uri) %} {{ secure_link_confirm(config.mod.link_deletebyip_global, 'Delete all posts by IP across all boards'|trans, 'Are you sure you want to delete all posts by this IP address, across all boards?'|trans, board.dir ~ 'deletebyip/' ~ post.id ~ '/global') }}  {% endif %} +{% if mod|hasPermission(config.mod.warning, board.uri) %} + {{ config.mod.link_warning }}  +{% endif %} {% if mod|hasPermission(config.mod.ban, board.uri) %} {{ config.mod.link_ban }}  {% endif %} diff --git a/templates/post_reply.html b/templates/post_reply.html index 451ff1b9..9a6cc392 100644 --- a/templates/post_reply.html +++ b/templates/post_reply.html @@ -19,6 +19,9 @@ {% include 'post/post_controls.html' %}
1 %}style="clear:both"{% endif %}> {% endfilter %}{% if index %}{{ post.body|truncate_body(post.link) }}{% else %}{{ post.body }}{% endif %}{% filter remove_whitespace %} + {% if post.modifiers['warning message'] %} + {{ config.mod.warning_message|sprintf(post.modifiers['warning message']) }} + {% endif %} {% if post.modifiers['ban message'] %} {{ config.mod.ban_message|sprintf(post.modifiers['ban message']) }} {% endif %} diff --git a/templates/post_thread.html b/templates/post_thread.html index 4ad08862..c984bc21 100644 --- a/templates/post_thread.html +++ b/templates/post_thread.html @@ -59,6 +59,9 @@

{% endfilter %}{% if index %}{{ post.body|truncate_body(post.link) }}{% else %}{{ post.body }}{% endif %}{% filter remove_whitespace %} + {% if post.modifiers['warning message'] %} + {{ config.mod.warning_message|sprintf(post.modifiers['warning message']) }} + {% endif %} {% if post.modifiers['ban message'] %} {{ config.mod.ban_message|sprintf(post.modifiers['ban message']) }} {% endif %} diff --git a/templates/warning.html b/templates/warning.html new file mode 100644 index 00000000..1952656d --- /dev/null +++ b/templates/warning.html @@ -0,0 +1,38 @@ +{% filter remove_whitespace %} +{# Automatically removes unnecessary whitespace #} +
+

{% trans %}You were issued an warning!{% endtrans %}

+ {% if warning.reason %} +

+ {% trans %}for the following reason:{% endtrans %} +

+

+ {{ warning.reason }} +

+ {% endif %} +

+ {% trans %}Your warning was filed on{% endtrans %} + {{ warning.created|date(config.ban_date) }} + {% if config.show_modname %} + {% if warning.username %} + {% trans %}by{% endtrans %} {{ warning.username }} + {% else %} + {% trans %}by{% endtrans %} 'system' + {% endif %} + {% endif %} +

+

{% trans %}Your IP address is{% endtrans %} {{ warning.ip }}.

+ + {% if config.warning_page_extra %} +

{{ config.warning_page_extra }}

+ {% endif %} + + {% if post and config.warning_show_post %} +
+

{% trans %}You were issued an warning for the following post on{% endtrans %} {{ board.url }}:

+ {{ post }} +
+ {% endif %} + +
+{% endfilter %}