diff --git a/inc/config.php b/inc/config.php
index 2ddbe905..d359c234 100644
--- a/inc/config.php
+++ b/inc/config.php
@@ -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'] = ' ## %s ';
// "## Custom" becomes lightgreen, italic and bold:
- //$config['custom_capcode']['Custom'] =' ## %s ';
+ $config['custom_capcode']['Custom'] =' ## %s ';
// "## Mod" makes everything purple, including the name and tripcode:
- //$config['custom_capcode']['Mod'] = array(
- // ' ## %s ',
- // 'color:purple', // Change name style; optional
- // 'color:purple' // Change tripcode style; optional
- //);
+ $config['custom_capcode']['Mod'] = array(
+ ' ## %s ',
+ '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(
- // ' ## %s ',
- // 'color:red;font-weight:bold', // Change name style; optional
- // 'color:red;font-weight:bold' // Change tripcode style; optional
- //);
+ $config['custom_capcode']['Admin'] = array(
+ ' ## %s ',
+ '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
diff --git a/inc/display.php b/inc/display.php
index 1a78e19b..9b4c2d83 100644
--- a/inc/display.php
+++ b/inc/display.php
@@ -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;
}
diff --git a/inc/functions.php b/inc/functions.php
index b2d945d2..48a399ec 100755
--- a/inc/functions.php
+++ b/inc/functions.php
@@ -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;
}
diff --git a/inc/instance-config.php b/inc/instance-config.php
index c2df3b5b..4c8c707e 100644
--- a/inc/instance-config.php
+++ b/inc/instance-config.php
@@ -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'] = '';
diff --git a/inc/mod/pages.php b/inc/mod/pages.php
index 3cdf1374..6aaaa2fe 100644
--- a/inc/mod/pages.php
+++ b/inc/mod/pages.php
@@ -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(
+ _('>>>/%s/ %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, ' '));
-
- 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) . '
';
-
- 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 {$ip} ");
+ }
+ 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 $ip ");
- 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;
diff --git a/install.php b/install.php
index 8b3815a2..07e94e7f 100644
--- a/install.php
+++ b/install.php
@@ -579,7 +579,8 @@ if ($step == 0) {
';
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 =
'I couldn\'t write to inc/instance-config.php with the new configuration, probably due to a permissions error.
@@ -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 .= '' . db_error() . ' ';
+ }
}
$page['title'] = 'Installation complete';
@@ -858,7 +867,8 @@ if ($step == 0) {
if (!empty($sql_errors)) {
$page['body'] .= 'SQL errors 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.
The errors encountered were:
Ignore errors and complete installation.
';
- } 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'] .= 'Delete install.php! I couldn\'t remove install.php . You will have to remove it manually.
';
- }*/
}
echo Element('page.html', $page);
-} elseif ($step == 5) {
+}
+elseif ($step == 5) {
$page['title'] = 'Installation complete';
$page['body'] = 'Thank you for using vichan. Please remember to report any bugs you discover.
';
diff --git a/mod.php b/mod.php
index 654d23fe..45c836ac 100644
--- a/mod.php
+++ b/mod.php
@@ -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
diff --git a/post.php b/post.php
index 40f71555..099ea6d7 100644
--- a/post.php
+++ b/post.php
@@ -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 {
diff --git a/stylesheets/mod/mod.css b/stylesheets/mod/mod.css
new file mode 100644
index 00000000..3c3a9bd9
--- /dev/null
+++ b/stylesheets/mod/mod.css
@@ -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: '';
+ }
\ No newline at end of file
diff --git a/stylesheets/style.css b/stylesheets/style.css
index 1df3c526..0a3ed8eb 100644
--- a/stylesheets/style.css
+++ b/stylesheets/style.css
@@ -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;
}
diff --git a/stylesheets/tomorrow.css b/stylesheets/tomorrow.css
new file mode 100644
index 00000000..1ddfcd3b
--- /dev/null
+++ b/stylesheets/tomorrow.css
@@ -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
+}
diff --git a/templates/8chan/index.html b/templates/8chan/index.html
index 2bad668d..3e97809d 100644
--- a/templates/8chan/index.html
+++ b/templates/8chan/index.html
@@ -223,8 +223,7 @@
-
-
+
{% endfilter %}
diff --git a/templates/post_thread.html b/templates/post_thread.html
index 51d1fb0c..33e953ee 100644
--- a/templates/post_thread.html
+++ b/templates/post_thread.html
@@ -1,7 +1,7 @@
{% filter remove_whitespace %}
{# tabs and new lines will be ignored #}
-
+
{% if not index %}
{% 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' %}
{% if post.omitted or post.omitted_images %}
@@ -78,6 +77,7 @@
{% endif %} {% trans %}omitted. Click reply to view.{% endtrans %}
{% endif %}
+ {% include 'post/edited_at.html' %}
{% if not index %}
{% endif %}
{% endfilter %}
diff --git a/templates/themes/basic/index.html b/templates/themes/basic/index.html
index 3376a68f..0ec18f9a 100644
--- a/templates/themes/basic/index.html
+++ b/templates/themes/basic/index.html
@@ -11,7 +11,7 @@
{% if config.default_stylesheet.1 != '' %} {% endif %}
{% if config.font_awesome %} {% endif %}
-
+
{{ boardlist.top }}