Browse Source

Implementing Warning System to Issue Warnings for Posts

Auto-tagging was omitted as warnings are visible anyway.
Co-Authored-By: Discomrade <[email protected]>
main
fallenPineapple 7 years ago
committed by -
parent
commit
7d8432d5f0
  1. 80
      inc/bans.php
  2. 19
      inc/config.php
  3. 90
      inc/functions.php
  4. 64
      inc/mod/pages.php
  5. 15
      install.php
  6. 20
      install.sql
  7. 1
      mod.php
  8. 7
      stylesheets/style.css
  9. 62
      templates/mod/warning_form.html
  10. 3
      templates/post/post_controls.html
  11. 3
      templates/post_reply.html
  12. 3
      templates/post_thread.html
  13. 38
      templates/warning.html

80
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 ? "<a href=\"?/IP/$cloaked_mask\">$cloaked_mask</a>" : $cloaked_mask) .
' (<small>#' . $pdo->lastInsertId() . '</small>)' .
' with ' . ($reason ? 'reason: ' . utf8tohtml($reason) . '' : 'no reason'));
}
return $pdo->lastInsertId();
}
}

19
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&amp;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'] = '<span class="public_ban">(%s)</span>';
// 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'] = '<span class="public_warning">(%s)</span>';
// 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)

90
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;

64
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<tinyboard warning message>%s</tinyboard>", 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;

15
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());

20
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 */;

1
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

7
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;

62
templates/mod/warning_form.html

@ -0,0 +1,62 @@
{% if post and board %}
{% set action = '?/' ~ board ~ '/warning/' ~ post %}
{% else %}
{% set action = '?/warning' %}
{% endif %}
<form action="{{ action }}" method="post">
<input type="hidden" name="token" value="{{ token }}">
{% if redirect %}
<input type="hidden" name="redirect" value="{{ redirect|e }}">
{% endif %}
{% if post and board %}
<input type="hidden" name="delete" value="{% if delete %}1{% else %}0{% endif %}">
{% endif %}
<table>
<tr>
<th>
<label for="ip">{% trans 'IP' %}</label>
</th>
<td>
{% if not hide_ip %}
<input type="text" name="ip" id="ip" size="30" maxlength="40" value="{{ ip|e }}">
{% else %}
<em>{% trans 'hidden' %}</em>
{% endif %}
</td>
</tr>
<tr>
<th>
<label for="reason">{% trans 'Reason' %}</label>
</th>
<td>
<textarea name="reason" id="reason" rows="5" cols="30">{{ reason|e }}</textarea>
</td>
</tr>
{% if post and board %}
<tr>
<th>
<label for="reason">{% trans 'Message' %}</label>
</th>
<td>
<input type="checkbox" id="public_message" name="public_message"{% if config.mod.check_warning_message %} checked{% endif %}>
<input type="text" name="message" id="message" size="35" maxlength="200" value="{{ config.mod.default_warning_message|e }}">
<span class="unimportant">({% trans 'public; attached to post' %})</span>
<script type="text/javascript">
document.getElementById('message').disabled = !document.getElementById('public_message').checked;
document.getElementById('public_message').onchange = function() {
document.getElementById('message').disabled = !this.checked;
}
</script>
</td>
</tr>
{% endif %}
<tr>
<td></td>
<td><input name="new_warning" type="submit" value="{% trans 'New Warning' %}"></td>
</tr>
</table>
</form>

3
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') }}&nbsp;
{% endif %}
{% if mod|hasPermission(config.mod.warning, board.uri) %}
<a title="{% trans %}Issue Warning{% endtrans %}" href="?/{{ board.dir }}warning/{{ post.id }}">{{ config.mod.link_warning }}</a>&nbsp;
{% endif %}
{% if mod|hasPermission(config.mod.ban, board.uri) %}
<a title="{% trans %}Ban{% endtrans %}" href="?/{{ board.dir }}ban/{{ post.id }}">{{ config.mod.link_ban }}</a>&nbsp;
{% endif %}

3
templates/post_reply.html

@ -19,6 +19,9 @@
{% include 'post/post_controls.html' %}
<div class="body" {% if post.files|length > 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 %}

3
templates/post_thread.html

@ -59,6 +59,9 @@
</p>
<div class="body">
{% 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 %}

38
templates/warning.html

@ -0,0 +1,38 @@
{% filter remove_whitespace %}
{# Automatically removes unnecessary whitespace #}
<div class="ban">
<h2>{% trans %}You were issued an warning!{% endtrans %}</h2>
{% if warning.reason %}
<p>
{% trans %}for the following reason:{% endtrans %}
</p>
<p class="reason">
{{ warning.reason }}
</p>
{% endif %}
<p>
{% trans %}Your warning was filed on{% endtrans %}
<strong>{{ warning.created|date(config.ban_date) }}</strong>
{% if config.show_modname %}
{% if warning.username %}
{% trans %}by{% endtrans %} <strong> {{ warning.username }} </strong>
{% else %}
{% trans %}by{% endtrans %} <em> 'system' </em>
{% endif %}
{% endif %}
</p>
<p>{% trans %}Your IP address is{% endtrans %} <strong>{{ warning.ip }}</strong>.</p>
{% if config.warning_page_extra %}
<p>{{ config.warning_page_extra }}</p>
{% endif %}
{% if post and config.warning_show_post %}
<hr>
<p>{% trans %}You were issued an warning for the following post on{% endtrans %} {{ board.url }}:</p>
{{ post }}
<br>
{% endif %}
</div>
{% endfilter %}
Loading…
Cancel
Save