1
0
mirror of https://github.com/vichan-devel/vichan.git synced 2024-11-25 16:00:22 +01:00

Merge pull request #198 from 8n-tech/master

Report system upgrade
This commit is contained in:
Fredrick Brennan 2014-10-22 20:23:41 -04:00
commit 5816b75bd7
26 changed files with 1484 additions and 351 deletions

View File

@ -873,9 +873,10 @@
// Custom stylesheets available for the user to choose. See the "stylesheets/" folder for a list of
// available stylesheets (or create your own).
$config['stylesheets']['Yotsuba B'] = ''; // Default; there is no additional/custom stylesheet for this.
$config['stylesheets']['Yotsuba'] = 'yotsuba.css';
// $config['stylesheets']['Futaba'] = 'futaba.css';
// $config['stylesheets']['Dark'] = 'dark.css';
$config['stylesheets']['Yotsuba'] = 'yotsuba.css';
// $config['stylesheets']['Futaba'] = 'futaba.css';
// $config['stylesheets']['Dark'] = 'dark.css';
$config['stylesheets']['Tomorrow'] = 'tomorrow.css';
// The prefix for each stylesheet URI. Defaults to $config['root']/stylesheets/
// $config['uri_stylesheets'] = 'http://static.example.org/stylesheets/';
@ -1222,21 +1223,21 @@
$config['capcode'] = ' <span class="capcode">## %s</span>';
// "## Custom" becomes lightgreen, italic and bold:
//$config['custom_capcode']['Custom'] ='<span class="capcode" style="color:lightgreen;font-style:italic;font-weight:bold"> ## %s</span>';
$config['custom_capcode']['Custom'] ='<span class="capcode" style="color:lightgreen;font-style:italic;font-weight:bold"> ## %s</span>';
// "## Mod" makes everything purple, including the name and tripcode:
//$config['custom_capcode']['Mod'] = array(
// '<span class="capcode" style="color:purple"> ## %s</span>',
// 'color:purple', // Change name style; optional
// 'color:purple' // Change tripcode style; optional
//);
$config['custom_capcode']['Mod'] = array(
'<span class="capcode" style="color:purple"> ## %s</span>',
'color:purple', // Change name style; optional
'color:purple' // Change tripcode style; optional
);
// "## Admin" makes everything red and bold, including the name and tripcode:
//$config['custom_capcode']['Admin'] = array(
// '<span class="capcode" style="color:red;font-weight:bold"> ## %s</span>',
// 'color:red;font-weight:bold', // Change name style; optional
// 'color:red;font-weight:bold' // Change tripcode style; optional
//);
$config['custom_capcode']['Admin'] = array(
'<span class="capcode" style="color:red;font-weight:bold"> ## %s</span>',
'color:red;font-weight:bold', // Change name style; optional
'color:red;font-weight:bold' // Change tripcode style; optional
);
// Enable the moving of single replies
$config['move_replies'] = false;
@ -1381,14 +1382,30 @@
$config['mod']['flood'] = &$config['mod']['bypass_filters'];
// Raw HTML posting
$config['mod']['rawhtml'] = ADMIN;
// Clean System
// Post edits remove local clean?
$config['clean']['edits_remove_local'] = true;
// Post edits remove global clean?
$config['clean']['edits_remove_global'] = true;
// Mark post clean for board rule
$config['mod']['clean'] = JANITOR;
// Mark post clean for global rule
$config['mod']['clean_global'] = MOD;
/* Administration */
// View the report queue
$config['mod']['reports'] = JANITOR;
// Dismiss an abuse report
$config['mod']['report_dismiss'] = JANITOR;
// Remove global status from a report
$config['mod']['report_demote'] = JANITOR;
// Elevate a global report to a local report.
$config['mod']['report_promote'] = JANITOR;
// Dismiss all abuse reports by an IP
$config['mod']['report_dismiss_ip'] = JANITOR;
// Dismiss all abuse reports on an individual post or thread
$config['mod']['report_dismiss_content'] = JANITOR;
// View list of bans
$config['mod']['view_banlist'] = MOD;
// View the username of the mod who made a ban

View File

