1028 lines
38 KiB
PHP
1028 lines
38 KiB
PHP
<?php
|
|
/*
|
|
* Ninechan Board Script
|
|
* by Flashwave <http://flash.moe>
|
|
* Distributed under the MIT-License
|
|
*/
|
|
|
|
// Set ninechan version, don't change this or it'll probably break things.
|
|
$version = '2.1x';
|
|
|
|
// Language file versions this version is compatible with
|
|
$langCompat = [
|
|
'2.1'
|
|
];
|
|
|
|
function ncCheckRemoteAddr(string $addr): float {
|
|
$addr = rawurlencode($addr);
|
|
$mail = rawurlencode('ninechan-ipintel@flash.moe');
|
|
// banned from the system, bother with this at some point
|
|
//$result = file_get_contents("https://check.getipintel.net/check.php?ip={$addr}&contact={$mail}");
|
|
return 0;
|
|
}
|
|
|
|
// Error messages
|
|
function error($data) {
|
|
|
|
global $version;
|
|
|
|
print '<!DOCTYPE html><html><head><meta charset="utf-8" /><title>ninechan error</title></head><body>';
|
|
print $data;
|
|
print '<hr />ninechan '. $version .'</body></html>';
|
|
exit;
|
|
|
|
}
|
|
|
|
// Include Configuration
|
|
if(@!include 'config.php')
|
|
error('Failed to load configuration file.');
|
|
|
|
// Getting configuration values
|
|
function getConfig($key) {
|
|
|
|
// Make $ninechan global
|
|
global $ninechan;
|
|
|
|
// Check if the key exists and return the proper string
|
|
return array_key_exists($key, $ninechan) ? $ninechan[$key] : null;
|
|
|
|
}
|
|
|
|
// Include language file
|
|
if(@!include 'lang/'. getConfig('lang') .'.php')
|
|
error('Failed to load language file.');
|
|
|
|
// Error Reporting
|
|
error_reporting(getConfig('exposeErrors') ? -1 : 0);
|
|
|
|
// Getting language string
|
|
function getLang($key) {
|
|
|
|
// Make $language global
|
|
global $language;
|
|
|
|
// Check if the key exists and return the proper string
|
|
return array_key_exists($key, $language) ? $language[$key] : 'Undefined index';
|
|
|
|
}
|
|
|
|
// Cleaning strings
|
|
function cleanString($string, $lower = false, $nospecial = false) {
|
|
|
|
// Run common sanitisation function over string
|
|
$string = htmlentities($string, ENT_QUOTES | ENT_IGNORE, getConfig('charset'));
|
|
$string = stripslashes($string);
|
|
$string = strip_tags($string);
|
|
|
|
// If set also make the string lowercase
|
|
if($lower)
|
|
$string = strtolower($string);
|
|
|
|
// If set remove all characters that aren't a-z or 0-9
|
|
if($nospecial)
|
|
$string = preg_replace('/[^a-z0-9]/', '', $string);
|
|
|
|
// Return clean string
|
|
return $string;
|
|
|
|
}
|
|
|
|
// Parsing tripcodes
|
|
function parseTrip($name) {
|
|
|
|
// Match ! or # and everything after it
|
|
if(preg_match("/(#|!)(.*)/", $name, $matches)) {
|
|
|
|
// Get the cap code
|
|
$cap = $matches[2];
|
|
$cap = mb_convert_encoding($cap, 'SJIS', 'UTF-8');
|
|
$cap = str_replace('#', '', $cap);
|
|
$cap = str_replace('&', '&', $cap);
|
|
$cap = str_replace('"', '"', $cap);
|
|
$cap = str_replace("'", ''', $cap);
|
|
$cap = str_replace('<', '<', $cap);
|
|
$cap = str_replace('>', '>', $cap);
|
|
|
|
// Create the salt
|
|
$salt = substr($cap .'H.', 1, 2);
|
|
$salt = preg_replace('/[^.\/0-9:;<=>?@A-Z\[\\\]\^_`a-z]/', '.', $salt);
|
|
$salt = strtr($salt, ':;<=>?@[\]^_`', 'ABCDEFGabcdef');
|
|
|
|
// Generate tripcode
|
|
$trip = substr(crypt($cap, $salt), -10);
|
|
|
|
// Return tripcode
|
|
return $trip;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Parsing post tags
|
|
function parsePost($content){
|
|
|
|
// Get bbcodes file
|
|
$bbcodes = json_decode(file_get_contents(getConfig('bbCodes')), true);
|
|
|
|
// Parse bbcodes
|
|
$content = preg_replace(array_flip($bbcodes), $bbcodes, $content);
|
|
|
|
// Add newlines and return
|
|
return nl2br($content);
|
|
|
|
}
|
|
|
|
// Generating Random Password
|
|
function generatePassword() {
|
|
|
|
// Set characters to work with
|
|
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_@#$!*\/[]{}=+';
|
|
|
|
// Generate the string
|
|
for($i = 0, $pass = ''; $i < 34; $i++) {
|
|
$index = rand(0, mb_strlen($chars) - 1);
|
|
$pass .= mb_substr($chars, $index, 1);
|
|
}
|
|
|
|
// Return it
|
|
return $pass;
|
|
|
|
}
|
|
|
|
// Moderation
|
|
function nMod($mode, $id, $action) {
|
|
|
|
// Make SQL variables global
|
|
global $sql, $sqldb;
|
|
|
|
// Switch to the proper mode
|
|
switch($mode) {
|
|
|
|
// Banning a poster
|
|
case 'ban':
|
|
$prepare = "UPDATE `". $sql['table'] ."` SET `ban` = :action WHERE `id` = :id";
|
|
break;
|
|
|
|
// Deleting a post
|
|
case 'del':
|
|
$prepare = "DELETE FROM `". $sql['table'] ."` WHERE `id` = :id";
|
|
break;
|
|
|
|
// Prune an entire thread
|
|
case 'prune':
|
|
$prepare = "DELETE FROM `". $sql['table'] ."` WHERE `tid` = :id";
|
|
break;
|
|
|
|
// Lock a thread
|
|
case 'lock':
|
|
$prepare = "UPDATE `". $sql['table'] ."` SET `locked` = :action WHERE `tid` = :id";
|
|
break;
|
|
|
|
// Return false if there's no proper option
|
|
default:
|
|
return false;
|
|
|
|
}
|
|
|
|
$query = $sqldb->prepare($prepare);
|
|
$action = $action ? '1' : '0';
|
|
|
|
$query->bindParam(':id', $id, PDO::PARAM_INT);
|
|
$query->bindParam(':action', $action, PDO::PARAM_INT);
|
|
|
|
$query->execute();
|
|
|
|
// Return true if successful
|
|
return true;
|
|
|
|
}
|
|
|
|
// Board HTML header
|
|
function nHead() {
|
|
|
|
$header = '<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="'. getConfig('charset') .'" />
|
|
<title>'. getConfig('title') .'</title>'.
|
|
(getConfig('desc') ? '<meta name="description" content="'. getConfig('desc') .'" />' : '')
|
|
.'<script type="text/javascript" charset="'. getConfig('charset') .'" src="ninechan.js"></script>
|
|
<script type="text/javascript">
|
|
|
|
ninechan.cookiePrefix = "'. getConfig('cookiePrefix') .'";
|
|
|
|
window.onload = function() {
|
|
ninechan.init();
|
|
}
|
|
|
|
</script>';
|
|
|
|
// Check if styles are enabled
|
|
if(getConfig('styles')) {
|
|
|
|
// Take the array
|
|
$styles = getConfig('styles');
|
|
|
|
// Iterate over all styles
|
|
foreach($styles as $url => $name) {
|
|
|
|
// Get the key of the first entry
|
|
$main = key($styles);
|
|
|
|
// Append styles to the header
|
|
$header .= '<link rel="'. ($url == $main ? null : 'alternate ') .'stylesheet" type="text/css" href="'. $url .'" title="'. $name .'" '. ($url == $main ? null : 'disabled ') .'/>';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$header .= '</head>
|
|
<body>
|
|
<h1><a href="./">'. getConfig('title') .'</a></h1>'.
|
|
(getConfig('desc') ? ' <i>'. getConfig('desc') .'</i>' : '')
|
|
.'<hr />';
|
|
|
|
$header .= '<h5>';
|
|
foreach($GLOBALS['navLinks'] as $title => $url)
|
|
$header .= " [<a href='{$url}'>{$title}</a>]";
|
|
foreach($GLOBALS['navBoards'] as $link => $board)
|
|
$header .= " [<a href='/{$link}/'>{$board[2]}</a>]";
|
|
$header .= '</h5><hr/><h6>[<a href="?v=index">'. getLang('INDEX') .'</a>] [<a href="?v=soup">'. getLang('NEWTHREAD') .'</a>]</h6><hr />';
|
|
|
|
return $header;
|
|
|
|
}
|
|
|
|
// Board HTML footer
|
|
function nFoot() {
|
|
|
|
// Make $version global
|
|
global $version;
|
|
|
|
$footer = '<hr />';
|
|
|
|
// Check if styles are enabled
|
|
if(getConfig('styles')) {
|
|
|
|
// Take the array
|
|
$styles = getConfig('styles');
|
|
|
|
$footer .= '<h6>';
|
|
|
|
// List every style
|
|
foreach($styles as $name)
|
|
$footer .= '[<a href="javascript:void(0);" onclick="ninechan.setStyle(\''.$name.'\');">'.$name.'</a>]'."\r\n";
|
|
|
|
$footer .= '</h6>';
|
|
|
|
}
|
|
|
|
/*
|
|
Please retain the full copyright notice below including the link to flash.moe.
|
|
This not only gives respect to the amount of time given freely by the developer
|
|
but also helps build interest, traffic and use of ninechan and other projects.
|
|
*/
|
|
|
|
$footer .= '<h6>
|
|
<a href="https://patchii.net/flash/ninechan" target="_blank">ninechan</a>
|
|
'. (getConfig('showVersion') ? $version : '') .'
|
|
© <a href="https://flash.moe" target="_blank">flashwave</a>
|
|
</h6>
|
|
</body>
|
|
</html>';
|
|
|
|
// Return the footer
|
|
return $footer;
|
|
|
|
}
|
|
|
|
// Setting cookies
|
|
function nCookie($name, $content) {
|
|
|
|
// Execute setcookie()
|
|
setcookie(
|
|
getConfig('cookiePrefix') . $name,
|
|
$content,
|
|
time() + getConfig('cookieLifetime'),
|
|
getConfig('cookiePath'),
|
|
$_SERVER['SERVER_NAME']
|
|
);
|
|
|
|
}
|
|
|
|
// Check if the IP is banned
|
|
function checkBan($ip) {
|
|
|
|
// Make SQL variables global
|
|
global $sql, $sqldb;
|
|
|
|
// Base64 encode IP
|
|
$ip = base64_encode($ip);
|
|
|
|
// Prepare the statement
|
|
$banCheck = $sqldb->prepare("SELECT * FROM `". $sql['table'] ."` WHERE `ip` = :ip AND `ban` = '1'");
|
|
|
|
// Append ip the statement
|
|
$banCheck->bindParam(':ip', $ip, PDO::PARAM_STR);
|
|
|
|
// Execute statement
|
|
$banCheck->execute();
|
|
|
|
// Fetch array
|
|
$return = $banCheck->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// Return the array
|
|
return $return;
|
|
|
|
}
|
|
|
|
// Getting thread data
|
|
function getPosts($id = 0) {
|
|
|
|
// Make SQL variables global
|
|
global $sql, $sqldb;
|
|
|
|
// Prepare the statement
|
|
$getPosts = $sqldb->prepare("SELECT * FROM `". $sql['table'] ."` WHERE ". ($id ? "`tid` = :tid" : "`op` = '1'") ." ORDER BY `". ($id ? "id`" : "lastreply` DESC"));
|
|
|
|
// Bind the ID to the statement
|
|
if($id)
|
|
$getPosts->bindParam(':tid', $id, PDO::PARAM_INT);
|
|
|
|
// Execute statement
|
|
$getPosts->execute();
|
|
|
|
// Fetch array
|
|
$return = $getPosts->fetchAll(PDO::FETCH_BOTH);
|
|
|
|
// Return the array
|
|
return $return;
|
|
|
|
}
|
|
|
|
function verifyCaptcha($response) {
|
|
|
|
// Attempt to get the response
|
|
$resp = @file_get_contents('https://www.google.com/recaptcha/api/siteverify?secret='. getConfig('reCaptchaPrivate') .'&response='. $response);
|
|
|
|
// In the highly unlikely case that it failed to get anything forge a false
|
|
if(!$resp)
|
|
error('Could not connect to the ReCAPTCHA server.');
|
|
|
|
// Decode the response JSON from the servers
|
|
$resp = json_decode($resp, true);
|
|
|
|
// Return shit
|
|
return $resp;
|
|
|
|
}
|
|
|
|
// Check language version
|
|
if(!in_array($langVersion, $langCompat)) {
|
|
|
|
print '<h2>The selected language file is incompatible with this version!</h2>';
|
|
print 'The version your language file was created for is <b>'. $langVersion .'</b>.<br />';
|
|
print 'Your version of ninechan is compatible with the following language file versions:<ul>';
|
|
foreach($langCompat as $ver) print '<li>'. $ver;
|
|
print '</ul>';
|
|
exit;
|
|
|
|
}
|
|
|
|
// Check dependencies
|
|
if(version_compare(phpversion(), '5.3.0', '<')) // PHP 5.3 or higher
|
|
error('<h2>'. getLang('PHP_OUTDATED') .'</h2>');
|
|
|
|
if(!extension_loaded('PDO')) // Check if PHP Data Objects is available
|
|
error('<h2>'. getLang('SQL_FUNCTION') .'</h2>');
|
|
|
|
// Connect to SQL using PDO
|
|
try {
|
|
$sqldb = new PDO($sql['dsn'], $sql['user'], $sql['pass']);
|
|
} catch(PDOException $e) { // Catch connection error
|
|
error('<h2>'. getLang('SQL_CONNECT') .'</h2>'. $e->getMessage());
|
|
}
|
|
|
|
// Initialise Database
|
|
$sqldb->query("
|
|
CREATE TABLE IF NOT EXISTS `". $sql['table'] ."` (
|
|
`id` int(16) unsigned NOT NULL AUTO_INCREMENT,
|
|
`title` varchar(255) COLLATE utf8_bin NOT NULL,
|
|
`name` varchar(255) COLLATE utf8_bin DEFAULT NULL,
|
|
`trip` varchar(255) COLLATE utf8_bin DEFAULT NULL,
|
|
`email` varchar(255) COLLATE utf8_bin DEFAULT NULL,
|
|
`date` int(11) unsigned NOT NULL,
|
|
`content` text COLLATE utf8_bin NOT NULL,
|
|
`password` varchar(255) COLLATE utf8_bin NOT NULL,
|
|
`ip` varchar(255) COLLATE utf8_bin NOT NULL,
|
|
`op` tinyint(1) unsigned NOT NULL DEFAULT '1',
|
|
`tid` int(16) unsigned NOT NULL DEFAULT '0',
|
|
`locked` tinyint(1) unsigned NOT NULL DEFAULT '0',
|
|
`ban` tinyint(1) unsigned NOT NULL DEFAULT '0',
|
|
`lastreply` int(11) unsigned NOT NULL DEFAULT '0',
|
|
PRIMARY KEY (`id`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
|
|
");
|
|
|
|
// Session
|
|
session_start(); // Start a session
|
|
$auth = @$_SESSION['mod']; // Set an alias for mod
|
|
|
|
$remoteAddr = $_SERVER['REMOTE_ADDR'];
|
|
$remoteAddrKey = 'ip_' . $remoteAddr;
|
|
if(true || !isset($_SESSION[$remoteAddrKey]) || $_SESSION[$remoteAddrKey] < 0) {
|
|
$_SESSION[$remoteAddrKey] = $remoteAddrState = ncCheckRemoteAddr($remoteAddr);
|
|
if($remoteAddrState >= .8)
|
|
file_put_contents(__DIR__ . '/../shame.txt', sprintf('[%s] %s - %F - %s%s', date('Y-m-d H:i:s'), $remoteAddr, $remoteAddrState, htmlspecialchars($_SERVER['HTTP_USER_AGENT'] ?? '(none)'), PHP_EOL), FILE_APPEND | LOCK_EX);
|
|
} else
|
|
$remoteAddrState = $_SESSION[$remoteAddrKey];
|
|
|
|
// Print the header
|
|
print nHead();
|
|
|
|
// Exit if board is set as closed in the config file
|
|
if($ninechan['closed']) {
|
|
|
|
print '<h3>'. getLang('BOARD_CLOSED') .'</h3>'. getLang('REASON') .': '. getConfig('closedReason') .'';
|
|
print nFoot();
|
|
exit;
|
|
|
|
}
|
|
|
|
// Catch &t= and redirect it properly
|
|
if(isset($_GET['t'])) {
|
|
|
|
// Get the url and replace &t= with &id=
|
|
$newUrl = str_replace('t=', 'id=', $_SERVER['REQUEST_URI']);
|
|
|
|
// Redirect and exit
|
|
header('Location: '. $newUrl);
|
|
print '<meta http-equiv="refresh" content="2; URL="'. $newUrl .'" />';
|
|
exit;
|
|
|
|
}
|
|
|
|
// Check if the current IP is banned
|
|
if($remoteAddrState >= .6)
|
|
print '<div class="banmsg">You have been automatically banned from posting on this board. <a href="/bot.html">Click here for more info</a>.</div><hr />';
|
|
|
|
// Check if the current IP is banned
|
|
if(checkBan($_SERVER['REMOTE_ADDR']))
|
|
print '<div class="banmsg">'. getLang('USERBANNEDMSG') .'</div><hr />';
|
|
|
|
// Check if pass cookie is set and set it if it isn't
|
|
if(!isset($_COOKIE[getConfig('cookiePrefix') .'pass']))
|
|
nCookie('pass', generatePassword());
|
|
|
|
// Check if view variable is set
|
|
if(isset($_GET['v'])) {
|
|
|
|
// Switch to proper view
|
|
switch($_GET['v']) {
|
|
|
|
// Main index
|
|
case 'index':
|
|
// Section title
|
|
print '<h2>'. getLang('THREADS') .'</h2>';
|
|
|
|
// New thread link
|
|
print '<h3><a href="'. $_SERVER['PHP_SELF'] .'?v=soup">'. getLang('NEWTHREAD') .'</a></h3>';
|
|
|
|
// Get threads
|
|
$threads = array_chunk(getPosts(), getConfig('threadsPerPage'), true);
|
|
|
|
// If at least one post was returned print the list
|
|
if($threads) {
|
|
|
|
print '<ol>';
|
|
|
|
foreach($threads[(isset($_GET['p']) && ($_GET['p'] - 1) >= 0 && array_key_exists(($_GET['p'] - 1), $threads)) ? $_GET['p'] - 1 : 0] as $thread)
|
|
print '<li><a href="'. $_SERVER['PHP_SELF'] .'?v=thread&id='. $thread['tid'] .'">'. $thread['title'] .'</a> <span style="font-size: x-small;">[<b title="'. date(getConfig('dateFormat'), $thread['lastreply']) .'">'. (count(getPosts($thread['tid'])) - 1) .'</b>]</span>';
|
|
|
|
print '</ol>';
|
|
|
|
} else // Else return EMPTY
|
|
print '<h3>'. getLang('EMPTY') .'</h3>';
|
|
|
|
// Pagination
|
|
if(count($threads) > 1) {
|
|
|
|
print '<h5 style="margin-bottom: 10px;">[';
|
|
|
|
foreach($threads as $page => $pthreads)
|
|
print '<a href="?v=index&p='. ($page + 1) .'"> '. ($page + 1) .' </a>'. ($page == key(array_reverse($threads, true)) ? '' : '/');
|
|
|
|
print ']</h5>';
|
|
|
|
}
|
|
|
|
// New thread link
|
|
print '<h3><a href="'. $_SERVER['PHP_SELF'] .'?v=soup">'. getLang('NEWTHREAD') .'</a></h3>';
|
|
break;
|
|
|
|
// Thread view
|
|
case 'thread':
|
|
// Just return NONEXISTENT if t is invalid
|
|
if(!isset($_GET['id']) || !is_numeric($_GET['id'])) {
|
|
|
|
print('<h3>'. getLang('NONEXISTENT') .'</h3>');
|
|
break;
|
|
|
|
}
|
|
|
|
// Strip all non-numeric characters from the string and assign it to $threadId
|
|
$threadId = preg_replace('/\D/', '', $_GET['id']);
|
|
|
|
// Get thread data
|
|
$thread = getPosts($threadId);
|
|
|
|
// Check if the thread exists
|
|
if($thread) {
|
|
|
|
// Print post title
|
|
print '<h2>'. getLang('THREAD') .': '. $thread[0]['title'] .'</h2>';
|
|
|
|
// Check if thread is locked and if true display message
|
|
if($thread[0]['locked'])
|
|
print '<h3>'. getLang('LOCKED') .'</h3>';
|
|
else
|
|
print '<h3><a href='. $_SERVER['PHP_SELF'] .'?v=soup&id='. $thread[0]['tid'] .'>'. getLang('NEWREPLY') .'</a></h3>';
|
|
|
|
// Moderator tools
|
|
if($auth == $ninechan['modPass']) {
|
|
|
|
print '<h6>';
|
|
|
|
// Purge button
|
|
print '[<a href="'. $_SERVER['PHP_SELF'] .'?v=mod&del=purge&id='. $thread[0]['tid'] .'">'. getLang('PURGE') .'</a>]'."\r\n";
|
|
|
|
if($thread[0]['locked'])
|
|
print '[<a href="'. $_SERVER['PHP_SELF'] .'?v=mod&lock=false&id='. $thread[0]['tid'] .'">'. getLang('UNLOCK') .'</a>]';
|
|
else
|
|
print '[<a href="'. $_SERVER['PHP_SELF'] .'?v=mod&lock=true&id='. $thread[0]['tid'] .'">'. getLang('LOCK') .'</a>]';
|
|
|
|
print '</h6>';
|
|
|
|
}
|
|
|
|
// Print every post
|
|
foreach($thread as $post) {
|
|
|
|
// Set poster name
|
|
if(getConfig('forcedAnon')) {
|
|
|
|
$posterName = getConfig('anonName');
|
|
$posterTrip = null;
|
|
|
|
// Check if forced anon for mods or admins is enabled
|
|
} elseif((getConfig('modsAreAnon') === 1 && in_array($post['trip'], getConfig('modTrip'))) || (getConfig('adminsAreAnon') === 1 && in_array($post['trip'], getConfig('adminTrip')))) {
|
|
|
|
$posterName = getConfig('anonName');
|
|
$posterTrip = null;
|
|
|
|
// Check if forced trip anon for mods or admins is enabled
|
|
} elseif((getConfig('modsAreAnon') === 2 && in_array($post['trip'], getConfig('modTrip'))) || (getConfig('adminsAreAnon') === 2 && in_array($post['trip'], getConfig('adminTrip')))) {
|
|
|
|
$posterName = $post['name'];
|
|
$posterTrip = null;
|
|
|
|
} else {
|
|
|
|
// Check if name is set
|
|
$posterName = (empty($post['name']) ? getConfig('anonName') : $post['name']);
|
|
|
|
// Check if trip isset
|
|
$posterTrip = (empty($post['trip']) ? '' : ' <span class="trip">!'. $post['trip'] .'</span>');
|
|
|
|
}
|
|
|
|
print '<fieldset id="'. $post['id'] .'">';
|
|
|
|
print '<legend><b>'. $post['title'] .'</b> <a href="#'. $post['id'].'">'. getLang('BY') .'</a> <b>';
|
|
|
|
if(empty($post['email']))
|
|
print $posterName . $posterTrip;
|
|
else
|
|
print '<a href="mailto:'. $post['email'] .'">'. $posterName . $posterTrip .'</a>';
|
|
|
|
if(in_array($post['trip'], getConfig('adminTrip'))) // Check if tripcode is Admin
|
|
print ' <span class="admincap">## Admin</span>';
|
|
elseif(in_array($post['trip'], getConfig('modTrip'))) // Check if tripcode is Mod
|
|
print ' <span class="modcap">## Mod</span>';
|
|
|
|
print '</b></legend>';
|
|
|
|
// Parse BBcode and other things in the post content
|
|
print '<div class="postContent">'. parsePost($post['content']) .'</div><br />';
|
|
|
|
// Check if (USER WAS BANNED FOR THIS POST)
|
|
if($post['ban'])
|
|
print '<h6 class="ban">'. getLang('POSTBANNED') .'</h6>';
|
|
|
|
// Moderator tools
|
|
if($auth == $ninechan['modPass']) {
|
|
|
|
print '<h6>';
|
|
print '[<a href="'. $_SERVER['PHP_SELF'] .'?v=mod&del=true&id='. $post['id'] .'&tid='. $post['tid'] .'">'. getLang('DELETE') .'</a>]'."\r\n";
|
|
print '[<a href="'. $_SERVER['PHP_SELF'] .'?v=mod&ban='. ($post['ban'] ? 'false' : 'true') .'&id='. $post['id'] .'&tid='. $post['tid'] .'">'. getLang($post['ban'] ? 'UNBAN' : 'BAN') .'</a>]'."\r\n";
|
|
print '[IP: '.base64_decode($post['ip']).']';
|
|
print '</h6>';
|
|
|
|
}
|
|
|
|
// Date and ID
|
|
print '<h6><i>'. date(getConfig('dateFormat'), $post['date']) .' <a href="#'. $post['id'] .'">No.</a> <a href="'. $_SERVER['PHP_SELF'] .'?v=soup&id='. $post['tid'] .'&text=>>'. $post['id'] .'">'. $post['id'] .'</a> [<a href="'. $_SERVER['PHP_SELF'] .'?v=del&id='. $post['id'] .'" title="'. getLang('DELPOST') .'">X</a>]</i></h6>';
|
|
|
|
print '</fieldset>';
|
|
|
|
}
|
|
|
|
// Moderator tools
|
|
if($auth == $ninechan['modPass']) {
|
|
|
|
print '<h6>';
|
|
|
|
// Purge button
|
|
print '[<a href="'. $_SERVER['PHP_SELF'] .'?v=mod&del=purge&id='. $thread[0]['tid'] .'">'. getLang('PURGE') .'</a>]'."\r\n";
|
|
|
|
if($thread[0]['locked'])
|
|
print '[<a href="'. $_SERVER['PHP_SELF'] .'?v=mod&lock=false&id='. $thread[0]['tid'] .'">'. getLang('UNLOCK') .'</a>]';
|
|
else
|
|
print '[<a href="'. $_SERVER['PHP_SELF'] .'?v=mod&lock=true&id='. $thread[0]['tid'] .'">'. getLang('LOCK') .'</a>]';
|
|
|
|
print '</h6>';
|
|
|
|
}
|
|
|
|
// Check if thread is locked and if true display message
|
|
if($thread[0]['locked'])
|
|
print '<h3>'. getLang('LOCKED') .'</h3>';
|
|
else
|
|
print '<h3><a href="'. $_SERVER['PHP_SELF'] .'?v=soup&id='. $thread[0]['tid'] .'">'. getLang('NEWREPLY') .'</a></h3>';
|
|
|
|
} else {
|
|
|
|
// If not return NONEXISTENT and stop
|
|
print getLang('NONEXISTENT');
|
|
break;
|
|
|
|
}
|
|
break;
|
|
|
|
// Posting
|
|
case 'soup':
|
|
if($remoteAddrState >= .6) {
|
|
header('Location: /bot.html');
|
|
exit;
|
|
}
|
|
|
|
// Check if user is banned and if so don't display the form at all
|
|
if(checkBan($_SERVER['REMOTE_ADDR'])) {
|
|
print '<h2>'. getLang('USERBANNED') .'</h2>';
|
|
break;
|
|
}
|
|
|
|
// Print "global" form elements
|
|
print '<form method="post" action="'. $_SERVER['PHP_SELF'] .'?v=boat">';
|
|
print '<table id="postForm" class="postForm">';
|
|
|
|
// Predefine that we're creating a new thread
|
|
$newThread = true;
|
|
|
|
// Predefine that the thread isn't locked
|
|
$locked = false;
|
|
|
|
// Predefine $threadTitle to avoid E_WARNINGs
|
|
$threadTitle = '';
|
|
|
|
// Check if a thread ID is set
|
|
if(isset($_GET['id'])) {
|
|
|
|
// If so make sure it's numeric
|
|
if(!is_numeric($_GET['id'])) {
|
|
|
|
print '<h2>'. getLang('NOTNUMERIC') .'</h2>';
|
|
print '<a href="'. $_SERVER['PHP_SELF'] .'?v=index">'. getLang('RETURN') .'</a>';
|
|
break;
|
|
|
|
}
|
|
|
|
// Strip non-numerical characters from the string
|
|
$threadId = preg_replace('/\D/', '', $_GET['id']);
|
|
|
|
// Get the thread data
|
|
$thread = getPosts($threadId);
|
|
|
|
if(!$thread) {
|
|
print '<h2>'. getLang('NONEXISTENT') .'</h2>';
|
|
print '<a href="'. $_SERVER['PHP_SELF'] .'?v=index">'. getLang('RETURN') .'</a>';
|
|
break;
|
|
}
|
|
|
|
// Reassign $thread
|
|
$thread = $thread[0];
|
|
|
|
// Since we're not creating a thread set newThread to false
|
|
$newThread = false;
|
|
|
|
// Reassign $locked
|
|
$locked = $thread['locked'];
|
|
|
|
// Don't display posting form if thread is locked
|
|
if($locked) {
|
|
|
|
print '<h2>'. getLang('LOCKEDMSG') .'</h2>';
|
|
print '<meta http-equiv="refresh" content="2; URL="'. $_SERVER['PHP_SELF'] .'?v=thread&id='. $thread['tid'] .'" />';
|
|
|
|
} else {
|
|
|
|
print '<h2>'. getLang('RETO') .' '. $thread['title'] .' ['. $thread['tid'] .']</h2>';
|
|
print '<input type="hidden" name="tid" value="'. $thread['tid'] .'" />';
|
|
|
|
$threadTitle = 'Re: '. $thread['title'];
|
|
|
|
}
|
|
} else
|
|
print '<h2>'. getLang('NEWTHREAD') .'</h2>';
|
|
|
|
// Predefine the comment
|
|
$comment = isset($_GET['text']) ? $_GET['text']."\r\n" : '';
|
|
|
|
if(!$locked) { //-- Only display post page if thread isn't locked
|
|
print '<tr><td>'. getLang('NAME') .'</td><td><input name="name" type="text" value="'. @$_COOKIE[getConfig('cookiePrefix') .'name'] .'" /></td></tr>';
|
|
print '<tr><td>'. getLang('EMAIL') .'</td><td><input name="email" type="text" value="'. @$_COOKIE[getConfig('cookiePrefix') .'email'] .'" /></td></tr>';
|
|
print '<tr><td>'. getLang('TITLE') .'</td><td><input name="title" type="text" value="'. $threadTitle .'" /></td></tr>';
|
|
print '<tr><td>'. getLang('COMMENT') .'</td><td><textarea name="content" rows="6" cols="48">'. $comment .'</textarea></td></tr>';
|
|
|
|
if(getConfig('reCaptcha')) { // Display reCaptcha if enabled in config
|
|
|
|
print '<tr><td>'. getLang('VERIFICATION') .'</td><td>
|
|
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
|
|
<div class="g-recaptcha" data-sitekey="'. getConfig('reCaptchaPublic') .'" style="margin: auto; display: inline-block;"></div>
|
|
<noscript>
|
|
<div style="width: 302px; height: 352px; margin: auto; display: inline-block;">
|
|
<div style="width: 302px; height: 352px; position: relative;">
|
|
<div style="width: 302px; height: 352px; position: absolute;">
|
|
<iframe src="https://www.google.com/recaptcha/api/fallback?k='. getConfig('reCaptchaPublic') .'" frameborder="0" scrolling="no" style="width: 302px; height:352px; border-style: none;"></iframe>
|
|
</div>
|
|
<div style="width: 250px; height: 80px; position: absolute; border-style: none; bottom: 21px; left: 25px; margin: 0px; padding: 0px; right: 25px;">
|
|
<textarea id="g-recaptcha-response" name="g-recaptcha-response" class="g-recaptcha-response" style="width: 250px; height: 80px; border: 1px solid #c1c1c1; margin: 0px; padding: 0px; resize: none;" value=""></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</noscript></td></tr>';
|
|
|
|
}
|
|
|
|
print '<tr><td>'. getLang('PASSWORD') .'</td><td><input name="password" type="password" placeholder="'. getLang('PASSWORDCONTEXT') .'" value="'. @$_COOKIE[getConfig('cookiePrefix') .'pass'] .'" /> <input value="'. getLang('SUBMIT') .'" type="submit" /></td></tr>';
|
|
print '</table></form>';
|
|
|
|
}
|
|
break;
|
|
|
|
// Submitting posts
|
|
case 'boat':
|
|
if($remoteAddrState >= .6) {
|
|
header('Location: /bot.html');
|
|
exit;
|
|
}
|
|
|
|
// Check if IP banned
|
|
if(checkBan($_SERVER['REMOTE_ADDR'])) {
|
|
print '<h2>'. getLang('USERBANNED') .'</h2>';
|
|
break;
|
|
}
|
|
|
|
// Check reCaptcha
|
|
if(getConfig('reCaptcha')) {
|
|
|
|
// Verify the captcha
|
|
$reCaptcha = verifyCaptcha($_POST['g-recaptcha-response']);
|
|
|
|
// If reCaptcha is invalid die and display error message
|
|
if(!$reCaptcha['success']) {
|
|
|
|
print '<h2>'. getLang('INVALIDCAPTCHA') .'</h2><meta http-equiv="refresh" content="2; URL='. $_SERVER['PHP_SELF'] .'" />';
|
|
print nFoot();
|
|
exit;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Assign variables
|
|
$submitData = [
|
|
|
|
'title' => cleanString($_POST['title']),
|
|
'content' => cleanString($_POST['content']),
|
|
'name' => ($_N_NAME = cleanString($_POST['name'])),
|
|
'nameNT' => (strlen(strstr($_N_NAME, "#", true)) ? strstr($_N_NAME, "#", true) : $_N_NAME),
|
|
'trip' => parseTrip($_POST['name']),
|
|
'email' => ($_N_MAIL = ($_POST['email'] == 'noko' ? null : cleanString($_POST['email']))),
|
|
'date' => time(),
|
|
'password' => md5(strlen($_POST['password']) ? $_POST['password'] : generatePassword()),
|
|
'ip' => base64_encode($_SERVER['REMOTE_ADDR']),
|
|
'op' => ($_N_OP = (isset($_POST['tid']) ? 0 : 1)),
|
|
'id' => ($_N_OP ? ($sqldb->query("SELECT MAX(tid) AS `tid` FROM `". $sql['table'] ."` LIMIT 1")->fetch()['tid'] + 1) : cleanString($_POST['tid'], true, true)),
|
|
'noredir' => ($_N_MAIL == 'noko' ? true : false)
|
|
|
|
];
|
|
|
|
// Update last time replied
|
|
if(isset($_POST['tid'])) {
|
|
|
|
// Prepare the statement
|
|
$updateLast = $sqldb->prepare("UPDATE `". $sql['table'] ."` SET `lastreply` = :lastreply WHERE `tid` = :tid AND `op` = 1");
|
|
|
|
// Bind the parameters
|
|
$updateLast->bindParam(':lastreply', $submitData['date'], PDO::PARAM_INT);
|
|
$updateLast->bindParam(':tid', $_POST['tid'], PDO::PARAM_INT);
|
|
|
|
// Execute statement
|
|
$updateLast->execute();
|
|
|
|
}
|
|
|
|
// Assign cookies
|
|
nCookie('name', $submitData['name']);
|
|
nCookie('email', $submitData['email']);
|
|
nCookie('pass', $submitData['password']);
|
|
|
|
// Check if title is too short
|
|
if(empty($submitData['title']) || strlen($submitData['title']) < getConfig('titleMinLength')) {
|
|
print '<h2>'. getLang('TITLETOOSHORT') .'</h2>';
|
|
print '<meta http-equiv="refresh" content="2; URL='. $_SERVER['PHP_SELF'] .'" />';
|
|
break;
|
|
}
|
|
|
|
// Check if title is too long
|
|
if(strlen($submitData['title']) > getConfig('titleMaxLength')) {
|
|
print '<h2>'. getLang('TITLETOOLONG') .'</h2>';
|
|
print '<meta http-equiv="refresh" content="2; URL='. $_SERVER['PHP_SELF'] .'" />';
|
|
break;
|
|
}
|
|
|
|
// Check if comment is too short
|
|
if(empty($submitData['title']) || strlen($submitData['content']) < getConfig('commentMinLength')) {
|
|
print '<h2>'. getLang('COMMENTTOOSHORT') .'</h2>';
|
|
print '<meta http-equiv="refresh" content="2; URL='. $_SERVER['PHP_SELF'] .'" />';
|
|
break;
|
|
}
|
|
|
|
// Check if comment is too long
|
|
if(strlen($submitData['content']) > getConfig('commentMaxLength')) {
|
|
print '<h2>'. getLang('COMMENTTOOLONG') .'</h2>';
|
|
print '<meta http-equiv="refresh" content="2; URL='. $_SERVER['PHP_SELF'] .'" />';
|
|
break;
|
|
}
|
|
|
|
$submitPost = $sqldb->prepare("INSERT INTO `". $sql['table'] ."` (`title`, `name`, `trip`, `email`, `date`, `content`, `password`, `ip`, `op`, `tid`, `lastreply`) VALUES (:title, :name, :trip, :email, :date, :content, :password, :ipaddr, :op, :threadid, :lastrep)");
|
|
|
|
$submitPost->bindParam(':title', $submitData['title']);
|
|
$submitPost->bindParam(':name', $submitData['nameNT']);
|
|
$submitPost->bindParam(':trip', $submitData['trip']);
|
|
$submitPost->bindParam(':email', $submitData['email']);
|
|
$submitPost->bindParam(':date', $submitData['date']);
|
|
$submitPost->bindParam(':content', $submitData['content']);
|
|
$submitPost->bindParam(':password', $submitData['password']);
|
|
$submitPost->bindParam(':ipaddr', $submitData['ip']);
|
|
$submitPost->bindParam(':op', $submitData['op']);
|
|
$submitPost->bindParam(':threadid', $submitData['id']);
|
|
$submitPost->bindParam(':lastrep', $submitData['date']);
|
|
|
|
$submitPost->execute();
|
|
|
|
print '<h1>'. getLang('POSTED') .'</h1>';
|
|
|
|
print '<meta http-equiv="refresh" content="1; URL='. $_SERVER['PHP_SELF'] . ($submitData['noredir'] ? '?v=index' : '?v=thread&id='. $submitData['id']) .'" />';
|
|
break;
|
|
|
|
case 'del':
|
|
if($remoteAddrState >= .6) {
|
|
header('Location: /bot.html');
|
|
exit;
|
|
}
|
|
|
|
// Check if IP banned
|
|
if(checkBan($_SERVER['REMOTE_ADDR'])) {
|
|
print '<h2>'. getLang('USERBANNED') .'</h2>';
|
|
break;
|
|
}
|
|
|
|
// Assign array to variable so we can store things in it later
|
|
$deletionData = array();
|
|
|
|
// If we're in _POST mode begin deletion preparation
|
|
if(isset($_POST['id'])) {
|
|
|
|
// Prepare statement to get the post's data
|
|
$getPostData = $sqldb->prepare("SELECT * FROM `". $sql['table'] ." WHERE `id` = :pid LIMIT 1");
|
|
|
|
// Bind the ID
|
|
$getPostData->bindParam(':pid', $_POST['id'], PDO::PARAM_INT);
|
|
|
|
// Execute the statement
|
|
$getPostData->execute();
|
|
|
|
// Fetch the data
|
|
$getData = $getPostData->fetch();
|
|
|
|
// Error if the post couldn't be found
|
|
if(!count($getData))
|
|
error(getLang('NONEXISTENT') .'<meta http-equiv="refresh" content="0; url='. $_SERVER['PHP_SELF'] .'?v=index" />');
|
|
|
|
// Check if the post is locked
|
|
if($getData['locked']) {
|
|
|
|
print '<h2>'. getLang('LOCKEDMSGDEL') .'</h2>';
|
|
print '<meta http-equiv="refresh" content="2; URL='. $_SERVER['PHP_SELF'] .'?v=index" />';
|
|
|
|
} else {
|
|
|
|
// Match the password
|
|
if($getData['password'] == md5($_POST['password'])) {
|
|
|
|
// Use the moderator function to delete the post
|
|
nMod('del', $getData['id'], true);
|
|
print '<h2>'. getLang('DEL_SUCCEED') .'</h2>';
|
|
print '<meta http-equiv="refresh" content="2; URL='. $_SERVER['PHP_SELF'] .'?v=index" />';
|
|
|
|
} else {
|
|
|
|
// Else display an error message
|
|
print '<h2>'. getLang('DEL_FAILED') .'</h2>';
|
|
print '<meta http-equiv="refresh" content="2; URL='. $_SERVER['PHP_SELF'] .'?v=index" />';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Else enter _GET mode
|
|
} elseif(isset($_GET['id'])) {
|
|
|
|
// Check if the post exists
|
|
if(!is_numeric($_GET['id']))
|
|
error(getLang('NONEXISTENT') .'<meta http-equiv="refresh" content="0; url='. $_SERVER['PHP_SELF'] .'?v=index" />');
|
|
|
|
// Prepare statement to get post data
|
|
$getPostData = $sqldb->prepare("SELECT * FROM `". $sql['table'] ." WHERE `id` = :pid LIMIT 1");
|
|
|
|
// Bind ID
|
|
$getPostData->bindParam(':pid', $_GET['id'], PDO::PARAM_INT);
|
|
|
|
// Execute statement
|
|
$getPostData->execute();
|
|
|
|
// Fetch data
|
|
$getData = $getPostData->fetch();
|
|
|
|
// Error if the post couldn't be found
|
|
if(!count($getData))
|
|
error(getLang('NONEXISTENT') .'<meta http-equiv="refresh" content="0; url='. $_SERVER['PHP_SELF'] .'?v=index" />');
|
|
|
|
// Create form
|
|
print '<form method="post" action="'. $_SERVER['PHP_SELF'] .'?v=del">';
|
|
|
|
if($getData['locked']) {
|
|
print '<h2>'. getLang('LOCKEDMSGDEL') .'</h2>';
|
|
print '<meta http-equiv="refresh" content="2; URL='. $_SERVER['PHP_SELF'] .'?v=index" />';
|
|
} else {
|
|
print '<h2>'. getLang('DELPOST') .' '. $getData['id'] .'</h2>';
|
|
print '<input type="hidden" name="id" value="'. $_GET['id'] .'" />';
|
|
}
|
|
|
|
print '<table id="postForm" class="postForm">';
|
|
|
|
print '<tr><td>'. getLang('PASSWORD') .'</td>';
|
|
print '<td><input name="password" type="password" placeholder="'. getLang('PASSWORDCONTEXT') .'" value="'.@$_COOKIE[getConfig('cookiePrefix') .'pass'].'" /> ';
|
|
print '<input value="'. getLang('SUBMIT') .'" type="submit" /></td></tr>';
|
|
|
|
print '</table>';
|
|
print '</form>';
|
|
|
|
}
|
|
break;
|
|
|
|
case 'destroy':
|
|
header('Location: ./');
|
|
session_destroy();
|
|
exit;
|
|
|
|
// Default action
|
|
default:
|
|
header('Location: '. $_SERVER['PHP_SELF'] .'?v=index'); // If invalid option is set redirect to index
|
|
print '<meta http-equiv="refresh" content="0; url='. $_SERVER['PHP_SELF'] .'?v=index" />'; // Fallback because I've had experiences where header() didn't work properly
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
header('Location: '. $_SERVER['PHP_SELF'] .'?v=index'); // If invalid option is set redirect to index
|
|
print '<meta http-equiv="refresh" content="0; url='. $_SERVER['PHP_SELF'] .'?v=index" />'; // Fallback because I've had experiences where header() didn't work properly
|
|
exit;
|
|
|
|
}
|
|
|
|
// Print footer
|
|
print nFoot();
|