@ -343,6 +343,7 @@ function embed_html($link) {
return 'Embedding error.';
}
class Post {
public function __construct($post, $root=null, $mod=false) {
global $config;
@ -389,11 +390,43 @@ class Post {
public function build($index=false) {
global $board, $config;
return Element('post_reply.html', array('config' => $config, 'board' => $board, 'post' => &$this, 'index' => $index, 'mod' => $this->mod));
return Element('post_reply.html', array(
'config' => $config,
'board' => $board,
'post' => &$this,
'index' => $index,
'mod' => $this->mod,
'clean' => $this->getClean(),
));
}
public function getClean( ) {
global $board;
if( !isset( $this->clean ) ) {
$query = prepare("SELECT * FROM `post_clean` WHERE `post_id` = :post AND `board_id` = :board");
$query->bindValue( ':board', $board['uri'] );
$query->bindValue( ':post', $this->id );
$query->execute() or error(db_error($query));
if( !($this->clean = $query->fetch(PDO::FETCH_ASSOC)) ) {
$this->clean = array(
'post_id' => $this->id,
'board_id' => $board['uri'],
'clean_local' => "0",
'clean_global' => "0",
'clean_local_mod_id' => null,
'clean_global_mod_id' => null,
);
}
}
return $this->clean;
}
};
class Thread {
class Thread extends Post {
public function __construct($post, $root = null, $mod = false, $hr = true) {
global $config;
if (!isset($root))
@ -453,7 +486,16 @@ class Thread {
event('show-thread', $this);
$built = Element('post_thread.html', array('config' => $config, 'board' => $board, 'post' => &$this, 'index' => $index, 'hasnoko50' => $hasnoko50, 'isnoko50' => $isnoko50, 'mod' => $this->mod));
$built = Element('post_thread.html', array(
'config' => $config,
'board' => $board,
'post' => &$this,
'index' => $index,
'hasnoko50' => $hasnoko50,
'isnoko50' => $isnoko50,
'mod' => $this->mod,
'clean' => $this->getClean(),
));
return $built;
}

View File

@ -311,6 +311,7 @@ function _syslog($priority, $message) {
function verbose_error_handler($errno, $errstr, $errfile, $errline) {
if (error_reporting() == 0)
return false; // Looks like this warning was suppressed by the @ operator.
error(utf8tohtml($errstr), true, array(
'file' => $errfile . ':' . $errline,
'errno' => $errno,
@ -894,10 +895,14 @@ function insertFloodPost(array $post) {
$query->bindValue(':board', $board['uri']);
$query->bindValue(':time', time());
$query->bindValue(':posthash', make_comment_hex($post['body_nomarkup']));
if ($post['has_file'])
if ($post['has_file']) {
$query->bindValue(':filehash', $post['filehash']);
else
}
else {
$query->bindValue(':filehash', null, PDO::PARAM_NULL);
}
$query->bindValue(':isreply', !$post['op'], PDO::PARAM_INT);
$query->execute() or error(db_error($query));
}
@ -2318,24 +2323,38 @@ function DNS($host) {
function shell_exec_error($command, $suppress_stdout = false) {
global $config, $debug;
if ($config['debug'])
if( $config['debug'] ) {
$which = microtime(true);
}
// Determine if $command is a valid command. If we don't, the following is considered valid output.
// '$command' is not recognized as an internal or external command, operable program or batch file.
if( empty( shell_exec("which $command") ) ) {
return false;
}
if( $config['debug'] ) {
$start = microtime(true);
}
$return = trim(shell_exec('PATH="' . escapeshellcmd($config['shell_path']) . ':$PATH";' .
$command . ' 2>&1 ' . ($suppress_stdout ? '> /dev/null ' : '') . '&& echo "TB_SUCCESS"'));
$return = preg_replace('/TB_SUCCESS$/', '', $return);
if ($config['debug']) {
$time = microtime(true) - $start;
if( $config['debug'] ) {
$time_which = $start - $which;
$time = microtime(true) - $start;
$debug['exec'][] = array(
'command' => $command,
'time' => '~' . round($time * 1000, 2) . 'ms',
'command' => $command,
'time' => '~' . round($time * 1000, 2) . 'ms + ~' . round($time_which * 1000, 2) . 'ms',
'response' => $return ? $return : null
);
$debug['time']['exec'] += $time;
}
return $return === 'TB_SUCCESS' ? false : $return;
}

View File

@ -18,15 +18,15 @@
$config['db']['user'] = 'root';
$config['db']['password'] = '';
$config['timezone'] = 'UTC';
$config['cache']['enabled'] = 'apc';
$config['cache']['enabled'] = false;
$config['cookies']['mod'] = 'mod';
$config['cookies']['salt'] = '';
$config['spam']['hidden_inputs_max_pass'] = 128;
$config['spam']['hidden_inputs_expire'] = 60 * 60 * 4; // three hours
$config['flood_time'] = 5;
$config['flood_time_ip'] = 30;
$config['flood_time_same'] = 2;
@ -46,10 +46,10 @@
$config['thread_subject_in_title'] = true;
$config['spam']['hidden_inputs_max_pass'] = 128;
$config['ayah_enabled'] = true;
// Load database credentials
require "secrets.php";
// Image shit
$config['thumb_method'] = 'gm+gifsicle';
$config['thumb_ext'] = '';

View File

@ -496,7 +496,7 @@ function mod_new_board() {
error(sprintf($config['error']['boardexists'], $board['url']));
}
$query = prepare('INSERT INTO ``boards`` VALUES (:uri, :title, :subtitle)');
$query = prepare('INSERT INTO ``boards`` (``uri``, ``title``, ``subtitle``) VALUES (:uri, :title, :subtitle)');
$query->bindValue(':uri', $_POST['uri']);
$query->bindValue(':title', $_POST['title']);
$query->bindValue(':subtitle', $_POST['subtitle']);
@ -1606,6 +1606,35 @@ function mod_edit_post($board, $edit_raw_html, $postID) {
}
$query->execute() or error(db_error($query));
if( $config['clean']['edits_remove_local'] || $config['clean']['edits_remove_global'] ) {
$query_global = "`clean_global` = :clean";
$query_global_mod = "`clean_global_mod_id` = :mod";
$query_local = "`clean_local` = :clean";
$query_local_mod = "`clean_local_mod_id` = :mod";
if( $config['clean']['edits_remove_local'] && $config['clean']['edits_remove_global'] ) {
$query = prepare("UPDATE `post_clean` SET {$query_global}, {$query_global_mod}, {$query_local}, {$query_local_mod} WHERE `board_id` = :board AND `post_id` = :post");
}
else if( $config['clean']['edits_remove_global'] ) {
$query = prepare("UPDATE `post_clean` SET {$query_global}, {$query_global_mod} WHERE `board_id` = :board AND `post_id` = :post");
}
else {
$query = prepare("UPDATE `post_clean` SET {$query_local}, {$query_local_mod} WHERE `board_id` = :board AND `post_id` = :post");
}
$query->bindValue( ':clean', false );
$query->bindValue( ':mod', NULL );
$query->bindValue( ':board', $board );
$query->bindValue( ':post', $postID );
$query->execute() or error(db_error($query));
// Finally, run a query to tidy up our records.
$cleanup = prepare("DELETE FROM `post_clean` WHERE `clean_local` = FALSE AND `clean_global` = FALSE");
$query->execute() or error(db_error($query));
}
if ($edit_raw_html) {
modLog("Edited raw HTML of post #{$postID}");
} else {
@ -1614,7 +1643,7 @@ function mod_edit_post($board, $edit_raw_html, $postID) {
}
buildIndex();
rebuildThemes('post', $board);
header('Location: ?/' . sprintf($config['board_path'], $board) . $config['dir']['res'] . sprintf($config['file_page'], $post['thread'] ? $post['thread'] : $postID) . '#' . $postID, true, $config['redirect_http']);
@ -2257,133 +2286,516 @@ function mod_rebuild() {
));
}
function mod_reports($global = false) {
function mod_reports() {
global $config, $mod;
if (!hasPermission($config['mod']['reports']))
error($config['error']['noaccess']);
// Parse arguments.
$urlArgs = func_get_args();
$global = in_array( "global", $urlArgs );
if ($mod['type'] == '20' and $global)
if( !hasPermission($config['mod']['reports']) ) {
error($config['error']['noaccess']);
$query = prepare("SELECT * FROM ``reports`` " . ($mod["type"] == "20" ? "WHERE board = :board" : "") . " ORDER BY `time` DESC LIMIT :limit");
if ($mod['type'] == '20')
$query->bindValue(':board', $mod['boards'][0]);
if ($global) {
$query = prepare("SELECT * FROM ``reports`` WHERE global = TRUE ORDER BY `time` DESC LIMIT :limit");
}
$query->bindValue(':limit', $config['mod']['recent_reports'], PDO::PARAM_INT);
if( $mod['type'] == '20' and $global ) {
error($config['error']['noaccess']);
}
// Limit reports to ONLY those in our scope.
$report_scope = $global ? "global" : "local";
// Get REPORTS.
$query = prepare("SELECT * FROM ``reports`` " . ($mod["type"] == "20" ? "WHERE board = :board" : "") . " WHERE ``".($global ? "global" : "local")."`` = TRUE LIMIT :limit");
// Limit reports by board if the moderator is local.
if( $mod['type'] == '20' ) {
$query->bindValue(':board', $mod['boards'][0]);
}
// Limit by config ceiling.
$query->bindValue( ':limit', $config['mod']['recent_reports'], PDO::PARAM_INT );
$query->execute() or error(db_error($query));
$reports = $query->fetchAll(PDO::FETCH_ASSOC);
$report_queries = array();
foreach ($reports as $report) {
if (!isset($report_queries[$report['board']]))
$report_queries[$report['board']] = array();
$report_queries[$report['board']][] = $report['post'];
}
$report_posts = array();
foreach ($report_queries as $board => $posts) {
$report_posts[$board] = array();
// Cut off here if we don't have any reports.
$reportCount = 0;
$reportHTML = '';
if( count( $reports ) > 0 ) {
$query = query(sprintf('SELECT * FROM ``posts_%s`` WHERE `id` = ' . implode(' OR `id` = ', $posts), $board)) or error(db_error());
while ($post = $query->fetch(PDO::FETCH_ASSOC)) {
$report_posts[$board][$post['id']] = $post;
// Build queries to fetch content.
$report_queries = array();
foreach ($reports as $report) {
if (!isset($report_queries[$report['board']]))
$report_queries[$report['board']] = array();
$report_queries[$report['board']][] = $report['post'];
}
// Get reported CONTENT.
$report_posts = array();
foreach ($report_queries as $board => $posts) {
$report_posts[$board] = array();
$query = query(sprintf('SELECT * FROM ``posts_%s`` WHERE `id` = ' . implode(' OR `id` = ', $posts), $board)) or error(db_error());
while ($post = $query->fetch(PDO::FETCH_ASSOC)) {
$report_posts[$board][$post['id']] = $post;
}
}
// Develop an associative array of posts to reports.
$report_index = array();
foreach( $reports as &$report ) {
// Delete reports which are for removed content.
if( !isset( $report_posts[ $report['board'] ][ $report['post'] ] ) ) {
// Invalid report (post has since been deleted)
$query = prepare("DELETE FROM ``reports`` WHERE `post` = :id AND `board` = :board");
$query->bindValue(':id', $report['post'], PDO::PARAM_INT);
$query->bindValue(':board', $report['board']);
$query->execute() or error(db_error($query));
continue;
}
// Build a unique ID.
$content_key = "{$report['board']}.{$report['post']}";
// Create a dummy array if it doesn't already exist.
if( !isset( $report_index[ $content_key ] ) ) {
$report_index[ $content_key ] = array(
"board_id" => $report['board'],
"post_id" => $report['post'],
"content" => $report_posts[ $report['board'] ][ $report['post'] ],
"reports" => array(),
);
}
// Add the report to the list of reports.
$report_index[ $content_key ]['reports'][ $report['id'] ] = $report;
// Increment the total report count.
++$reportCount;
}
// Only continue if we have something to do.
// If there are no valid reports left, we're done.
if( $reportCount > 0 ) {
// Sort this report index by number of reports, desc.
usort( $report_index, function( $a, $b ) {
$ra = count( $a['reports'] );
$rb = count( $b['reports'] );
if( $ra < $rb ) {
return 1;
}
else if( $rb > $ra ) {
return -1;
}
else {
return 0;
}
} );
// Loop through the custom index.
foreach( $report_index as &$report_item ) {
$content = $report_item['content'];
// Load board content.
openBoard($report_item['board_id']);
// Load the reported content.
if( !$content['thread'] ) {
// Still need to fix this:
$po = new Thread($content, '?/', $mod, false);
}
else {
$po = new Post($content, '?/', $mod);
}
// Fetch clean status.
$po->getClean();
$clean = $po->clean;
// Add each report's template to this container.
$report_html = "";
$reports_can_demote = false;
$reports_can_promote = false;
$content_reports = 0;
foreach( $report_item['reports'] as $report ) {
$uri_report_base = "reports/" . ($global ? "global/" : "" ) . $report['id'];
$report_html .= Element('mod/report.html', array(
'report' => $report,
'config' => $config,
'mod' => $mod,
'global' => $global,
'clean' => $clean,
'uri_dismiss' => "?/{$uri_report_base}/dismiss",
'uri_ip' => "?/{$uri_report_base}/dismissall",
'uri_demote' => "?/{$uri_report_base}/demote",
'uri_promote' => "?/{$uri_report_base}/promote",
'token_dismiss' => make_secure_link_token( $uri_report_base . '/dismiss' ),
'token_ip' => make_secure_link_token( $uri_report_base . '/dismissall' ),
'token_demote' => make_secure_link_token( $uri_report_base . '/demote' ),
'token_promote' => make_secure_link_token( $uri_report_base . '/promote' ),
));
// Determines if we can "Demote All" / "Promote All"
// This logic only needs one instance of a demotable or promotable report to work.
// DEMOTE can occur when we're global and the report has a 1 for local (meaning locally, it's not dismissed)
// PROMOTE can occur when we're local and the report has a 0 for global (meaning it's not global).
if( $global && $report['local'] == "1" ) {
$reports_can_demote = true;
}
else if( !$global && $report['global'] != "1" ) {
$reports_can_promote = true;
}
++$content_reports;
}
// Build the ">>>/b/ thread reported 3 times" title.
$report_title = sprintf(
_('<a href="%s" title="View content" target="_new">&gt;&gt;&gt;/%s/</a> %s reported %d time(s).'),
"?/{$report_item['board_id']}/res/" . ( $content['thread'] ?: $content['id'] ) . ".html#{$content['thread']}",
$report_item['board_id'],
_( $content['thread'] ? "reply" : "thread" ),
$content_reports
);
// Figure out some stuff we need for the page.
$reports_can_demote = ( $clean['clean_local'] ? false : $reports_can_demote );
$reports_can_promote = ( $clean['clean_global'] ? false : $reports_can_promote );
$uri_content_base = "reports/" . ($global ? "global/" : "" ) . "content/";
$uri_clean_base = "reports/" . ($global ? "global/" : "" ) . "{$report_item['board_id']}/clean/{$content['id']}";
// Build the actions page.
$content_html = Element('mod/report_content.html', array(
'reports_html' => $report_html,
'reports_can_demote' => $reports_can_demote,
'reports_can_promote' => $reports_can_promote,
'report_count' => $content_reports,
'report_title' => $report_title,
'content_html' => $po->build(true),
'content_board' => $report_item['board_id'],
'content' => (array) $content,
'clean' => $clean,
'uri_content_demote' => "?/{$uri_content_base}{$report_item['board_id']}/{$content['id']}/demote",
'uri_content_promote' => "?/{$uri_content_base}{$report_item['board_id']}/{$content['id']}/promote",
'uri_content_dismiss' => "?/{$uri_content_base}{$report_item['board_id']}/{$content['id']}/dismiss",
'token_content_demote' => make_secure_link_token( "{$uri_content_base}{$report_item['board_id']}/{$content['id']}/demote" ),
'token_content_promote' => make_secure_link_token( "{$uri_content_base}{$report_item['board_id']}/{$content['id']}/promote" ),
'token_content_dismiss' => make_secure_link_token( "{$uri_content_base}{$report_item['board_id']}/{$content['id']}/dismiss" ),
'uri_clean' => "?/{$uri_clean_base}/local",
'uri_clean_global' => "?/{$uri_clean_base}/global",
'uri_clean_both' => "?/{$uri_clean_base}/global+local",
'token_clean' => make_secure_link_token( $uri_clean_base . '/local' ),
'token_clean_global' => make_secure_link_token( $uri_clean_base . '/global' ),
'token_clean_both' => make_secure_link_token( $uri_clean_base . '/global+local' ),
'global' => $global,
'config' => $config,
'mod' => $mod,
));
$reportHTML .= $content_html;
}
}
}
$count = 0;
$body = '';
foreach ($reports as $report) {
if (!isset($report_posts[$report['board']][$report['post']])) {
// // Invalid report (post has since been deleted)
$query = prepare("DELETE FROM ``reports`` WHERE `post` = :id AND `board` = :board");
$query->bindValue(':id', $report['post'], PDO::PARAM_INT);
$query->bindValue(':board', $report['board']);
$query->execute() or error(db_error($query));
continue;
}
openBoard($report['board']);
$post = &$report_posts[$report['board']][$report['post']];
if (!$post['thread']) {
// Still need to fix this:
$po = new Thread($post, '?/', $mod, false);
} else {
$po = new Post($post, '?/', $mod);
}
// a little messy and inefficient
$append_html = Element('mod/report.html', array(
'report' => $report,
'config' => $config,
'mod' => $mod,
'token' => make_secure_link_token('reports/' . $report['id'] . '/dismiss'),
'token_all' => make_secure_link_token('reports/' . $report['id'] . '/dismissall')
));
// Bug fix for https://github.com/savetheinternet/Tinyboard/issues/21
$po->body = truncate($po->body, $po->link(), $config['body_truncate'] - substr_count($append_html, '<br>'));
if (mb_strlen($po->body) + mb_strlen($append_html) > $config['body_truncate_char']) {
// still too long; temporarily increase limit in the config
$__old_body_truncate_char = $config['body_truncate_char'];
$config['body_truncate_char'] = mb_strlen($po->body) + mb_strlen($append_html);
}
$po->body .= $append_html;
$body .= $po->build(true) . '<hr>';
if (isset($__old_body_truncate_char))
$config['body_truncate_char'] = $__old_body_truncate_char;
$count++;
}
$pageArgs = array(
'count' => $reportCount,
'reports' => $reportHTML,
'global' => $global,
);
mod_page(sprintf('%s (%d)', _('Report queue'), $count), 'mod/reports.html', array('reports' => $body, 'count' => $count));
mod_page( sprintf('%s (%d)', _( ( $global ? 'Global report queue' : 'Report queue' ) ), $reportCount), 'mod/reports.html', $pageArgs );
}
function mod_report_dismiss($id, $all = false) {
global $config;
function mod_report_dismiss() {
global $config, $mod;
$query = prepare("SELECT `post`, `board`, `ip` FROM ``reports`` WHERE `id` = :id");
$query->bindValue(':id', $id);
$query->execute() or error(db_error($query));
if ($report = $query->fetch(PDO::FETCH_ASSOC)) {
$ip = $report['ip'];
$board = $report['board'];
$post = $report['post'];
} else
error($config['error']['404']);
// Parse arguments.
$arguments = func_get_args();
$global = in_array( "global", $arguments );
$content = in_array( "content", $arguments );
if (!$all && !hasPermission($config['mod']['report_dismiss'], $board))
if( $mod['type'] == '20' and $global ) {
error($config['error']['noaccess']);
if ($all && !hasPermission($config['mod']['report_dismiss_ip'], $board))
error($config['error']['noaccess']);
if ($all) {
$query = prepare("DELETE FROM ``reports`` WHERE `ip` = :ip");
$query->bindValue(':ip', $ip);
} else {
$query = prepare("DELETE FROM ``reports`` WHERE `id` = :id");
$query->bindValue(':id', $id);
}
$query->execute() or error(db_error($query));
if( $content ) {
$board = @$arguments[2];
$post = @$arguments[3];
if( !hasPermission($config['mod']['report_dismiss_content'], $board) ) {
error($config['error']['noaccess']);
}
if( $board != "" && $post != "" ) {
$query = prepare("SELECT `id` FROM `reports` WHERE `board` = :board AND `post` = :post");
$query->bindValue(':board', $board);
$query->bindValue(':post', $post);
$query->execute() or error(db_error($query));
if( count( $reports = $query->fetchAll(PDO::FETCH_ASSOC) ) > 0 ) {
$report_ids = array();
foreach( $reports as $report ) {
$report_ids[ $report['id'] ] = $report['id'];
}
if( $global ) {
$scope = "``global`` = FALSE AND ``local`` = FALSE";
}
else {
$scope = "``local`` = FALSE";
}
$query = prepare("UPDATE ``reports`` SET {$scope} WHERE `id` IN (".implode(',', array_map('intval', $report_ids)).")");
$query->execute() or error(db_error($query));
modLog("Promoted " . count($report_ids) . " local report(s) for post #{$post}", $board);
}
else {
error($config['error']['404']);
}
}
else {
error($config['error']['404']);
}
}
else {
$report = @$arguments[1];
$all = in_array( "all", $arguments );
if( $report != "" ) {
$query = prepare("SELECT `post`, `board`, `ip` FROM ``reports`` WHERE `id` = :id");
$query->bindValue(':id', $report);
$query->execute() or error(db_error($query));
if ($reportobj = $query->fetch(PDO::FETCH_ASSOC)) {
$ip = $reportobj['ip'];
$board = $reportobj['board'];
$post = $reportobj['post'];
if( !$all && !hasPermission($config['mod']['report_dismiss'], $board) ) {
error($config['error']['noaccess']);
}
if( $all && !hasPermission($config['mod']['report_dismiss_ip'], $board) ) {
error($config['error']['noaccess']);
}
// Determine scope (local and global or just local) based on /global/ being in URI.
if( $global ) {
$scope = "`global` = FALSE";
$boards = "";
}
else {
$scope = "`local` = FALSE";
$boards = "AND `board` = '{$board}'";
}
// Prepare query.
// We don't delete reports, only modify scope.
if( $all ) {
$query = prepare("UPDATE ``reports`` SET {$scope} WHERE `ip` = :ip {$boards}");
$query->bindValue(':ip', $ip);
}
else {
$query = prepare("UPDATE ``reports`` SET {$scope} WHERE `id` = :id {$boards}");
$query->bindValue(':id', $report);
}
$query->execute() or error(db_error($query));
// Cleanup - Remove reports that have been completely dismissed.
$query = prepare("DELETE FROM `reports` WHERE `local` = FALSE AND `global` = FALSE");
$query->execute() or error(db_error($query));
if( $all ) {
modLog("Dismissed all reports by <a href=\"?/IP/{$ip}\">{$ip}</a>");
}
else {
modLog("Dismissed a report for post #{$post}", $board);
}
}
else {
error($config['error']['404']);
}
}
else {
error($config['error']['404']);
}
}
if ($all)
modLog("Dismissed all reports by <a href=\"?/IP/$ip\">$ip</a>");
else
modLog("Dismissed a report for post #{$id}", $board);
if( $global ) {
header('Location: ?/reports/global', true, $config['redirect_http']);
}
else {
header('Location: ?/reports', true, $config['redirect_http']);
}
}
function mod_report_demote() {
global $config, $mod;
if( $mod['type'] == '20' ) {
error($config['error']['noaccess']);
}
// Parse arguments.
$arguments = func_get_args();
$content = in_array( "content", $arguments );
if( $content ) {
$board = @$arguments[2];
$post = @$arguments[3];
if( !hasPermission($config['mod']['report_demote'], $board) ) {
error($config['error']['noaccess']);
}
if( $board != "" && $post != "" ) {
$query = prepare("SELECT `id` FROM `reports` WHERE `global` = TRUE AND `board` = :board AND `post` = :post");
$query->bindValue(':board', $board);
$query->bindValue(':post', $post);
$query->execute() or error(db_error($query));
if( count( $reports = $query->fetchAll(PDO::FETCH_ASSOC) ) > 0 ) {
$report_ids = array();
foreach( $reports as $report ) {
$report_ids[ $report['id'] ] = $report['id'];
}
$query = prepare("UPDATE ``reports`` SET ``global`` = FALSE WHERE `id` IN (".implode(',', array_map('intval', $report_ids)).")");
$query->execute() or error(db_error($query));
modLog("Demoted " . count($report_ids) . " global report(s) for post #{$post}", $board);
}
else {
error($config['error']['404']);
}
}
else {
error($config['error']['404']);
}
}
else {
$report = @$arguments[1];
if( $report != "" ) {
$query = prepare("SELECT `post`, `board`, `ip` FROM ``reports`` WHERE `id` = :id AND ``global`` = TRUE");
$query->bindValue(':id', $report);
$query->execute() or error(db_error($query));
if( $reportobj = $query->fetch(PDO::FETCH_ASSOC) ) {
$ip = $reportobj['ip'];
$board = $reportobj['board'];
$post = $reportobj['post'];
if( !hasPermission($config['mod']['report_demote'], $board) ) {
error($config['error']['noaccess']);
}
$query = prepare("UPDATE ``reports`` SET ``global`` = FALSE WHERE `id` = :id");
$query->bindValue(':id', $report);
$query->execute() or error(db_error($query));
modLog("Demoted a global report for post #{$report}", $board);
}
else {
error($config['error']['404']);
}
}
else {
error($config['error']['404']);
}
}
header('Location: ?/reports/global', true, $config['redirect_http']);
}
function mod_report_promote() {
global $config, $mod;
// Parse arguments.
$arguments = func_get_args();
$content = in_array( "content", $arguments );
if( $content ) {
$board = @$arguments[2];
$post = @$arguments[3];
if( !hasPermission($config['mod']['report_promote'], $board) ) {
error($config['error']['noaccess']);
}
if( $board != "" && $post != "" ) {
$query = prepare("SELECT `id` FROM `reports` WHERE `global` = FALSE AND `board` = :board AND `post` = :post");
$query->bindValue(':board', $board);
$query->bindValue(':post', $post);
$query->execute() or error(db_error($query));
if( count( $reports = $query->fetchAll(PDO::FETCH_ASSOC) ) > 0 ) {
$report_ids = array();
foreach( $reports as $report ) {
$report_ids[ $report['id'] ] = $report['id'];
}
$query = prepare("UPDATE ``reports`` SET ``global`` = TRUE WHERE `id` IN (".implode(',', array_map('intval', $report_ids)).")");
$query->execute() or error(db_error($query));
modLog("Promoted " . count($report_ids) . " local report(s) for post #{$post}", $board);
}
else {
error($config['error']['404']);
}
}
else {
error($config['error']['404']);
}
}
else {
$report = @$arguments[1];
if( $report != "" ) {
$query = prepare("SELECT `post`, `board`, `ip` FROM ``reports`` WHERE `id` = :id AND ``global`` = FALSE");
$query->bindValue(':id', $report);
$query->execute() or error(db_error($query));
if ($reportobj = $query->fetch(PDO::FETCH_ASSOC)) {
$ip = $reportobj['ip'];
$board = $reportobj['board'];
$post = $reportobj['post'];
if( !hasPermission($config['mod']['report_promote'], $board) ) {
error($config['error']['noaccess']);
}
$query = prepare("UPDATE ``reports`` SET ``global`` = TRUE WHERE `id` = :id");
$query->bindValue(':id', $report);
$query->execute() or error(db_error($query));
modLog("Promoted a local report for post #{$report}", $board);
}
else {
error($config['error']['404']);
}
}
else {
error($config['error']['404']);
}
}
header('Location: ?/reports', true, $config['redirect_http']);
}
@ -2444,6 +2856,150 @@ function mod_recent_posts($lim) {
}
function mod_report_clean( $global_reports, $board, $unclean, $post, $global, $local ) {
global $config, $mod;
if( !openBoard($board) ) {
error($config['error']['noboard']);
}
$query_global = "";
$query_global_mod = "";
if( $global ) {
if( !hasPermission($config['mod']['clean_global'], $board) ) {
error($config['error']['noaccess']);
}
$query_global = "`clean_global` = :clean";
$query_global_mod = "`clean_global_mod_id` = :mod";
}
$query_local = "";
$query_local_mod = "";
if( $local ) {
if( !hasPermission($config['mod']['clean'], $board) ) {
error($config['error']['noaccess']);
}
$query_local = "`clean_local` = :clean";
$query_local_mod = "`clean_local_mod_id` = :mod";
}
// Marking this post as "Clean" (report immune?)
if( !$unclean ) {
// Attempt to find a `post_clean` row for this content.
$query = prepare("SELECT * FROM `post_clean` WHERE `board_id` = :board AND `post_id` = :post");
$query->bindValue( ':board', $board );
$query->bindValue( ':post', $post );
$query->execute() or error(db_error($query));
// If the $clean object doesn't exist we need to insert a row for this post.
if( !($cleanRecord = $query->fetch(PDO::FETCH_ASSOC)) ) {
$query = prepare("INSERT INTO `post_clean` (`post_id`, `board_id`) VALUES ( :post, :board )");
$query->bindValue( ':board', $board );
$query->bindValue( ':post', $post );
$query->execute() or error(db_error($query));
if( $query->rowCount() == 0 ) {
error("The database failed to create a record for this content in `post_clean` to record clean status.");
}
$cleanRecord = true;
}
}
// Revoking clean status (open it to reports?)
else {
// Attempt to find a `post_clean` row for this content.
$query = prepare("SELECT * FROM `post_clean` WHERE `board_id` = :board AND `post_id` = :post");
$query->bindValue( ':board', $board );
$query->bindValue( ':post', $post );
$query->execute() or error(db_error($query));
if( !($cleanRecord = $query->fetch(PDO::FETCH_ASSOC)) ) {
error($config['error']['404']);
}
}
// Update the `post_clean` row represented by $clean.
if( $cleanRecord ) {
// Build our query based on the URI arguments.
if( $global && $local ) {
$query = prepare("UPDATE `post_clean` SET {$query_global}, {$query_global_mod}, {$query_local}, {$query_local_mod} WHERE `board_id` = :board AND `post_id` = :post");
}
else if( $global ) {
$query = prepare("UPDATE `post_clean` SET {$query_global}, {$query_global_mod} WHERE `board_id` = :board AND `post_id` = :post");
}
else {
$query = prepare("UPDATE `post_clean` SET {$query_local}, {$query_local_mod} WHERE `board_id` = :board AND `post_id` = :post");
}
$query->bindValue( ':clean', !$unclean );
$query->bindValue( ':mod', $unclean ? NULL : $mod['id'] );
$query->bindValue( ':board', $board );
$query->bindValue( ':post', $post );
$query->execute() or error(db_error($query));
// Finally, run a query to tidy up our records.
if( $unclean ) {
// Query is removing clean status from content.
// Remove any clean records that are now null.
$cleanup = prepare("DELETE FROM `post_clean` WHERE `clean_local` = FALSE AND `clean_global` = FALSE");
$query->execute() or error(db_error($query));
}
else {
// Content is clean, auto-handle all reports.
// If this is a total clean, we don't need to update records first.
if( !($global && $local) ) {
$query = prepare("UPDATE `reports` SET `" . ($local ? "local" : "global") . "` = FALSE WHERE `board` = :board AND `post` = :post");
$query->bindValue( ':board', $board );
$query->bindValue( ':post', $post );
$query->execute() or error(db_error($query));
// If we didn't hit anything, this content doesn't have reports, so don't run the delete query.
$require_delete = ($query->rowCount() > 0);
if( $require_delete ) {
$query = prepare("DELETE FROM `reports` WHERE `local` = FALSE and `global` = FALSE");
$query->execute() or error(db_error($query));
}
}
// This is a total clean, so delete content by ID rather than via cleanup.
else {
$query = prepare("DELETE FROM `reports` WHERE `board` = :board AND `post` = :post");
$query->bindValue( ':board', $board );
$query->bindValue( ':post', $post );
$query->execute() or error(db_error($query));
}
}
// Log the action.
// Having clear wording of ths log is very important because of the nature of clean status.
$log_action = ($unclean ? "Closed" : "Re-opened" );
$log_scope = ($local && $global ? "local and global" : ($local ? "local" : "global" ) );
modLog( "{$log_action} reports for post #{$post} in {$log_scope}.", $board);
rebuildPost( $post );
}
// Redirect
if( $global_reports ) {
header('Location: ?/reports/global', true, $config['redirect_http']);
}
else {
header('Location: ?/reports', true, $config['redirect_http']);
}
}
function mod_config($board_config = false) {
global $config, $mod, $board;

View File

@ -579,7 +579,8 @@ if ($step == 0) {
</p>';
echo Element('page.html', $page);
} elseif ($step == 1) {
}
elseif ($step == 1) {
$page['title'] = 'Pre-installation test';
$can_exec = true;
@ -761,7 +762,8 @@ if ($step == 0) {
'title' => 'Checking environment',
'config' => $config
));
} elseif ($step == 2) {
}
elseif ($step == 2) {
// Basic config
$page['title'] = 'Configuration';
@ -775,7 +777,8 @@ if ($step == 0) {
'title' => 'Configuration',
'config' => $config
));
} elseif ($step == 3) {
}
elseif ($step == 3) {
$instance_config =
'<?php
@ -814,7 +817,8 @@ if ($step == 0) {
if (@file_put_contents('inc/instance-config.php', $instance_config)) {
header('Location: ?step=4', true, $config['redirect_http']);
} else {
}
else {
$page['title'] = 'Manual installation required';
$page['body'] = '
<p>I couldn\'t write to <strong>inc/instance-config.php</strong> with the new configuration, probably due to a permissions error.</p>
@ -826,7 +830,8 @@ if ($step == 0) {
';
echo Element('page.html', $page);
}
} elseif ($step == 4) {
}
elseif ($step == 4) {
// SQL installation
buildJavascript();
@ -846,11 +851,15 @@ if ($step == 0) {
$sql_errors = '';
foreach ($queries as $query) {
if ($mysql_version < 50503)
if ($mysql_version < 50503) {
$query = preg_replace('/(CHARSET=|CHARACTER SET )utf8mb4/', '$1utf8', $query);
}
$query = preg_replace('/^([\w\s]*)`([0-9a-zA-Z$_\x{0080}-\x{FFFF}]+)`/u', '$1``$2``', $query);
if (!query($query))
if (!query($query)) {
$sql_errors .= '<li>' . db_error() . '</li>';
}
}
$page['title'] = 'Installation complete';
@ -858,7 +867,8 @@ if ($step == 0) {
if (!empty($sql_errors)) {
$page['body'] .= '<div class="ban"><h2>SQL errors</h2><p>SQL errors were encountered when trying to install the database. This may be the result of using a database which is already occupied with a vichan installation; if so, you can probably ignore this.</p><p>The errors encountered were:</p><ul>' . $sql_errors . '</ul><p><a href="?step=5">Ignore errors and complete installation.</a></p></div>';
} else {
}
else {
$boards = listBoards();
foreach ($boards as &$_board) {
setupBoard($_board);
@ -866,13 +876,11 @@ if ($step == 0) {
}
file_write($config['has_installed'], VERSION);
/*if (!file_unlink(__FILE__)) {
$page['body'] .= '<div class="ban"><h2>Delete install.php!</h2><p>I couldn\'t remove <strong>install.php</strong>. You will have to remove it manually.</p></div>';
}*/
}
echo Element('page.html', $page);
} elseif ($step == 5) {
}
elseif ($step == 5) {
$page['title'] = 'Installation complete';
$page['body'] = '<p style="text-align:center">Thank you for using vichan. Please remember to report any bugs you discover.</p>';

46
mod.php
View File

@ -53,10 +53,23 @@ $pages = array(
'/edit/(\%b)' => 'secure_POST edit_board', // edit board details
'/new-board' => 'secure_POST new_board', // create a new board
'/rebuild' => 'secure_POST rebuild', // rebuild static files
'/reports' => 'reports', // report queue
'/reports/(global)' => 'reports', // global report queue
'/reports/(\d+)/dismiss(all)?' => 'secure report_dismiss', // dismiss a report
'/rebuild' => 'secure_POST rebuild', // rebuild static files
// Report management
// (global) denotes if the action is being carried out from the global dashboard,
// and if the return address should also be the global dashboard.
// Important to note that (?:global) will make no argument.
// (global)? will make argument 0 either "global" or "".
'/reports(?:/)?' => 'reports', // report queue
'/reports/(global)?(?:/)?' => 'reports', // global report queue
'/reports/(global)?(?:/)?(content)/(\%b)/(\d+)(?:/)?' => 'reports', // specific reported content (also historic)
'/reports/(global)?(?:/)?(content)/(\%b)/(\d+)/dismiss(?:/)?' => 'secure report_dismiss', // dismiss all reports on content
'/reports/(global)?(?:/)?(content)/(\%b)/(\d+)/demote(?:/)?' => 'secure report_demote', // demote all reports on content
'/reports/(global)?(?:/)?(content)/(\%b)/(\d+)/promote(?:/)?' => 'secure report_promote', // demote all reports on content
'/reports/(global)?(?:/)?(\d+)/dismiss(all)?(?:/)?' => 'secure report_dismiss', // dismiss a report
'/reports/(global)?(?:/)?(\d+)/demote(?:/)?' => 'secure report_demote', // demote a global report to a local report
'/reports/(global)?(?:/)?(\d+)/promote(?:/)?' => 'secure report_promote', // promote a local report to a global report
'/reports/(global)?(?:/)?(\%b)/(un)?clean/(\d+)/(global)?(?:\+)?(local)?' => 'secure report_clean', // protect/unprotect from reports
'/IP/([\w.:]+)' => 'secure_POST ip', // view ip address
'/IP/([\w.:]+)/remove_note/(\d+)' => 'secure ip_remove_note', // remove note from ip address
@ -73,18 +86,19 @@ $pages = array(
'/search' => 'search_redirect', // search
'/search/(posts|IP_notes|bans|log)/(.+)/(\d+)' => 'search', // search
'/search/(posts|IP_notes|bans|log)/(.+)' => 'search', // search
'/(\%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
'/(\%b)/edit(_raw)?/(\d+)' => 'secure_POST edit_post', // edit post
'/(\%b)/delete/(\d+)' => 'secure delete', // delete post
'/(\%b)/deletefile/(\d+)/(\d+)' => 'secure deletefile', // delete file from post
'/(\%b+)/spoiler/(\d+)/(\d+)' => 'secure spoiler_image', // spoiler file
'/(\%b)/deletebyip/(\d+)(/global)?' => 'secure deletebyip', // delete all posts by IP address
'/(\%b)/(un)?lock/(\d+)' => 'secure lock', // lock thread
'/(\%b)/(un)?sticky/(\d+)' => 'secure sticky', // sticky thread
'/(\%b)/bump(un)?lock/(\d+)' => 'secure bumplock', // "bumplock" thread
// Content management
'/(\%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
'/(\%b)/edit(_raw)?/(\d+)' => 'secure_POST edit_post', // edit post
'/(\%b)/delete/(\d+)' => 'secure delete', // delete post
'/(\%b)/deletefile/(\d+)/(\d+)' => 'secure deletefile', // delete file from post
'/(\%b+)/spoiler/(\d+)/(\d+)' => 'secure spoiler_image', // spoiler file
'/(\%b)/deletebyip/(\d+)(/global)?' => 'secure deletebyip', // delete all posts by IP address
'/(\%b)/(un)?lock/(\d+)' => 'secure lock', // lock thread
'/(\%b)/(un)?sticky/(\d+)' => 'secure sticky', // sticky thread
'/(\%b)/bump(un)?lock/(\d+)' => 'secure bumplock', // "bumplock" thread
'/themes' => 'themes_list', // manage themes
'/themes/(\w+)' => 'secure_POST theme_configure', // configure/reconfigure theme

View File

@ -2,10 +2,13 @@
/*
* Copyright (c) 2010-2014 Tinyboard Development Group
*/
require "./inc/functions.php";
require "./inc/anti-bot.php";
require 'inc/functions.php';
require 'inc/anti-bot.php';
include "inc/dnsbls.php";
// The dnsbls is an optional DNS blacklist include.
// Squelch warnings if it doesn't exist.
@include "./inc/dnsbls.php";
// Fix for magic quotes
if (get_magic_quotes_gpc()) {
@ -101,7 +104,8 @@ if (isset($_POST['delete'])) {
header('Content-Type: text/json');
echo json_encode(array('success' => true));
}
} elseif (isset($_POST['report'])) {
}
elseif (isset($_POST['report'])) {
if (!isset($_POST['board'], $_POST['reason']))
error($config['error']['bot']);
@ -131,25 +135,45 @@ if (isset($_POST['delete'])) {
markup($reason);
foreach ($report as &$id) {
$query = prepare(sprintf("SELECT `thread` FROM ``posts_%s`` WHERE `id` = :id", $board['uri']));
$query = prepare(
"SELECT
`thread`,
`post_clean`.`clean_local`,
`post_clean`.`clean_global`
FROM `posts_{$board['uri']}`
LEFT JOIN `post_clean`
ON `post_clean`.`board_id` = '{$board['uri']}'
AND `post_clean`.`post_id` = :id
WHERE `id` = :id"
);
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
$thread = $query->fetchColumn();
if ($config['syslog'])
_syslog(LOG_INFO, 'Reported post: ' .
'/' . $board['dir'] . $config['dir']['res'] . sprintf($config['file_page'], $thread ? $thread : $id) . ($thread ? '#' . $id : '') .
' for "' . $reason . '"'
);
$query = prepare("INSERT INTO ``reports`` VALUES (NULL, :time, :ip, :board, :post, :reason, :global)");
$query->bindValue(':time', time(), PDO::PARAM_INT);
$query->bindValue(':ip', $_SERVER['REMOTE_ADDR'], PDO::PARAM_STR);
$query->bindValue(':board', $board['uri'], PDO::PARAM_INT);
$query->bindValue(':post', $id, PDO::PARAM_INT);
$query->bindValue(':reason', $reason, PDO::PARAM_STR);
$query->bindValue(':global', isset($_POST['global']), PDO::PARAM_BOOL);
$query->execute() or error(db_error($query));
if( $post = $query->fetch(PDO::FETCH_ASSOC) ) {
$report_local = !$post['clean_local'];
$report_global = isset($_POST['global']) && !$post['clean_global'];
if( $report_local || $report_global ) {
$thread = $post['thread'];
if ($config['syslog']) {
_syslog(LOG_INFO, 'Reported post: ' .
'/' . $board['dir'] . $config['dir']['res'] . sprintf($config['file_page'], $thread ? $thread : $id) . ($thread ? '#' . $id : '') .
' for "' . $reason . '"'
);
}
$query = prepare("INSERT INTO `reports` (`time`, `ip`, `board`, `post`, `reason`, `local`, `global`) VALUES (:time, :ip, :board, :post, :reason, :local, :global)");
$query->bindValue(':time', time(), PDO::PARAM_INT);
$query->bindValue(':ip', $_SERVER['REMOTE_ADDR'], PDO::PARAM_STR);
$query->bindValue(':board', $board['uri'], PDO::PARAM_INT);
$query->bindValue(':post', $id, PDO::PARAM_INT);
$query->bindValue(':reason', $reason, PDO::PARAM_STR);
$query->bindValue(':local', $report_local, PDO::PARAM_BOOL);
$query->bindValue(':global', $report_global, PDO::PARAM_BOOL);
$query->execute() or error(db_error($query));
}
}
}
$is_mod = isset($_POST['mod']) && $_POST['mod'];
@ -161,7 +185,8 @@ if (isset($_POST['delete'])) {
header('Content-Type: text/json');
echo json_encode(array('success' => true));
}
} elseif (isset($_POST['post'])) {
}
elseif (isset($_POST['post'])) {
if (!isset($_POST['body'], $_POST['board']))
error($config['error']['bot']);
@ -573,14 +598,16 @@ if (isset($_POST['delete'])) {
}
$md5cmd = $config['bsd_md5'] ? 'md5 -r' : 'md5sum';
if ($output = shell_exec_error("cat $filenames | $md5cmd")) {
if( ($output = shell_exec_error("cat $filenames | $md5cmd")) !== false ) {
$explodedvar = explode(' ', $output);
$hash = $explodedvar[0];
$post['filehash'] = $hash;
} elseif ($config['max_images'] === 1) {
}
elseif ($config['max_images'] === 1) {
$post['filehash'] = md5_file($upload);
} else {
}
else {
$str_to_hash = '';
foreach (explode(' ', $filenames) as $i => $f) {
$str_to_hash .= file_get_contents($f);
@ -884,7 +911,8 @@ if (isset($_POST['delete'])) {
'id' => $id
));
}
} elseif (isset($_POST['appeal'])) {
}
elseif (isset($_POST['appeal'])) {
if (!isset($_POST['ban_id']))
error($config['error']['bot']);
@ -925,7 +953,8 @@ if (isset($_POST['delete'])) {
$query->execute() or error(db_error($query));
displayBan($ban);
} else {
}
else {
if (!file_exists($config['has_installed'])) {
header('Location: install.php', true, $config['redirect_http']);
} else {

128
stylesheets/mod/mod.css Normal file
View File

@ -0,0 +1,128 @@
.mod-reports {
display: block;
list-style: none;
margin: 0;
padding: 0;
}
.mod-report {
border: none;
border-bottom: 1px solid #B7C5D9;
clear: left;
padding: 0.5em;
}
.mod-report:last-child {
border-bottom: none;
}
.report-header {
margin: 0 0 0.25em 0;
}
.report-list {
display: block;
list-style: none;
margin: 0;
padding: 0;
}
.report-item {
display: inline-block;
margin: 0;
padding: 0;
}
.report-item .report {
background: #D6DAF0;
margin: 0.2em 4px 0.2em 0;
padding: 0.3em 0.3em 0.5em 0.6em;
border-width: 1px;
border-style: none solid solid none;
border-color: #B7C5D9;
display: inline-block;
max-width: 94% !important;
}
.report-reason {
display: block;
font-size: 115%;
line-height: 115%;
}
.report-details {
display: block;
margin: 0.3em 0 0 0;
padding: 0.3em 0 0 0;
clear: left;
list-style: none;
}
.report-detail {
display: block;
margin: 0;
padding: 0;
}
.detail-name {
display: inline-block;
min-width: 6.25em;
}
.report-actions {
display: block;
border: none;
border-top: 1px solid #B7C5D9;
margin: 0.3em 0 0 0;
padding: 0.3em 0 0 0;
clear: left;
list-style: none;
}
.report-action {
display: inline-block;
margin: 0 0.5em 0 0;
padding: 0;
}
.report-action::after {
display: inline-block;
margin: 0 0 0 0.5em;
padding: 0;
content: ' | ';
}
.report-action:last-child::after {
display: none;
content: '';
}
.report-content div.post.reply,
.report-content div.thread {
background: #D6DAF0;
margin: 0.2em 4px 0.2em 0;
padding: 0.3em 0.3em 0.5em 0.6em;
border-width: 1px;
border-style: none solid solid none;
border-color: #B7C5D9;
display: inline-block;
max-width: 94% !important;
}
.mod-report:hover .report-content div.post.reply,
.mod-report:hover .report-content div.thread {
background: #FFC4C4;
border-color: #F88;
}
.report-content-actions {
display: block;
padding: 0.3em 0;
clear: left;
list-style: none;
}
.report-content-action {
display: inline-block;
margin: 0 0.5em 0 0;
padding: 0;
}
.report-content-action::after {
display: inline-block;
margin: 0 0 0 0.5em;
padding: 0;
content: ' | ';
}
.report-content-action:last-child::after {
display: none;
content: '';
}

View File

@ -288,6 +288,18 @@ div.post.reply {
max-width: 94%!important;
}
div.post_modified {
min-width: 47.5em;
margin-left: 1.8em;
padding-top: 0.8em;
}
div.post_modified div.content-status {
margin-top: 0.5em;
padding-bottom: 0em;
font-size: 72%;
}
span.trip {
color: #228854;
}
@ -327,6 +339,7 @@ div#wrap {
margin: 0 auto;
}
div.module,
div.ban {
background: white;
border: 1px solid #98E;
@ -334,7 +347,8 @@ div.ban {
margin: 30px auto;
}
div.ban p,div.ban h2 {
div.ban p,
div.ban h2 {
padding: 3px 7px;
}
@ -658,7 +672,7 @@ pre {
margin-left: -20px;
}
div.thread:hover {
.theme-catalog div.thread:hover {
background: #D6DAF0;
border-color: #B7C5D9;
}

152
stylesheets/tomorrow.css Normal file
View File

@ -0,0 +1,152 @@
/** TOMORROW, I'LL ...
A cool dark skin by 7185.
https://github.com/7185/8chan-tomorrow/
**/
body {
background:#1d1f21 none;
color:#C5C8C6
}
h1,div.subtitle {
color:#C5C8C6!important
}
a:link,a:visited,p.intro a.email span.name {
color:#81a2be
}
a:link:hover {
color:#5F89AC
}
a.post_no {
color:#C5C8C6
}
a.post_no:hover {
color:#5F89AC!important
}
div.banner {
background-color:#1d1f21
}
div.post.reply {
background-color:#282a2e;
border:1px solid #282a2e;
margin-bottom:2px;
margin-left:16px;
margin-top:2px
}
div.post.reply.highlighted {
background-color:#1d1d21;
border:1px solid #111
}
div.post.reply div.body a {
color:#81a2be
}
div.post.reply div.body a:hover {
color:#5F89AC
}
div.post-hover {
border:1px solid #000!important;
box-shadow:none!important
}
div.thread:hover {
background-color:#1d1f21;
border-color:#000
}
p.intro span.subject {
color:#b294bb
}
p.intro span.name {
color:#C5C8C6
}
span.quote {
color:#adbd68
}
span.heading {
color:#F20
}
form table tr th {
background:#282a2e;
border:1px solid #111;
color:#C5C8C6
}
div.ban h2 {
background:#FCA;
color:inherit
}
div.ban {
border-color:#800
}
div.ban p {
color:#000
}
div.pages {
background:#1d1f21;
border-color:#1d1f21
}
div.pages a.selected {
color:#81a2be;
font-weight:700
}
div.boardlist {
background-color:#282a2e!important;
color:#C5C8C6
}
div.boardlist:nth-of-type(1) {
border-bottom:1px solid #111!important;
box-shadow:0 0 3px 0 #111
}
div.boardlist a {
color:#81a2be
}
hr {
background-color:#282a2e;
border:0;
height:1px
}
div#options_div {
background-color:#282a2e
}
div.options_tab_icon {
color: #AAA
}
div.options_tab_icon:hover {
background-color: #111
}
div.options_tab_icon.active {
color: #F20
}
div.blotter {
color:#F20
}
span.omitted {
color:#707070
}
p.intro a, span.omitted a {
text-decoration:none
}
form#quick-reply {
padding-right:1px;
border: 1px solid #111
}
span.capcode {
background-color: #000;
padding:2px 5px;
border-radius: 10px
}
div#watchlist {
border:1px solid #111;
background-color:#282a2e
}
div#watchlist a,a.watchThread {
color:#81a2be;
text-decoration:none
}
div#watchlist a:hover,a.watchThread:hover {
color:#5F89AC
}
/* Keep small thumbnails */
a:not([data-expanded="true"]) .post-image{
width:auto!important;
height:auto!important;
max-height:200px!important;
max-width:200px!important
}

View File

@ -223,8 +223,7 @@
</style>
</head>
<body>
<body class="8chan index">
<div id="main">
<header>

View File

@ -6,7 +6,7 @@
<title>{{ board.url }} - {{ board.name }}</title>
{% endblock %}
</head>
<body>
<body class="8chan {% if mod %}is-moderator{% else %}is-not-moderator{% endif %}" data-stylesheet="{% if config.default_stylesheet.1 != '' and not mod %}{{ config.default_stylesheet.1 }}{% else %}default{% endif %}">
{{ boardlist.top }}
{% if pm %}<div class="top_notice">You have <a href="?/PM/{{ pm.id }}">an unread PM</a>{% if pm.waiting > 0 %}, plus {{ pm.waiting }} more waiting{% endif %}.</div><hr />{% endif %}
{% if config.url_banner %}<img class="banner" src="{{ config.url_banner }}" {% if config.banner_width or config.banner_height %}style="{% if config.banner_width %}width:{{ config.banner_width }}px{% endif %};{% if config.banner_width %}height:{{ config.banner_height }}px{% endif %}" {% endif %}alt="" />{% endif %}

View File

@ -14,7 +14,7 @@
{% include 'header.html' %}
<title>{{ board.url }} - {{ board.title|e }}</title>
</head>
<body>
<body class="8chan {% if mod %}is-moderator{% else %}is-not-moderator{% endif %}" data-stylesheet="{% if config.default_stylesheet.1 != '' and not mod %}{{ config.default_stylesheet.1 }}{% else %}default{% endif %}">
{{ boardlist.top }}
{% if pm %}<div class="top_notice">You have <a href="?/PM/{{ pm.id }}">an unread PM</a>{% if pm.waiting > 0 %}, plus {{ pm.waiting }} more waiting{% endif %}.</div><hr />{% endif %}

View File

@ -96,11 +96,6 @@ var saved = {};
var selectedstyle = '{% endraw %}{{ config.default_stylesheet.0|addslashes }}{% raw %}';
/*var styles = {
{% endraw %}
{% for stylesheet in stylesheets %}{% raw %}'{% endraw %}{{ stylesheet.name|addslashes }}{% raw %}' : '{% endraw %}{{ stylesheet.uri|addslashes }}{% raw %}',
{% endraw %}{% endfor %}{% raw %}
};*/
var board_name = false;
function changeStyle(styleName, link) {
@ -115,36 +110,78 @@ function changeStyle(styleName, link) {
{% endif %}
{% raw %}
if (!document.getElementById('stylesheet')) {
var s = document.createElement('link');
s.rel = 'stylesheet';
s.type = 'text/css';
s.id = 'stylesheet';
// Find the <dom> for the stylesheet. May be nothing.
var domStylesheet = document.getElementById('stylesheet');
// Determine if this stylesheet is the default.
var setToDefault = ( styles[styleName] == "" || styles[styleName] == "/stylesheets/" );
// Turn "Yotsuba B" to "yotsuba_b"
var attributeName = styleName.replace(/[^a-z0-9_\-]/gi, '_').toLowerCase();
if( !domStylesheet && !setToDefault ) {
domStylesheet = document.createElement('link');
domStylesheet.rel = 'stylesheet';
domStylesheet.type = 'text/css';
domStylesheet.id = 'stylesheet';
var x = document.getElementsByTagName('head')[0];
x.appendChild(s);
x.appendChild(domStylesheet);
}
{% endraw %}
var root = "{{ config.root }}";
{% raw %}
root = root.replace(/\/$/, "");
document.getElementById('stylesheet').href = root + styles[styleName];
selectedstyle = styleName;
if (document.getElementsByClassName('styles').length != 0) {
var styleLinks = document.getElementsByClassName('styles')[0].childNodes;
for (var i = 0; i < styleLinks.length; i++) {
styleLinks[i].className = '';
if( !setToDefault ) {
{% endraw %}
var root = "{{ config.root }}";
{% raw %}
root = root.replace(/\/$/, "");
domStylesheet.href = root + styles[styleName];
selectedstyle = styleName;
if (document.getElementsByClassName('styles').length != 0) {
var styleLinks = document.getElementsByClassName('styles')[0].childNodes;
for (var i = 0; i < styleLinks.length; i++) {
styleLinks[i].className = '';
}
}
if (link) {
link.className = 'selected';
}
}
if (link) {
link.className = 'selected';
else if( domStylesheet ) {
domStylesheet.parentNode.removeChild( domStylesheet );
}
if (typeof $ != 'undefined')
// Fix the classes on the body tag.
var body = document.getElementsByTagName('body')[0];
if( body ) {
var bodyClasses = document.getElementsByTagName('body')[0].getAttribute('class').split(" ");
var bodyClassesNew = [];
for( i = 0; i < bodyClasses.length; ++i ) {
var bodyClass = bodyClasses[ i ];
// null class from a double-space.
if( bodyClass == "" ) {
continue;
}
if( bodyClass.indexOf( "stylesheet-" ) == 0 ) {
continue;
}
bodyClassesNew.push( bodyClass );
}
// Add stylesheet-yotsuba_b at the end.
bodyClassesNew.push( "stylesheet-" + attributeName );
body.setAttribute( 'class', bodyClassesNew.join(" ") );
body.setAttribute( 'data-stylesheet', attributeName );
}
if (typeof $ != 'undefined') {
$(window).trigger('stylesheet', styleName);
}
}
@ -190,7 +227,7 @@ function init_stylechooser() {
}
}
}
{% endraw%}
{% endraw %}
{% else %}
{% raw %}
if (localStorage.stylesheet) {
@ -208,10 +245,13 @@ function init_stylechooser() {
function get_cookie(cookie_name) {
var results = document.cookie.match ( '(^|;) ?' + cookie_name + '=([^;]*)(;|$)');
if (results)
if (results) {
return (unescape(results[2]));
else
}
else {
return null;
}
}
function highlightReply(id) {

View File

@ -1,40 +1,5 @@
<fieldset>
<legend>{% trans 'Boards' %}</legend>
<ul>
{% for board in boards %}
{% if board.uri in mod.boards or mod.boards[0] == '*' %}
<li>
<a href="?/{{ config.board_path|sprintf(board.uri) }}{{ config.file_index }}">{{ config.board_abbreviation|sprintf(board.uri) }}</a>
-
{{ board.title|e }}
{% if board.subtitle %}
<small>&mdash;
{% if config.allow_subtitle_html %}
{{ board.subtitle }}
{% else %}
{{ board.subtitle|e }}
{% endif %}
</small>
{% endif %}
{% if mod.type == "20" %}
<a href="?/settings/{{ board.uri }}"><small>[{% trans 'settings' %}]</small></a>
{% endif %}
{% if mod|hasPermission(config.mod.manageboards) %}
<a href="?/edit/{{ board.uri }}"><small>[{% trans 'edit' %}]</small></a>
{% endif %}
</li>
{% endif %}
{% endfor %}
{% if mod|hasPermission(config.mod.newboard) %}
<li style="margin-top:15px"><a href="?/new-board"><strong>{% trans 'Create new board' %}</strong></a></li>
{% endif %}
</ul>
</fieldset>
<fieldset>
<!-- Messages -->
<fieldset class="mod-dash mod-dash-set mod-dash-messages">
<legend>{% trans 'Messages' %}</legend>
<ul>
{% if mod|hasPermission(config.mod.noticeboard) %}
@ -78,7 +43,8 @@
</ul>
</fieldset>
<fieldset>
<!-- Administration -->
<fieldset class="mod-dash mod-dash-set mod-dash-messages">
<legend>{% trans 'Administration' %}</legend>
<ul>
@ -91,7 +57,6 @@
{% if mod.type != 20 %}<a href="?/reports/global">Global reports ({{global_reports}})</a>{% endif %}
{% if reports > 0 %}</strong>{% endif %}
</li>
{% endif %}
{% if mod|hasPermission(config.mod.view_banlist) %}
<li><a href="?/bans">{% trans 'Ban list' %}</a></li>
@ -123,57 +88,99 @@
</ul>
</fieldset>
<!-- Search -->
{% if mod|hasPermission(config.mod.search) %}
<fieldset>
<legend>{% trans 'Search' %}</legend>
<ul>
<li>
{% include 'mod/search_form.html' %}
</li>
</ul>
</fieldset>
<fieldset class="mod-dash mod-dash-set mod-dash-search">
<legend>{% trans 'Search' %}</legend>
<ul>
<li>
{% include 'mod/search_form.html' %}
</li>
</ul>
</fieldset>
{% endif %}
{% if config.mod.dashboard_links|count %}
<fieldset>
<legend>{% trans 'Other' %}</legend>
<!-- Boards -->
<fieldset class="mod-dash mod-dash-set mod-dash-boards">
<legend>{% trans 'Boards' %}</legend>
<ul>
{% for label,link in config.mod.dashboard_links %}
<li><a href="{{ link }}">{{ label }}</a></li>
{% endfor %}
</ul>
</fieldset>
{% endif %}
{% if config.debug %}
<fieldset>
<legend>{% trans 'Debug' %}</legend>
<ul>
<li><a href="?/debug/antispam">{% trans 'Anti-spam' %}</a></li>
<li><a href="?/debug/recent">{% trans 'Recent posts' %}</a></li>
{% if mod|hasPermission(config.mod.debug_sql) %}
<li><a href="?/debug/sql">{% trans 'SQL' %}</a></li>
{% endif %}
</ul>
</fieldset>
{% endif %}
{% if newer_release %}
<fieldset>
<legend>Update</legend>
<ul>
<ul>
{% for board in boards %}
{% if board.uri in mod.boards or mod.boards[0] == '*' %}
<li>
A newer version of Tinyboard
(<strong>v{{ newer_release.massive }}.{{ newer_release.major }}.{{ newer_release.minor }}</strong>) is available!
See <a href="http://tinyboard.org">http://tinyboard.org/</a> for upgrade instructions.
<a href="?/{{ config.board_path|sprintf(board.uri) }}{{ config.file_index }}">{{ config.board_abbreviation|sprintf(board.uri) }}</a>
-
{{ board.title|e }}
{% if board.subtitle %}
<small>&mdash;
{% if config.allow_subtitle_html %}
{{ board.subtitle }}
{% else %}
{{ board.subtitle|e }}
{% endif %}
</small>
{% endif %}
{% if mod.type == "20" %}
<a href="?/settings/{{ board.uri }}"><small>[{% trans 'settings' %}]</small></a>
{% endif %}
{% if mod|hasPermission(config.mod.manageboards) %}
<a href="?/edit/{{ board.uri }}"><small>[{% trans 'edit' %}]</small></a>
{% endif %}
</li>
</ul>
</fieldset>
{% endif %}
{% endfor %}
{% if mod|hasPermission(config.mod.newboard) %}
<li style="margin-top:15px"><a href="?/new-board"><strong>{% trans 'Create new board' %}</strong></a></li>
{% endif %}
</ul>
</fieldset>
<!-- Misc -->
{% if config.mod.dashboard_links|count %}
<fieldset class="mod-dash mod-dash-set mod-dash-misc">
<legend>{% trans 'Other' %}</legend>
<ul>
{% for label,link in config.mod.dashboard_links %}
<li><a href="{{ link }}">{{ label }}</a></li>
{% endfor %}
</ul>
</fieldset>
{% endif %}
<fieldset>
<!-- Debug Information -->
{% if config.debug %}
<fieldset class="mod-dash mod-dash-set mod-dash-debug">
<legend>{% trans 'Debug' %}</legend>
<ul>
<li><a href="?/debug/antispam">{% trans 'Anti-spam' %}</a></li>
<li><a href="?/debug/recent">{% trans 'Recent posts' %}</a></li>
{% if mod|hasPermission(config.mod.debug_sql) %}
<li><a href="?/debug/sql">{% trans 'SQL' %}</a></li>
{% endif %}
</ul>
</fieldset>
{% endif %}
<!-- Update -->
{% if newer_release %}
<fieldset class="mod-dash mod-dash-set mod-dash-update">
<legend>Update</legend>
<ul>
<li>
A newer version of Tinyboard
(<strong>v{{ newer_release.massive }}.{{ newer_release.major }}.{{ newer_release.minor }}</strong>) is available!
See <a href="http://tinyboard.org">http://tinyboard.org/</a> for upgrade instructions.
</li>
</ul>
</fieldset>
{% endif %}
<!-- Account Actions -->
<fieldset class="mod-dash mod-dash-set mod-dash-account">
<legend>{% trans 'User account' %}</legend>
<ul>

View File

@ -0,0 +1 @@
<link rel="stylesheet" media="screen" href="{{ config.uri_stylesheets }}mod/mod.css" />

View File

@ -1,26 +1,56 @@
<div class="report">
<hr>
{% trans 'Board' %}: <a href="?/{{ report.board }}/{{ config.file_index }}">{{ config.board_abbreviation|sprintf(report.board) }}</a>
<br>
{% trans 'Reason' %}: {{ report.reason }}
<br>
{% trans 'Report date' %}: {{ report.time|date(config.post_date) }}
<br>
{% if mod|hasPermission(config.mod.show_ip, report.board) %}
{% trans 'Reported by' %}: <a href="?/IP/{{ report.ip }}">{{ report.ip }}</a>
<br>
{% endif %}
{% if mod|hasPermission(config.mod.report_dismiss, report.board) or mod|hasPermission(config.mod.report_dismiss_ip, report.board) %}
<hr>
{% if mod|hasPermission(config.mod.report_dismiss, report.board) %}
<a title="{% trans 'Discard abuse report' %}" href="?/reports/{{ report.id }}/dismiss/{{ token }}">Dismiss</a>
{% endif %}
{% if mod|hasPermission(config.mod.report_dismiss_ip, report.board) %}
{% if mod|hasPermission(config.mod.report_dismiss, report.board) %}
|
<li class="report-item">
<div class="report" id="report-{{ report.id }}">
<span class="report-reason">{% if report.reason %}{{ report.reason }}{% else %}<em>{% trans 'No reason given.' %}</em>{% endif %}</span>
<ul class="report-details">
<li class="report-detail detail-date">
<span class="detail-name">{% trans 'Report date' %}:</span>
<span class="detail-value">{{ report.time|date(config.post_date) }}</span>
</li>
{% if mod|hasPermission(config.mod.show_ip, report.board) %}
<li class="report-detail detail-date">
<span class="detail-name">{% trans 'Reported by' %}:</span>
<span class="detail-value"><a href="?/IP/{{ report.ip }}">{{ report.ip }}</a></span>
</li>
{% endif %}
<a title="{% trans 'Discard all abuse reports by this IP address' %}" href="?/reports/{{ report.id }}/dismissall/{{ token_all }}">Dismiss+</a>
</ul>
{% if mod|hasPermission(config.mod.report_dismiss, report.board) or mod|hasPermission(config.mod.report_dismiss_ip, report.board) %}
<ul class="report-actions">
{% if mod|hasPermission(config.mod.report_dismiss, report.board) %}
<li class="report-action">
<a class="action-item action-available" title="{% trans 'Discard abuse report' %}" href="{{uri_dismiss}}/{{token_dismiss}}">Dismiss</a>
</li>
{% endif %}
{% if mod|hasPermission(config.mod.report_dismiss_ip, report.board) %}
<li class="report-action">
<a class="action-item action-available" title="{% trans 'Discard all abuse reports by this IP address' %}" href="{{uri_ip}}/{{token_ip}}">Dismiss+</a>
</li>
{% endif %}
{% if global and mod|hasPermission(config.mod.report_demote, report.board) %}
<li class="report-action">
{% if clean.clean_local %}
<span class="content-action-item content-action-unavailable" title="{% trans 'Content is permitted by board rules' %}">Demote</span>
{% elseif report.local %}
<a class="action-item action-available" title="{% trans 'Demote global abuse report to a local report' %}" href="{{uri_demote}}/{{token_demote}}">Demote</a>
{% else %}
<span class="action-item action-unavailable" title="{% trans 'Report has already been dismissed locally' %}">Demote</span>
{% endif %}
</li>
{% elseif not global and mod|hasPermission(config.mod.report_promote, report.board) %}
<li class="report-action">
{% if clean.clean_global %}
<span class="content-action-item content-action-unavailable" title="{% trans 'Content is permitted by global rules' %}">Promote</span>
{% elseif report.global %}
<span class="action-item action-unavailable" title="{% trans 'Report is already a global report' %}">Promote</span>
{% else %}
<a class="action-item action-available" title="{% trans 'Promote local abuse report to a global report' %}" href="{{uri_promote}}/{{token_promote}}">Promote</a>
{% endif %}
</li>
{% endif %}
</ul>
{% endif %}
{% endif %}
</div>
</li>

View File

@ -0,0 +1,67 @@
<li class="mod-report">
<h2 class="report-header">{{ report_title }}</h2>
<div class="report-content">
{{ content_html }}
<!-- Content Sweep Actions -->
<ul class="report-content-actions">
{% if mod|hasPermission(config.mod.report_dismiss_content, report.board) %}
<!-- Dismiss All -->
<li class="report-content-action">
<a class="content-action-item content-action-available" title="{% trans 'Discard all abuse reports on this content' %}" href="{{uri_content_dismiss}}/{{token_content_dismiss}}">Dismiss All</a>
</li>
{% endif %}
{% if global and mod|hasPermission(config.mod.report_demote, report.board) %}
<!-- Demote All -->
<li class="report-action">
{% if reports_can_demote %}
<a class="content-action-item content-action-available"title="{% trans 'Demote global abuse reports to local reports' %}" href="{{uri_content_demote}}/{{token_content_demote}}">Demote All</a>
{% elseif clean.clean_local %}
<span class="content-action-item content-action-unavailable" title="{% trans 'Content is permitted by board rules' %}">Demote All</span>
{% else %}
<span class="content-action-item content-action-unavailable" title="{% trans 'Reports have all been dismissed locally' %}">Demote All</span>
{% endif %}
</li>
{% elseif not global and mod|hasPermission(config.mod.report_promote, report.board) %}
<!-- Promote All -->
<li class="report-action">
{% if reports_can_promote %}
<a class="content-action-item content-action-available"title="{% trans 'Promote all local abuse reports to global reports' %}" href="{{uri_content_promote}}/{{token_content_promote}}">Promote All</a>
{% elseif clean.clean_global %}
<span class="content-action-item content-action-unavailable" title="{% trans 'Content is permitted by global rules' %}">Promote All</span>
{% else %}
<span class="content-action-item content-action-unavailable" title="{% trans 'Reports are already global reports' %}">Promote All</span>
{% endif %}
</li>
{% endif %}
{% if not clean.clean_local or not clean.clean_global %}
{% if mod|hasPermission(config.mod.clean, report.board) or mod|hasPermission(config.mod.clean_global, report.board) %}
<li class="report-content-action">
Clean&nbsp;
{% if not clean.clean_local and mod|hasPermission(config.mod.clean, report.board) %}
<!-- Clean Local -->
<a class="content-action-item content-action-available" title="{% trans 'Ignore and dismiss local abuse reports on this post for this board' %}" href="{{ uri_clean }}/{{ token_clean }}">(/{{ content_board }}/)</a>
&nbsp;
{% endif %}
{% if not clean.clean_global and mod|hasPermission(config.mod.clean_global, report.board) %}
<!-- Clean Global -->
<a class="content-action-item content-action-available" title="{% trans 'Ignore and demote global abuse reports on this post' %}" href="{{ uri_clean_global }}/{{ token_clean_global }}">(Global)</a>
{% if not clean.clean_local and mod|hasPermission(config.mod.clean, report.board) %}
&nbsp;
<!-- Clean Local + Global -->
<a class="content-action-item content-action-available" title="{% trans 'Ignore and dismiss local AND global abuse reports on this post' %}" href="{{ uri_clean_both }}/{{ token_clean_both }}">(/{{ content_board }}/+Global)</a>
{% endif %}
{% endif %}
</li>
{% endif %}
{% endif %}
</ul>
</div>
<ul class="report-list">
{{ reports_html }}
</ul>
</li>

View File

@ -1,6 +1,8 @@
{% if reports %}
<ul class="mod-reports reports-global">
{{ reports }}
</ul>
{% else %}
<p style="text-align:center" class="unimportant">({% trans 'There are no reports.' %})</p>
<p style="text-align:center" class="unimportant">({% trans 'There are no reports.' %})</p>
{% endif %}

View File

@ -6,9 +6,10 @@
active_page = "page";
</script>
{% include 'header.html' %}
{% if mod %}{% include 'mod/header.html' %}{% endif %}
<title>{{ title }}</title>
</head>
<body>
<body class="8chan {% if mod %}is-moderator{% else %}is-not-moderator{% endif %} stylesheet-{% if config.default_stylesheet.1 != '' and not mod %}{{ config.default_stylesheet.1 }}{% else %}default{% endif %}">
{% if pm %}<div class="top_notice">You have <a href="?/PM/{{ pm.id }}">an unread PM</a>{% if pm.waiting > 0 %}, plus {{ pm.waiting }} more waiting{% endif %}.</div><hr>{% endif %}
<header>
<h1>{{ title }}</h1>

View File

@ -1,4 +1,11 @@
{% if post.edited_at %}
<br>
<span class="unimportant edited">Post last edited at <em class="edited-time">{{ post.edited_at }}</em></span>
{% endif %}
<div class="post_modified">
{% if post.edited_at %}
<div class="content-status edited">{% trans 'Post last edited at' %} <em class="edited-time">{{ post.edited_at }}</em></div>
{% endif %}
{% if clean.clean_local == '1' %}
<div class="content-status clean-local">{% trans 'Board rules permit this content' %}</div>
{% endif %}
{% if clean.clean_global == '1' %}
<div class="content-status clean-global">{% trans 'Global rules permit this content' %}</div>
{% endif %}
</div>

View File

@ -22,8 +22,8 @@
{% if post.modifiers['ban message'] %}
{{ config.mod.ban_message|sprintf(post.modifiers['ban message']) }}
{% endif %}
{% include 'post/edited_at.html' %}
</div>
{% include 'post/edited_at.html' %}
</div>
<br/>
{% endfilter %}

View File

@ -1,7 +1,7 @@
{% filter remove_whitespace %}
{# tabs and new lines will be ignored #}
<div id="thread_{{ post.id }}" data-board="{{ board.uri }}">
<div class="thread" id="thread_{{ post.id }}" data-board="{{ board.uri }}">
{% if not index %}<a id="{{ post.id }}" class="post_anchor"></a>{% endif %}
{% include 'post/fileinfo.html' %}
@ -55,7 +55,6 @@
{% if post.modifiers['ban message'] %}
{{ config.mod.ban_message|sprintf(post.modifiers['ban message']) }}
{% endif %}
{% include 'post/edited_at.html' %}
</div>
{% if post.omitted or post.omitted_images %}
<span class="omitted">
@ -78,6 +77,7 @@
{% endif %} {% trans %}omitted. Click reply to view.{% endtrans %}
</span>
{% endif %}
{% include 'post/edited_at.html' %}
{% if not index %}
{% endif %}
</div>{% endfilter %}

View File

@ -11,7 +11,7 @@
{% if config.default_stylesheet.1 != '' %}<link rel="stylesheet" type="text/css" id="stylesheet" href="{{ config.uri_stylesheets }}{{ config.default_stylesheet.1 }}">{% endif %}
{% if config.font_awesome %}<link rel="stylesheet" href="{{ config.root }}{{ config.font_awesome_css }}">{% endif %}
</head>
<body>
<body class="8chan {% if mod %}is-moderator{% else %}is-not-moderator{% endif %}" data-stylesheet="{% if config.default_stylesheet.1 != '' and not mod %}{{ config.default_stylesheet.1 }}{% else %}default{% endif %}">
{{ boardlist.top }}
<header>
<h1>{{ settings.title }}</h1>

View File

@ -10,7 +10,7 @@
{% include 'header.html' %}
<title>{{ board.url }} - {% if config.thread_subject_in_title and thread.subject %}{{ thread.subject }}{% else %}{{ board.title|e }}{% endif %}</title>
</head>
<body>
<body class="8chan {% if mod %}is-moderator{% else %}is-not-moderator{% endif %}" data-stylesheet="{% if config.default_stylesheet.1 != '' and not mod %}{{ config.default_stylesheet.1 }}{% else %}default{% endif %}">
{{ boardlist.top }}
{% if pm %}<div class="top_notice">You have <a href="?/PM/{{ pm.id }}">an unread PM</a>{% if pm.waiting > 0 %}, plus {{ pm.waiting }} more waiting{% endif %}.</div><hr />{% endif %}
{% if config.url_banner %}<img class="board_image" src="{{ config.url_banner }}?board={{ board.uri }}" {% if config.banner_width or config.banner_height %}style="{% if config.banner_width %}width:{{ config.banner_width }}px{% endif %};{% if config.banner_width %}height:{{ config.banner_height }}px{% endif %}" {% endif %}alt="" />{% endif %}