1
0
mirror of https://github.com/vichan-devel/vichan.git synced 2024-11-12 01:50:48 +01:00

Merge branch 'mod-rewrite'

Conflicts:
	inc/lib/Twig/Extensions/Extension/Tinyboard.php
	install.php
	mod.php
	stylesheets/style.css
	templates/index.html
	templates/page.html
	templates/thread.html
This commit is contained in:
Michael Save 2012-08-27 02:28:04 +10:00
commit 6a705fd8c2
59 changed files with 7314 additions and 3526 deletions

View File

@ -19,7 +19,7 @@ class AntiBot {
if ($uppercase) if ($uppercase)
$chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; $chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
if ($special_chars) if ($special_chars)
$chars .= ' ~!@#$%^&*()_+,./;\'[]\\{}|:"<>?=-` '; $chars .= ' ~!@#$%^&*()_+,./;\'[]\\{}|:<>?=-` ';
$chars = str_split($chars); $chars = str_split($chars);
@ -48,7 +48,8 @@ class AntiBot {
foreach ($chars as &$c) { foreach ($chars as &$c) {
if (rand(0, 2) != 0) if (rand(0, 2) != 0)
continue; $c = utf8tohtml($c);
else
$c = mb_encode_numericentity($c, array(0, 0xffff, 0, 0xffff), 'UTF-8'); $c = mb_encode_numericentity($c, array(0, 0xffff, 0, 0xffff), 'UTF-8');
} }

View File

@ -48,7 +48,7 @@ class Cache {
} }
// debug // debug
if ($data && $config['debug']) { if ($data !== false && $config['debug']) {
$debug['cached'][] = $key; $debug['cached'][] = $key;
} }

View File

@ -38,7 +38,6 @@
$config['check_updates_time'] = 43200; // 12 hours $config['check_updates_time'] = 43200; // 12 hours
// Shows some extra information at the bottom of pages. Good for debugging development. // Shows some extra information at the bottom of pages. Good for debugging development.
// Also experimental.
$config['debug'] = false; $config['debug'] = false;
// For development purposes. Turns 'display_errors' on. Not recommended for production. // For development purposes. Turns 'display_errors' on. Not recommended for production.
$config['verbose_errors'] = true; $config['verbose_errors'] = true;
@ -362,7 +361,7 @@
$config['markup'][] = array("/'''(.+?)'''/", "<strong>\$1</strong>"); $config['markup'][] = array("/'''(.+?)'''/", "<strong>\$1</strong>");
$config['markup'][] = array("/''(.+?)''/", "<em>\$1</em>"); $config['markup'][] = array("/''(.+?)''/", "<em>\$1</em>");
$config['markup'][] = array("/\*\*(.+?)\*\*/", "<span class=\"spoiler\">\$1</span>"); $config['markup'][] = array("/\*\*(.+?)\*\*/", "<span class=\"spoiler\">\$1</span>");
$config['markup'][] = array("/^\s*==(.+?)==\s*$/m", "<span class=\"heading\">\$1</span>"); $config['markup'][] = array("/^[ |\t]*==(.+?)==[ |\t]*$/m", "<span class=\"heading\">\$1</span>");
// Highlight PHP code wrapped in <code> tags (PHP 5.3.0+) // Highlight PHP code wrapped in <code> tags (PHP 5.3.0+)
// $config['markup'][] = array( // $config['markup'][] = array(
@ -816,8 +815,6 @@
// Do a DNS lookup on IP addresses to get their hostname on the IP summary page // Do a DNS lookup on IP addresses to get their hostname on the IP summary page
$config['mod']['dns_lookup'] = true; $config['mod']['dns_lookup'] = true;
// Show ban form on the IP summary page
$config['mod']['ip_banform'] = true;
// How many recent posts, per board, to show in the IP summary page // How many recent posts, per board, to show in the IP summary page
$config['mod']['ip_recentposts'] = 5; $config['mod']['ip_recentposts'] = 5;
@ -826,12 +823,17 @@
// How many actions to show per page in the moderation log // How many actions to show per page in the moderation log
$config['mod']['modlog_page'] = 350; $config['mod']['modlog_page'] = 350;
// How many bans to show per page in the ban list
$config['mod']['banlist_page'] = 350;
// Number of news entries to display per page
$config['mod']['news_page'] = 40;
// Maximum number of results to display for a search, per board // Maximum number of results to display for a search, per board
$config['mod']['search_results'] = 75; $config['mod']['search_results'] = 75;
// Maximum number of notices to display on the moderator noticeboard // How many entries to show per page in the moderator noticeboard
$config['mod']['noticeboard_display'] = 50; $config['mod']['noticeboard_page'] = 50;
// Number of entries to summarize and display on the dashboard // Number of entries to summarize and display on the dashboard
$config['mod']['noticeboard_dashboard'] = 5; $config['mod']['noticeboard_dashboard'] = 5;
@ -868,6 +870,19 @@
* ==================== * ====================
*/ */
// Capcode permissions
$config['mod']['capcode'] = array(
// JANITOR => array('Janitor'),
MOD => array('Mod'),
ADMIN => true
);
// Example: Allow mods to post with "## Moderator" as well
// $config['mod']['capcode'][MOD][] = 'Moderator';
// Example: Allow janitors to post with any capcode
// $config['mod']['capcode'][JANITOR] = true;
// Set any of the below to "DISABLED" to make them unavailable for everyone. // Set any of the below to "DISABLED" to make them unavailable for everyone.
// Don't worry about per-board moderators. Let all mods moderate any board. // Don't worry about per-board moderators. Let all mods moderate any board.
@ -1043,6 +1058,4 @@
// Complex regular expression to catch URLs // Complex regular expression to catch URLs
$config['url_regex'] = '/' . '(https?|ftp):\/\/' . '(([\w\-]+\.)+[a-zA-Z]{2,6}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' . '(:\d+)?' . '(\/([\w\-~.#\/?=&;:+%!*\[\]@$\'()+,|\^]+)?)?' . '/'; $config['url_regex'] = '/' . '(https?|ftp):\/\/' . '(([\w\-]+\.)+[a-zA-Z]{2,6}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' . '(:\d+)?' . '(\/([\w\-~.#\/?=&;:+%!*\[\]@$\'()+,|\^]+)?)?' . '/';
// INSANE regular expression for IPv6 addresses
$config['ipv6_regex'] = '((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?';

View File

@ -28,7 +28,7 @@ class Filter {
case 'name': case 'name':
return preg_match($match, $post['name']); return preg_match($match, $post['name']);
case 'trip': case 'trip':
return preg_match($match, $post['trip']); return $match === $post['trip'];
case 'email': case 'email':
return preg_match($match, $post['email']); return preg_match($match, $post['email']);
case 'subject': case 'subject':

View File

@ -32,7 +32,30 @@ function loadConfig() {
if (!isset($_SERVER['REMOTE_ADDR'])) if (!isset($_SERVER['REMOTE_ADDR']))
$_SERVER['REMOTE_ADDR'] = '0.0.0.0'; $_SERVER['REMOTE_ADDR'] = '0.0.0.0';
$arrays = array('db', 'cache', 'cookies', 'error', 'dir', 'mod', 'spam', 'flood_filters', 'wordfilters', 'custom_capcode', 'custom_tripcode', 'dnsbl', 'dnsbl_exceptions', 'remote', 'allowed_ext', 'allowed_ext_files', 'file_icons', 'footer', 'stylesheets', 'additional_javascript', 'markup'); $arrays = array(
'db',
'cache',
'cookies',
'error',
'dir',
'mod',
'spam',
'flood_filters',
'wordfilters',
'custom_capcode',
'custom_tripcode',
'dnsbl',
'dnsbl_exceptions',
'remote',
'allowed_ext',
'allowed_ext_files',
'file_icons',
'footer',
'stylesheets',
'additional_javascript',
'markup',
'custom_pages'
);
$config = array(); $config = array();
foreach ($arrays as $key) { foreach ($arrays as $key) {
@ -277,10 +300,13 @@ function setupBoard($array) {
$board = array( $board = array(
'uri' => $array['uri'], 'uri' => $array['uri'],
'name' => $array['title'], 'title' => $array['title'],
'title' => $array['subtitle'] 'subtitle' => $array['subtitle']
); );
// older versions
$board['name'] = &$board['title'];
$board['dir'] = sprintf($config['board_path'], $board['uri']); $board['dir'] = sprintf($config['board_path'], $board['uri']);
$board['url'] = sprintf($config['board_abbreviation'], $board['uri']); $board['url'] = sprintf($config['board_abbreviation'], $board['uri']);
@ -690,13 +716,13 @@ function post(array $post) {
$query->bindValue(':password', $post['password']); $query->bindValue(':password', $post['password']);
$query->bindValue(':ip', isset($post['ip']) ? $post['ip'] : $_SERVER['REMOTE_ADDR']); $query->bindValue(':ip', isset($post['ip']) ? $post['ip'] : $_SERVER['REMOTE_ADDR']);
if ($post['mod'] && $post['sticky']) { if ($post['op'] && $post['mod'] && $post['sticky']) {
$query->bindValue(':sticky', 1, PDO::PARAM_INT); $query->bindValue(':sticky', 1, PDO::PARAM_INT);
} else { } else {
$query->bindValue(':sticky', 0, PDO::PARAM_INT); $query->bindValue(':sticky', 0, PDO::PARAM_INT);
} }
if ($post['mod'] && $post['locked']) { if ($post['op'] && $post['mod'] && $post['locked']) {
$query->bindValue(':locked', 1, PDO::PARAM_INT); $query->bindValue(':locked', 1, PDO::PARAM_INT);
} else { } else {
$query->bindValue(':locked', 0, PDO::PARAM_INT); $query->bindValue(':locked', 0, PDO::PARAM_INT);
@ -777,12 +803,8 @@ function deleteFile($id, $remove_entirely_if_already=true) {
$query = prepare(sprintf("SELECT `thread`,`thumb`,`file` FROM `posts_%s` WHERE `id` = :id LIMIT 1", $board['uri'])); $query = prepare(sprintf("SELECT `thread`,`thumb`,`file` FROM `posts_%s` WHERE `id` = :id LIMIT 1", $board['uri']));
$query->bindValue(':id', $id, PDO::PARAM_INT); $query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query)); $query->execute() or error(db_error($query));
if (!$post = $query->fetch())
if ($query->rowCount() < 1) {
error($config['error']['invalidpost']); error($config['error']['invalidpost']);
}
$post = $query->fetch();
if ($post['file'] == 'deleted' && !$post['thread']) if ($post['file'] == 'deleted' && !$post['thread'])
return; // Can't delete OP's image completely. return; // Can't delete OP's image completely.
@ -801,13 +823,14 @@ function deleteFile($id, $remove_entirely_if_already=true) {
// Set file to 'deleted' // Set file to 'deleted'
$query->bindValue(':file', 'deleted', PDO::PARAM_INT); $query->bindValue(':file', 'deleted', PDO::PARAM_INT);
} }
// Update database
$query->bindValue(':id', $id, PDO::PARAM_INT); $query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query)); $query->execute() or error(db_error($query));
if ($post['thread']) if ($post['thread'])
buildThread($post['thread']); buildThread($post['thread']);
else
buildThread($id);
} }
// rebuild post (markup) // rebuild post (markup)
@ -1179,6 +1202,7 @@ function buildIndex() {
global $board, $config; global $board, $config;
$pages = getPages(); $pages = getPages();
$antibot = create_antibot($board['uri']);
$page = 1; $page = 1;
while ($page <= $config['max_pages'] && $content = index($page)) { while ($page <= $config['max_pages'] && $content = index($page)) {
@ -1188,7 +1212,7 @@ function buildIndex() {
$content['pages'] = $pages; $content['pages'] = $pages;
$content['pages'][$page-1]['selected'] = true; $content['pages'][$page-1]['selected'] = true;
$content['btn'] = getPageButtons($content['pages']); $content['btn'] = getPageButtons($content['pages']);
$content['antibot'] = create_antibot($board['uri']); $content['antibot'] = $antibot;
file_write($filename, Element('index.html', $content)); file_write($filename, Element('index.html', $content));
if (isset($md5) && $md5 == md5_file($filename)) { if (isset($md5) && $md5 == md5_file($filename)) {
@ -1370,10 +1394,12 @@ function markup(&$body, $track_cites = false) {
if ($config['auto_unicode']) { if ($config['auto_unicode']) {
$body = unicodify($body); $body = unicodify($body);
if ($config['markup_urls']) {
foreach ($markup_urls as &$url) { foreach ($markup_urls as &$url) {
$body = str_replace(unicodify($url), $url, $body); $body = str_replace(unicodify($url), $url, $body);
} }
} }
}
// replace tabs with 8 spaces // replace tabs with 8 spaces
$body = str_replace("\t", ' ', $body); $body = str_replace("\t", ' ', $body);

View File

@ -10,19 +10,22 @@ class Twig_Extensions_Extension_Tinyboard extends Twig_Extension
public function getFilters() public function getFilters()
{ {
return Array( return Array(
'filesize' => new Twig_Filter_Function('format_bytes', Array('needs_environment' => false)), 'filesize' => new Twig_Filter_Function('format_bytes'),
'truncate' => new Twig_Filter_Function('twig_truncate_filter', array('needs_environment' => false)), 'truncate' => new Twig_Filter_Function('twig_truncate_filter'),
'truncate_body' => new Twig_Filter_Function('truncate', array('needs_environment' => false)), 'truncate_body' => new Twig_Filter_Function('truncate'),
'extension' => new Twig_Filter_Function('twig_extension_filter', array('needs_environment' => false)), 'extension' => new Twig_Filter_Function('twig_extension_filter'),
'sprintf' => new Twig_Filter_Function('sprintf', array('needs_environment' => false)), 'sprintf' => new Twig_Filter_Function('sprintf'),
'capcode' => new Twig_Filter_Function('capcode', array('needs_environment' => false)), 'capcode' => new Twig_Filter_Function('capcode'),
'hasPermission' => new Twig_Filter_Function('twig_hasPermission_filter', array('needs_environment' => false)), 'hasPermission' => new Twig_Filter_Function('twig_hasPermission_filter'),
'date' => new Twig_Filter_Function('twig_date_filter', array('needs_environment' => false)), 'date' => new Twig_Filter_Function('twig_date_filter'),
'poster_id' => new Twig_Filter_Function('poster_id', array('needs_environment' => false)), 'poster_id' => new Twig_Filter_Function('poster_id'),
'remove_whitespace' => new Twig_Filter_Function('twig_remove_whitespace_filter', array('needs_environment' => false)), 'remove_whitespace' => new Twig_Filter_Function('twig_remove_whitespace_filter'),
'count' => new Twig_Filter_Function('count', array('needs_environment' => false)), 'count' => new Twig_Filter_Function('count'),
'until' => new Twig_Filter_Function('until', array('needs_environment' => false)), 'ago' => new Twig_Filter_Function('ago'),
'addslashes' => new Twig_Filter_Function('addslashes', array('needs_environment' => false)), 'until' => new Twig_Filter_Function('until'),
'split' => new Twig_Filter_Function('twig_split_filter'),
'push' => new Twig_Filter_Function('twig_push_filter'),
'addslashes' => new Twig_Filter_Function('addslashes')
); );
} }
@ -34,10 +37,11 @@ class Twig_Extensions_Extension_Tinyboard extends Twig_Extension
public function getFunctions() public function getFunctions()
{ {
return Array( return Array(
'time' => new Twig_Filter_Function('time', array('needs_environment' => false)), 'time' => new Twig_Filter_Function('time'),
'timezone' => new Twig_Filter_Function('twig_timezone_function', array('needs_environment' => false)), 'floor' => new Twig_Filter_Function('floor'),
'hiddenInputs' => new Twig_Filter_Function('hiddenInputs', array('needs_environment' => false)), 'timezone' => new Twig_Filter_Function('twig_timezone_function'),
'hiddenInputsHash' => new Twig_Filter_Function('hiddenInputsHash', array('needs_environment' => false)) 'hiddenInputs' => new Twig_Filter_Function('hiddenInputs'),
'hiddenInputsHash' => new Twig_Filter_Function('hiddenInputsHash'),
); );
} }
@ -57,6 +61,15 @@ function twig_timezone_function() {
return sprintf("%s%02d", ($hr = (int)floor(($tz = date('Z')) / 3600)) > 0 ? '+' : '-', abs($hr)) . ':' . sprintf("%02d", (($tz / 3600) - $hr) * 60); return sprintf("%s%02d", ($hr = (int)floor(($tz = date('Z')) / 3600)) > 0 ? '+' : '-', abs($hr)) . ':' . sprintf("%02d", (($tz / 3600) - $hr) * 60);
} }
function twig_split_filter($str, $delim) {
return explode($delim, $str);
}
function twig_push_filter($array, $value) {
array_push($array, $value);
return $array;
}
function twig_remove_whitespace_filter($data) { function twig_remove_whitespace_filter($data) {
return preg_replace('/[\t\r\n]/', '', $data); return preg_replace('/[\t\r\n]/', '', $data);
} }
@ -65,7 +78,7 @@ function twig_date_filter($date, $format) {
return strftime($format, $date); return strftime($format, $date);
} }
function twig_hasPermission_filter($mod, $permission, $board) { function twig_hasPermission_filter($mod, $permission, $board = null) {
return hasPermission($permission, $board, $mod); return hasPermission($permission, $board, $mod);
} }

287
inc/mod-old.php Normal file
View File

@ -0,0 +1,287 @@
<?php
/*
* Copyright (c) 2010-2012 Tinyboard Development Group
*/
if (realpath($_SERVER['SCRIPT_FILENAME']) == str_replace('\\', '/', __FILE__)) {
// You cannot request this file directly.
exit;
}
// create a hash/salt pair for validate logins
function mkhash($username, $password, $salt = false) {
global $config;
if (!$salt) {
// create some sort of salt for the hash
$salt = substr(base64_encode(sha1(rand() . time(), true) . $config['cookies']['salt']), 0, 15);
$generated_salt = true;
}
// generate hash (method is not important as long as it's strong)
$hash = substr(base64_encode(md5($username . sha1($username . $password . $salt . ($config['mod']['lock_ip'] ? $_SERVER['REMOTE_ADDR'] : ''), true), true)), 0, 20);
if (isset($generated_salt))
return Array($hash, $salt);
else
return $hash;
}
function login($username, $password, $makehash=true) {
global $mod;
// SHA1 password
if ($makehash) {
$password = sha1($password);
}
$query = prepare("SELECT `id`,`type`,`boards` FROM `mods` WHERE `username` = :username AND `password` = :password LIMIT 1");
$query->bindValue(':username', $username);
$query->bindValue(':password', $password);
$query->execute() or error(db_error($query));
if ($user = $query->fetch()) {
return $mod = Array(
'id' => $user['id'],
'type' => $user['type'],
'username' => $username,
'hash' => mkhash($username, $password),
'boards' => explode(',', $user['boards'])
);
} else return false;
}
function setCookies() {
global $mod, $config;
if (!$mod)
error('setCookies() was called for a non-moderator!');
setcookie($config['cookies']['mod'],
$mod['username'] . // username
':' .
$mod['hash'][0] . // password
':' .
$mod['hash'][1], // salt
time() + $config['cookies']['expire'], $config['cookies']['jail'] ? $config['cookies']['path'] : '/', null, false, true);
}
function destroyCookies() {
global $config;
// Delete the cookies
setcookie($config['cookies']['mod'], 'deleted', time() - $config['cookies']['expire'], $config['cookies']['jail']?$config['cookies']['path'] : '/', null, false, true);
}
function create_pm_header() {
global $mod;
$query = prepare("SELECT `id` FROM `pms` WHERE `to` = :id AND `unread` = 1");
$query->bindValue(':id', $mod['id'], PDO::PARAM_INT);
$query->execute() or error(db_error($query));
if ($pm = $query->fetch()) {
return Array('id' => $pm['id'], 'waiting' => $query->rowCount() - 1);
}
return false;
}
function modLog($action, $_board=null) {
global $mod, $board, $config;
$query = prepare("INSERT INTO `modlogs` VALUES (:id, :ip, :board, :time, :text)");
$query->bindValue(':id', $mod['id'], PDO::PARAM_INT);
$query->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
$query->bindValue(':time', time(), PDO::PARAM_INT);
$query->bindValue(':text', $action);
if (isset($_board))
$query->bindValue(':board', $_board);
elseif (isset($board))
$query->bindValue(':board', $board['uri']);
else
$query->bindValue(':board', null, PDO::PARAM_NULL);
$query->execute() or error(db_error($query));
if ($config['syslog'])
_syslog(LOG_INFO, '[mod/' . $mod['username'] . ']: ' . $action);
}
// Generates a <ul> element with a list of linked
// boards and their subtitles. (without the <ul> opening and ending tags)
function ulBoards() {
global $mod, $config;
$body = '';
// List of boards
$boards = listBoards();
foreach ($boards as &$b) {
$body .= '<li>' .
'<a href="?/' .
sprintf($config['board_path'], $b['uri']) . $config['file_index'] .
'">' .
sprintf($config['board_abbreviation'], $b['uri']) .
'</a> - ' .
$b['title'] .
(isset($b['subtitle']) ? '<span class="unimportant"> — ' . $b['subtitle'] . '</span>' : '') .
($mod['type'] >= $config['mod']['manageboards'] ?
' <a href="?/' . $b['uri'] . '/edit" class="unimportant">[manage]</a>' : '') .
'</li>';
}
if ($mod['type'] >= $config['mod']['newboard']) {
$body .= '<li style="margin-top:15px;"><a href="?/new"><strong>' . _('Create new board') . '</strong></a></li>';
}
return $body;
}
function form_newBan($ip=null, $reason='', $continue=false, $delete=false, $board=false, $allow_public = false) {
global $config, $mod;
$boards = listBoards();
$__boards = '<li><input type="radio" checked="checked" name="board" id="board_*" value=""/> <label style="display:inline" for="board_*"><em>' . _('all boards') . '</em></label></li>';
foreach ($boards as &$_board) {
$__boards .= '<li>' .
'<input type="radio" name="board" id="board_' . $_board['uri'] . '" value="' . $_board['uri'] . '">' .
'<label style="display:inline" for="board_' . $_board['uri'] . '"> ' .
($_board['uri'] == '*' ?
'<em>"*"</em>'
:
sprintf($config['board_abbreviation'], $_board['uri'])
) .
' - ' . $_board['title'] .
'</label>' .
'</li>';
}
return '<fieldset><legend>New ban</legend>' .
'<form action="?/ban" method="post">' .
($continue ? '<input type="hidden" name="continue" value="' . htmlentities($continue) . '" />' : '') .
($delete || $allow_public ? '<input type="hidden" name="' . (!$allow_public ? 'delete' : 'post') . '" value="' . htmlentities($delete) . '" />' : '') .
($board ? '<input type="hidden" name="board" value="' . htmlentities($board) . '" />' : '') .
'<table>' .
'<tr>' .
'<th><label for="ip">IP ' .
($config['ban_cidr'] ? '<span class="unimportant">(or subnet)' : '') .
'</span></label></th>' .
'<td><input type="text" name="ip" id="ip" size="30" maxlength="30" ' .
(isset($ip) ?
'value="' . htmlentities($ip) . '" ' : ''
) .
'/></td>' .
'</tr>' .
'<tr>' .
'<th><label for="reason">Reason</label></th>' .
'<td><textarea name="reason" id="reason" rows="5" cols="30">' .
htmlentities($reason) .
'</textarea></td>' .
'</tr>' .
($mod['type'] >= $config['mod']['public_ban'] && $allow_public ?
'<tr>' .
'<th><label for="message">Message</label></th>' .
'<td><input type="checkbox" id="public_message" name="public_message"/>' .
' <input type="text" name="message" id="message" size="35" maxlength="200" value="' . htmlentities($config['mod']['default_ban_message']) . '" />' .
' <span class="unimportant">(public; attached to post)</span></td>' .
'<script type="text/javascript">' .
'document.getElementById(\'message\').disabled = true;' .
'document.getElementById(\'public_message\').onchange = function() {' .
'document.getElementById(\'message\').disabled = !this.checked;' .
'}' .
'</script>' .
'</tr>'
: '') .
'<tr>' .
'<th><label for="length">Length</label></th>' .
'<td><input type="text" name="length" id="length" size="20" maxlength="40" />' .
' <span class="unimportant">(eg. "2d1h30m" or "2 days")</span></td>' .
'</tr>' .
'<tr>' .
'<th>Board</th>' .
'<td><ul style="list-style:none;padding:2px 5px">' . $__boards . '</tl></td>' .
'</tr>' .
'<tr>' .
'<td></td>' .
'<td><input name="new_ban" type="submit" value="New Ban" /></td>' .
'</tr>' .
'</table>' .
'</form>' .
'</fieldset>';
}
function form_newBoard() {
return '<fieldset><legend>New board</legend>' .
'<form action="?/new" method="post">' .
'<table>' .
'<tr>' .
'<th><label for="board">URI</label></th>' .
'<td><input type="text" name="uri" id="board" size="10" />' .
' <span class="unimportant">(eg. "b"; "mu")</span></td>' .
'</tr>' .
'<tr>' .
'<th><label for="title">Title</label></th>' .
'<td><input type="text" name="title" id="title" size="25" />' .
' <span class="unimportant">(eg. "Random")</span></td>' .
'</tr>' .
'<tr>' .
'<th><label for="subtitle">Subtitle</label></th>' .
'<td><input type="text" name="subtitle" id="subtitle" size="25" />' .
' <span class="unimportant">(optional)</span></td>' .
'</tr>' .
'<tr>' .
'<td></td>' .
'<td><input name="new_board" type="submit" value="New Board" /></td>' .
'</tr>' .
'</table>' .
'</form>' .
'</fieldset>';
}
function removeBan($id) {
global $config, $memcached;
$query = prepare("DELETE FROM `bans` WHERE `id` = :id");
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
//if ($config['memcached']['enabled']) {
// Remove cached ban
// TODO
// $memcached->delete("ban_{$id}");
//}
}
// Validate session
if (isset($_COOKIE[$config['cookies']['mod']])) {
// Should be username:hash:salt
$cookie = explode(':', $_COOKIE[$config['cookies']['mod']]);
if (count($cookie) != 3) {
destroyCookies();
error($config['error']['malformed']);
}
$query = prepare("SELECT `id`, `type`, `boards`, `password` FROM `mods` WHERE `username` = :username LIMIT 1");
$query->bindValue(':username', $cookie[0]);
$query->execute() or error(db_error($query));
$user = $query->fetch();
// validate password hash
if ($cookie[1] != mkhash($cookie[0], $user['password'], $cookie[2])) {
// Malformed cookies
destroyCookies();
error($config['error']['malformed']);
}
$mod = Array(
'id' => $user['id'],
'type' => $user['type'],
'username' => $cookie[0],
'boards' => explode(',', $user['boards'])
);
}

View File

@ -4,284 +4,12 @@
* Copyright (c) 2010-2012 Tinyboard Development Group * Copyright (c) 2010-2012 Tinyboard Development Group
*/ */
// WARNING: Including this file is DEPRECIATED. It's only here to support older versions and won't exist forever.
if (realpath($_SERVER['SCRIPT_FILENAME']) == str_replace('\\', '/', __FILE__)) { if (realpath($_SERVER['SCRIPT_FILENAME']) == str_replace('\\', '/', __FILE__)) {
// You cannot request this file directly. // You cannot request this file directly.
exit; exit;
} }
// create a hash/salt pair for validate logins require 'inc/mod/auth.php';
function mkhash($username, $password, $salt = false) {
global $config;
if (!$salt) {
// create some sort of salt for the hash
$salt = substr(base64_encode(sha1(rand() . time(), true) . $config['cookies']['salt']), 0, 15);
$generated_salt = true;
}
// generate hash (method is not important as long as it's strong)
$hash = substr(base64_encode(md5($username . sha1($username . $password . $salt . ($config['mod']['lock_ip'] ? $_SERVER['REMOTE_ADDR'] : ''), true), true)), 0, 20);
if (isset($generated_salt))
return Array($hash, $salt);
else
return $hash;
}
function login($username, $password, $makehash=true) {
global $mod;
// SHA1 password
if ($makehash) {
$password = sha1($password);
}
$query = prepare("SELECT `id`,`type`,`boards` FROM `mods` WHERE `username` = :username AND `password` = :password LIMIT 1");
$query->bindValue(':username', $username);
$query->bindValue(':password', $password);
$query->execute() or error(db_error($query));
if ($user = $query->fetch()) {
return $mod = Array(
'id' => $user['id'],
'type' => $user['type'],
'username' => $username,
'hash' => mkhash($username, $password),
'boards' => explode(',', $user['boards'])
);
} else return false;
}
function setCookies() {
global $mod, $config;
if (!$mod)
error('setCookies() was called for a non-moderator!');
setcookie($config['cookies']['mod'],
$mod['username'] . // username
':' .
$mod['hash'][0] . // password
':' .
$mod['hash'][1], // salt
time() + $config['cookies']['expire'], $config['cookies']['jail'] ? $config['cookies']['path'] : '/', null, false, true);
}
function destroyCookies() {
global $config;
// Delete the cookies
setcookie($config['cookies']['mod'], 'deleted', time() - $config['cookies']['expire'], $config['cookies']['jail']?$config['cookies']['path'] : '/', null, false, true);
}
function create_pm_header() {
global $mod;
$query = prepare("SELECT `id` FROM `pms` WHERE `to` = :id AND `unread` = 1");
$query->bindValue(':id', $mod['id'], PDO::PARAM_INT);
$query->execute() or error(db_error($query));
if ($pm = $query->fetch()) {
return Array('id' => $pm['id'], 'waiting' => $query->rowCount() - 1);
}
return false;
}
function modLog($action, $_board=null) {
global $mod, $board, $config;
$query = prepare("INSERT INTO `modlogs` VALUES (:id, :ip, :board, :time, :text)");
$query->bindValue(':id', $mod['id'], PDO::PARAM_INT);
$query->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
$query->bindValue(':time', time(), PDO::PARAM_INT);
$query->bindValue(':text', $action);
if (isset($_board))
$query->bindValue(':board', $_board);
elseif (isset($board))
$query->bindValue(':board', $board['uri']);
else
$query->bindValue(':board', null, PDO::PARAM_NULL);
$query->execute() or error(db_error($query));
if ($config['syslog'])
_syslog(LOG_INFO, '[mod/' . $mod['username'] . ']: ' . $action);
}
// Generates a <ul> element with a list of linked
// boards and their subtitles. (without the <ul> opening and ending tags)
function ulBoards() {
global $mod, $config;
$body = '';
// List of boards
$boards = listBoards();
foreach ($boards as &$b) {
$body .= '<li>' .
'<a href="?/' .
sprintf($config['board_path'], $b['uri']) . $config['file_index'] .
'">' .
sprintf($config['board_abbreviation'], $b['uri']) .
'</a> - ' .
$b['title'] .
(isset($b['subtitle']) ? '<span class="unimportant"> — ' . $b['subtitle'] . '</span>' : '') .
($mod['type'] >= $config['mod']['manageboards'] ?
' <a href="?/' . $b['uri'] . '/edit" class="unimportant">[manage]</a>' : '') .
'</li>';
}
if ($mod['type'] >= $config['mod']['newboard']) {
$body .= '<li style="margin-top:15px;"><a href="?/new"><strong>' . _('Create new board') . '</strong></a></li>';
}
return $body;
}
function form_newBan($ip=null, $reason='', $continue=false, $delete=false, $board=false, $allow_public = false) {
global $config, $mod;
$boards = listBoards();
$__boards = '<li><input type="radio" checked="checked" name="board" id="board_*" value=""/> <label style="display:inline" for="board_*"><em>' . _('all boards') . '</em></label></li>';
foreach ($boards as &$_board) {
$__boards .= '<li>' .
'<input type="radio" name="board" id="board_' . $_board['uri'] . '" value="' . $_board['uri'] . '">' .
'<label style="display:inline" for="board_' . $_board['uri'] . '"> ' .
($_board['uri'] == '*' ?
'<em>"*"</em>'
:
sprintf($config['board_abbreviation'], $_board['uri'])
) .
' - ' . $_board['title'] .
'</label>' .
'</li>';
}
return '<fieldset><legend>New ban</legend>' .
'<form action="?/ban" method="post">' .
($continue ? '<input type="hidden" name="continue" value="' . htmlentities($continue) . '" />' : '') .
($delete || $allow_public ? '<input type="hidden" name="' . (!$allow_public ? 'delete' : 'post') . '" value="' . htmlentities($delete) . '" />' : '') .
($board ? '<input type="hidden" name="board" value="' . htmlentities($board) . '" />' : '') .
'<table>' .
'<tr>' .
'<th><label for="ip">IP ' .
($config['ban_cidr'] ? '<span class="unimportant">(or subnet)' : '') .
'</span></label></th>' .
'<td><input type="text" name="ip" id="ip" size="30" maxlength="30" ' .
(isset($ip) ?
'value="' . htmlentities($ip) . '" ' : ''
) .
'/></td>' .
'</tr>' .
'<tr>' .
'<th><label for="reason">Reason</label></th>' .
'<td><textarea name="reason" id="reason" rows="5" cols="30">' .
htmlentities($reason) .
'</textarea></td>' .
'</tr>' .
($mod['type'] >= $config['mod']['public_ban'] && $allow_public ?
'<tr>' .
'<th><label for="message">Message</label></th>' .
'<td><input type="checkbox" id="public_message" name="public_message"/>' .
' <input type="text" name="message" id="message" size="35" maxlength="200" value="' . htmlentities($config['mod']['default_ban_message']) . '" />' .
' <span class="unimportant">(public; attached to post)</span></td>' .
'<script type="text/javascript">' .
'document.getElementById(\'message\').disabled = true;' .
'document.getElementById(\'public_message\').onchange = function() {' .
'document.getElementById(\'message\').disabled = !this.checked;' .
'}' .
'</script>' .
'</tr>'
: '') .
'<tr>' .
'<th><label for="length">Length</label></th>' .
'<td><input type="text" name="length" id="length" size="20" maxlength="40" />' .
' <span class="unimportant">(eg. "2d1h30m" or "2 days")</span></td>' .
'</tr>' .
'<tr>' .
'<th>Board</th>' .
'<td><ul style="list-style:none;padding:2px 5px">' . $__boards . '</tl></td>' .
'</tr>' .
'<tr>' .
'<td></td>' .
'<td><input name="new_ban" type="submit" value="New Ban" /></td>' .
'</tr>' .
'</table>' .
'</form>' .
'</fieldset>';
}
function form_newBoard() {
return '<fieldset><legend>New board</legend>' .
'<form action="?/new" method="post">' .
'<table>' .
'<tr>' .
'<th><label for="board">URI</label></th>' .
'<td><input type="text" name="uri" id="board" size="10" />' .
' <span class="unimportant">(eg. "b"; "mu")</span></td>' .
'</tr>' .
'<tr>' .
'<th><label for="title">Title</label></th>' .
'<td><input type="text" name="title" id="title" size="25" />' .
' <span class="unimportant">(eg. "Random")</span></td>' .
'</tr>' .
'<tr>' .
'<th><label for="subtitle">Subtitle</label></th>' .
'<td><input type="text" name="subtitle" id="subtitle" size="25" />' .
' <span class="unimportant">(optional)</span></td>' .
'</tr>' .
'<tr>' .
'<td></td>' .
'<td><input name="new_board" type="submit" value="New Board" /></td>' .
'</tr>' .
'</table>' .
'</form>' .
'</fieldset>';
}
function removeBan($id) {
global $config, $memcached;
$query = prepare("DELETE FROM `bans` WHERE `id` = :id");
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
//if ($config['memcached']['enabled']) {
// Remove cached ban
// TODO
// $memcached->delete("ban_{$id}");
//}
}
// Validate session
if (isset($_COOKIE[$config['cookies']['mod']])) {
// Should be username:hash:salt
$cookie = explode(':', $_COOKIE[$config['cookies']['mod']]);
if (count($cookie) != 3) {
destroyCookies();
error($config['error']['malformed']);
}
$query = prepare("SELECT `id`, `type`, `boards`, `password` FROM `mods` WHERE `username` = :username LIMIT 1");
$query->bindValue(':username', $cookie[0]);
$query->execute() or error(db_error($query));
$user = $query->fetch();
// validate password hash
if ($cookie[1] != mkhash($cookie[0], $user['password'], $cookie[2])) {
// Malformed cookies
destroyCookies();
error($config['error']['malformed']);
}
$mod = Array(
'id' => $user['id'],
'type' => $user['type'],
'username' => $cookie[0],
'boards' => explode(',', $user['boards'])
);
}

152
inc/mod/auth.php Normal file
View File

@ -0,0 +1,152 @@
<?php
/*
* Copyright (c) 2010-2012 Tinyboard Development Group
*/
if (realpath($_SERVER['SCRIPT_FILENAME']) == str_replace('\\', '/', __FILE__)) {
// You cannot request this file directly.
exit;
}
// create a hash/salt pair for validate logins
function mkhash($username, $password, $salt = false) {
global $config;
if (!$salt) {
// create some sort of salt for the hash
$salt = substr(base64_encode(sha1(rand() . time(), true) . $config['cookies']['salt']), 0, 15);
$generated_salt = true;
}
// generate hash (method is not important as long as it's strong)
$hash = substr(base64_encode(md5($username . sha1($username . $password . $salt . ($config['mod']['lock_ip'] ? $_SERVER['REMOTE_ADDR'] : ''), true), true)), 0, 20);
if (isset($generated_salt))
return Array($hash, $salt);
else
return $hash;
}
function login($username, $password, $makehash=true) {
global $mod;
// SHA1 password
if ($makehash) {
$password = sha1($password);
}
$query = prepare("SELECT `id`,`type`,`boards` FROM `mods` WHERE `username` = :username AND `password` = :password LIMIT 1");
$query->bindValue(':username', $username);
$query->bindValue(':password', $password);
$query->execute() or error(db_error($query));
if ($user = $query->fetch()) {
return $mod = Array(
'id' => $user['id'],
'type' => $user['type'],
'username' => $username,
'hash' => mkhash($username, $password),
'boards' => explode(',', $user['boards'])
);
} else return false;
}
function setCookies() {
global $mod, $config;
if (!$mod)
error('setCookies() was called for a non-moderator!');
setcookie($config['cookies']['mod'],
$mod['username'] . // username
':' .
$mod['hash'][0] . // password
':' .
$mod['hash'][1], // salt
time() + $config['cookies']['expire'], $config['cookies']['jail'] ? $config['cookies']['path'] : '/', null, false, true);
}
function destroyCookies() {
global $config;
// Delete the cookies
setcookie($config['cookies']['mod'], 'deleted', time() - $config['cookies']['expire'], $config['cookies']['jail']?$config['cookies']['path'] : '/', null, false, true);
}
function modLog($action, $_board=null) {
global $mod, $board, $config;
$query = prepare("INSERT INTO `modlogs` VALUES (:id, :ip, :board, :time, :text)");
$query->bindValue(':id', $mod['id'], PDO::PARAM_INT);
$query->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
$query->bindValue(':time', time(), PDO::PARAM_INT);
$query->bindValue(':text', $action);
if (isset($_board))
$query->bindValue(':board', $_board);
elseif (isset($board))
$query->bindValue(':board', $board['uri']);
else
$query->bindValue(':board', null, PDO::PARAM_NULL);
$query->execute() or error(db_error($query));
if ($config['syslog'])
_syslog(LOG_INFO, '[mod/' . $mod['username'] . ']: ' . $action);
}
// Validate session
if (isset($_COOKIE[$config['cookies']['mod']])) {
// Should be username:hash:salt
$cookie = explode(':', $_COOKIE[$config['cookies']['mod']]);
if (count($cookie) != 3) {
destroyCookies();
error($config['error']['malformed']);
}
$query = prepare("SELECT `id`, `type`, `boards`, `password` FROM `mods` WHERE `username` = :username LIMIT 1");
$query->bindValue(':username', $cookie[0]);
$query->execute() or error(db_error($query));
$user = $query->fetch();
// validate password hash
if ($cookie[1] != mkhash($cookie[0], $user['password'], $cookie[2])) {
// Malformed cookies
destroyCookies();
error($config['error']['malformed']);
}
$mod = Array(
'id' => $user['id'],
'type' => $user['type'],
'username' => $cookie[0],
'boards' => explode(',', $user['boards'])
);
}
function create_pm_header() {
global $mod, $config;
if ($config['cache']['enabled'] && ($header = cache::get('pm_unread_' . $mod['id'])) !== false) {
if ($header === true)
return false;
return $header;
}
$query = prepare("SELECT `id` FROM `pms` WHERE `to` = :id AND `unread` = 1");
$query->bindValue(':id', $mod['id'], PDO::PARAM_INT);
$query->execute() or error(db_error($query));
if ($pm = $query->fetch())
$header = Array('id' => $pm['id'], 'waiting' => $query->rowCount() - 1);
else
$header = true;
if ($config['cache']['enabled'])
cache::set('pm_unread_' . $mod['id'], $header);
if ($header === true)
return false;
return $header;
}

95
inc/mod/ban.php Normal file
View File

@ -0,0 +1,95 @@
<?php
/*
* Copyright (c) 2010-2012 Tinyboard Development Group
*/
if (realpath($_SERVER['SCRIPT_FILENAME']) == str_replace('\\', '/', __FILE__)) {
// You cannot request this file directly.
exit;
}
function parse_time($str) {
if (empty($str))
return false;
if (($time = @strtotime($str)) !== false)
return $time;
if (!preg_match('/^((\d+)\s?ye?a?r?s?)?\s?+((\d+)\s?mon?t?h?s?)?\s?+((\d+)\s?we?e?k?s?)?\s?+((\d+)\s?da?y?s?)?((\d+)\s?ho?u?r?s?)?\s?+((\d+)\s?mi?n?u?t?e?s?)?\s?+((\d+)\s?se?c?o?n?d?s?)?$/', $str, $matches))
return false;
$expire = 0;
if (isset($matches[2])) {
// Years
$expire += $matches[2]*60*60*24*365;
}
if (isset($matches[4])) {
// Months
$expire += $matches[4]*60*60*24*30;
}
if (isset($matches[6])) {
// Weeks
$expire += $matches[6]*60*60*24*7;
}
if (isset($matches[8])) {
// Days
$expire += $matches[8]*60*60*24;
}
if (isset($matches[10])) {
// Hours
$expire += $matches[10]*60*60;
}
if (isset($matches[12])) {
// Minutes
$expire += $matches[12]*60;
}
if (isset($matches[14])) {
// Seconds
$expire += $matches[14];
}
return time() + $expire;
}
function ban($mask, $reason, $length, $board) {
global $mod, $pdo;
$query = prepare("INSERT INTO `bans` VALUES (NULL, :ip, :mod, :time, :expires, :reason, :board)");
$query->bindValue(':ip', $mask);
$query->bindValue(':mod', $mod['id']);
$query->bindValue(':time', time());
if ($reason !== '') {
markup($reason);
$query->bindValue(':reason', $reason);
} else
$query->bindValue(':reason', null, PDO::PARAM_NULL);
if ($length > 0)
$query->bindValue(':expires', $length);
else
$query->bindValue(':expires', null, PDO::PARAM_NULL);
if ($board)
$query->bindValue(':board', $board);
else
$query->bindValue(':board', null, PDO::PARAM_NULL);
$query->execute() or error(db_error($query));
modLog('Created a new ' .
($length > 0 ? preg_replace('/^(\d+) (\w+?)s?$/', '$1-$2', until($length)) : 'permanent') .
' ban (<small>#' . $pdo->lastInsertId() . '</small>) for ' .
(filter_var($mask, FILTER_VALIDATE_IP) !== false ? "<a href=\"?/IP/$mask\">$mask</a>" : utf8tohtml($mask)) .
' with ' . ($reason ? 'reason: ' . utf8tohtml($reason) . '' : 'no reason'));
}
function unban($id) {
$query = prepare("DELETE FROM `bans` WHERE `id` = :id");
$query->bindValue(':id', $id);
$query->execute() or error(db_error($query));
modLog("Removed ban #{$id}");
}

75
inc/mod/config-editor.php Normal file
View File

@ -0,0 +1,75 @@
<?php
function config_vars() {
global $config;
$config_file = file('inc/config.php', FILE_IGNORE_NEW_LINES);
$conf = array();
$var = array(
'name' => false,
'comment' => array(),
'default' => false,
'default_temp' => false
);
$temp_comment = false;
foreach ($config_file as $line) {
if ($temp_comment) {
$var['comment'][] = $temp_comment;
$temp_comment = false;
}
if (preg_match('!^\s*// (.*)$!', $line, $matches)) {
if ($var['default'] !== false) {
$line = '';
$temp_comment = $matches[1];
} else {
$var['comment'][] = $matches[1];
}
} else if ($var['default_temp'] !== false) {
$var['default_temp'] .= "\n" . $line;
} elseif (preg_match('!^\s*\$config\[(.+?)\] = (.+?)(;( //.+)?)?$!', $line, $matches)) {
$var['name'] = explode('][', $matches[1]);
if (count($var['name']) == 1) {
$var['name'] = preg_replace('/^\'(.*)\'$/', '$1', end($var['name']));
} else {
foreach ($var['name'] as &$i)
$i = preg_replace('/^\'(.*)\'$/', '$1', $i);
}
if (isset($matches[3]))
$var['default'] = $matches[2];
else
$var['default_temp'] = $matches[2];
}
if (trim($line) === '') {
if ($var['name'] !== false) {
if ($var['default_temp'])
$var['default'] = $var['default_temp'];
$temp = eval('return ' . $var['default'] . ';');
if (!isset($temp))
$var['type'] = 'unknown';
else
$var['type'] = gettype($temp);
unset($var['default_temp']);
if (!is_array($var['name']) || (end($var['name']) != '' && !in_array(reset($var['name']), array('stylesheets')))) {
$conf[] = $var;
}
}
$var = array(
'name' => false,
'comment' => array(),
'default' => false,
'default_temp' => false
);
}
}
return $conf;
}

1796
inc/mod/pages.php Normal file

File diff suppressed because it is too large Load Diff

View File

@ -27,7 +27,7 @@ function load_twig() {
$twig = new Twig_Environment($loader, array( $twig = new Twig_Environment($loader, array(
'autoescape' => false, 'autoescape' => false,
'cache' => "{$config['dir']['template']}/cache", 'cache' => "{$config['dir']['template']}/cache",
'debug' => ($config['debug'] ? true : false), 'debug' => $config['debug']
)); ));
$twig->addExtension(new Twig_Extensions_Extension_Tinyboard()); $twig->addExtension(new Twig_Extensions_Extension_Tinyboard());
$twig->addExtension(new Twig_Extensions_Extension_I18n()); $twig->addExtension(new Twig_Extensions_Extension_I18n());
@ -39,7 +39,7 @@ function Element($templateFile, array $options) {
if (!$twig) if (!$twig)
load_twig(); load_twig();
if (function_exists('create_pm_header') && ((isset($options['mod']) && $options['mod']) || isset($options['__mod']))) { if (function_exists('create_pm_header') && ((isset($options['mod']) && $options['mod']) || isset($options['__mod'])) && !preg_match('!^mod/!', $templateFile)) {
$options['pm'] = create_pm_header(); $options['pm'] = create_pm_header();
} }

View File

@ -1,7 +1,7 @@
<?php <?php
// Installation/upgrade file // Installation/upgrade file
define('VERSION', 'v0.9.6-dev-4'); define('VERSION', 'v0.9.6-dev-5');
require 'inc/functions.php'; require 'inc/functions.php';
@ -210,6 +210,8 @@ if (file_exists($config['has_installed'])) {
} }
case 'v0.9.6-dev-3': case 'v0.9.6-dev-3':
query("ALTER TABLE `antispam` CHANGE `hash` `hash` CHAR( 40 ) NOT NULL") or error(db_error()); query("ALTER TABLE `antispam` CHANGE `hash` `hash` CHAR( 40 ) NOT NULL") or error(db_error());
case 'v0.9.6-dev-4':
query("ALTER TABLE `news` DROP INDEX `id`, ADD PRIMARY KEY ( `id` )") or error(db_error());
case false: case false:
// Update version number // Update version number
file_write($config['has_installed'], VERSION); file_write($config['has_installed'], VERSION);

3128
mod-old.php Normal file

File diff suppressed because it is too large Load Diff

3186
mod.php

File diff suppressed because it is too large Load Diff

View File

@ -281,7 +281,7 @@ if (isset($_POST['delete'])) {
$post['name'] = $_POST['name'] != '' ? $_POST['name'] : $config['anonymous']; $post['name'] = $_POST['name'] != '' ? $_POST['name'] : $config['anonymous'];
$post['subject'] = $_POST['subject']; $post['subject'] = $_POST['subject'];
$post['email'] = utf8tohtml($_POST['email']); $post['email'] = str_replace(' ', '%20', htmlspecialchars($_POST['email']));
$post['body'] = $_POST['body']; $post['body'] = $_POST['body'];
$post['password'] = $_POST['password']; $post['password'] = $_POST['password'];
$post['has_file'] = !isset($post['embed']) && (($post['op'] && !isset($post['no_longer_require_an_image_for_op']) && $config['force_image_op']) || (isset($_FILES['file']) && $_FILES['file']['tmp_name'] != '')); $post['has_file'] = !isset($post['embed']) && (($post['op'] && !isset($post['no_longer_require_an_image_for_op']) && $config['force_image_op']) || (isset($_FILES['file']) && $_FILES['file']['tmp_name'] != ''));
@ -313,13 +313,23 @@ if (isset($_POST['delete'])) {
))); )));
} }
if ($mod && $mod['type'] >= MOD && preg_match('/^((.+) )?## (.+)$/', $post['name'], $match)) {
if (($mod['type'] == MOD && $match[3] == 'Mod') || $mod['type'] >= ADMIN) {
$post['capcode'] = utf8tohtml($match[3]);
$post['name'] = $match[2] != '' ? $match[2] : $config['anonymous'];
}
} else {
$post['capcode'] = false; $post['capcode'] = false;
if ($mod && preg_match('/^((.+) )?## (.+)$/', $post['name'], $matches)) {
$name = $matches[2] != '' ? $matches[2] : $config['anonymous'];
$cap = $matches[3];
if (isset($config['mod']['capcode'][$mod['type']])) {
if ( $config['mod']['capcode'][$mod['type']] === true ||
(is_array($config['mod']['capcode'][$mod['type']]) &&
in_array($cap, $config['mod']['capcode'][$mod['type']])
)) {
$post['capcode'] = utf8tohtml($cap);
$post['name'] = $name;
}
}
} }
$trip = generate_tripcode($post['name']); $trip = generate_tripcode($post['name']);
@ -527,7 +537,11 @@ if (isset($_POST['delete'])) {
} }
$post = (array)$post; $post = (array)$post;
$id = post($post); $post['id'] = $id = post($post);
if (isset($post['antispam_hash'])) {
incrementSpamHash($post['antispam_hash']);
}
if (isset($post['antispam_hash'])) { if (isset($post['antispam_hash'])) {
incrementSpamHash($post['antispam_hash']); incrementSpamHash($post['antispam_hash']);

View File

@ -70,15 +70,18 @@ form table input {
} }
input[type="text"], input[type="password"], textarea { input[type="text"], input[type="password"], textarea {
border: 1px solid #a9a9a9; border: 1px solid #a9a9a9;
text-indent: 0px; text-indent: 0;
text-shadow: none; text-shadow: none;
text-transform: none; text-transform: none;
word-spacing: normal; word-spacing: normal;
} }
form table tr td { form table tr td {
text-align: left; text-align: left;
margin: 0px; margin: 0;
padding: 0px; padding: 0;
}
form table.mod tr td {
padding: 2px;
} }
form table tr th { form table tr th {
text-align: left; text-align: left;
@ -104,7 +107,7 @@ form table tr td div label {
} }
p.fileinfo { p.fileinfo {
display: block; display: block;
margin: 0px; margin: 0;
padding-right: 7em; padding-right: 7em;
} }
div.banner { div.banner {
@ -263,11 +266,11 @@ span.heading {
color: #AF0A0F; color: #AF0A0F;
font-size: 11pt; font-size: 11pt;
font-weight: bold; font-weight: bold;
display: block;
} }
span.spoiler { span.spoiler {
background: black; background: black;
color: black; color: black;
padding: 0px 1px;
} }
div.post.reply p.body span.spoiler a { div.post.reply p.body span.spoiler a {
color: black; color: black;
@ -328,7 +331,7 @@ div.pages form input {
hr { hr {
border: none; border: none;
border-top: 1px solid #B7C5D9; border-top: 1px solid #B7C5D9;
height: 0px; height: 0;
clear: left; clear: left;
} }
div.boardlist { div.boardlist {
@ -351,7 +354,7 @@ table.modlog {
} }
table.modlog tr td { table.modlog tr td {
text-align: left; text-align: left;
margin: 0px; margin: 0;
padding: 4px 15px 0 0; padding: 4px 15px 0 0;
} }
table.modlog tr th { table.modlog tr th {
@ -385,19 +388,15 @@ div.blotter {
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;
} }
table.mod.config-editor {
/* Uboachan stuff */ font-size: 9pt;
div.styles-sidebar { width: 100%;
text-align: center;
padding-bottom: 0px;
} }
div.styles-sidebar a { table.mod.config-editor td {
margin: 0 5px; text-align: left;
padding: 5px;
border-bottom: 1px solid #98e;
} }
div.styles-sidebar a.selected { table.mod.config-editor input[type="text"] {
text-decoration: none; width: 98%;
}
.category {
background: #98E;
color: black;
} }

View File

@ -0,0 +1,71 @@
<!doctype html>
<html>
<head>
{% block head %}
<link rel="stylesheet" media="screen" href="{{ config.url_stylesheet }}">
{% if config.url_favicon %}<link rel="shortcut icon" href="{{ config.url_favicon }}">{% endif %}
<title>{{ board.url }} - {{ board.name }}</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=yes">
{% if config.meta_keywords %}<meta name="keywords" content="{{ config.meta_keywords }}">{% endif %}
{% if config.default_stylesheet.1 != '' %}<link rel="stylesheet" type="text/css" id="stylesheet" href="{{ config.uri_stylesheets }}{{ config.default_stylesheet.1 }}">{% endif %}
{% if not nojavascript %}
<script type="text/javascript" src="{{ config.url_javascript }}"></script>
{% if not config.additional_javascript_compile %}
{% for javascript in config.additional_javascript %}<script type="text/javascript" src="{{ config.additional_javascript_url }}{{ javascript }}"></script>{% endfor %}
{% endif %}
{% endif %}
{% if config.recaptcha %}<style type="text/css">{% raw %}
.recaptcha_image_cell {
background: none !important;
}
table.recaptchatable {
border: none !important;
}
#recaptcha_logo, #recaptcha_tagline {
display: none;
float: right;
}
.recaptchatable a {
display: block;
}
{% endraw %}</style>{% endif %}
{% endblock %}
</head>
<body>
{{ 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 %}
<header>
<h1>{{ board.url }} - {{ board.name }}</h1>
<div class="subtitle">
{% if board.title %}
{{ board.title|e }}
{% endif %}
{% if mod %}<p><a href="?/">{% trans %}Return to dashboard{% endtrans %}</a></p>{% endif %}
</div>
</header>
{% include 'post_form.html' %}
{% if config.blotter %}<hr /><div class="blotter">{{ config.blotter }}</div>{% endif %}
<hr />
<form name="postcontrols" action="{{ config.post_url }}" method="post">
<input type="hidden" name="board" value="{{ board.uri }}" />
{% if mod %}<input type="hidden" name="mod" value="1" />{% endif %}
{{ body }}
{% include 'report_delete.html' %}
</form>
<div class="pages">{{ btn.prev }} {% for page in pages %}
[<a {% if page.selected %}class="selected"{% endif %}{% if not page.selected %}href="{{ page.link }}"{% endif %}>{{ page.num }}</a>]{% if loop.last %} {% endif %}
{% endfor %} {{ btn.next }}</div>
{{ boardlist.bottom }}
<footer>
<p class="unimportant" style="margin-top:20px;text-align:center;">Powered by <a href="http://tinyboard.org/">Tinyboard</a> {{ config.version }} | <a href="http://tinyboard.org/">Tinyboard</a> Copyright &copy; 2010-2012 Tinyboard Development Group</p>
{% for footer in config.footer %}<p class="unimportant" style="text-align:center;">{{ footer }}</p>{% endfor %}
</footer>
<script type="text/javascript">{% raw %}
ready();
{% endraw %}</script>
</body>
</html>

View File

@ -1,11 +1,11 @@
<!doctype html> <!doctype html>
<html> <html>
<head> <head>
<meta charset="utf-8">
<link rel="stylesheet" media="screen" href="{{ config.url_stylesheet }}"> <link rel="stylesheet" media="screen" href="{{ config.url_stylesheet }}">
{% if config.url_favicon %}<link rel="shortcut icon" href="{{ config.url_favicon }}">{% endif %} {% if config.url_favicon %}<link rel="shortcut icon" href="{{ config.url_favicon }}">{% endif %}
<title>{{ board.url }} - {{ board.name }}</title> <title>{{ board.url }} - {{ board.name }}</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=yes">
{% if config.meta_keywords %}<meta name="keywords" content="{{ config.meta_keywords }}">{% endif %} {% if config.meta_keywords %}<meta name="keywords" content="{{ config.meta_keywords }}">{% endif %}
{% if config.default_stylesheet.1 != '' %}<link rel="stylesheet" type="text/css" id="stylesheet" href="{{ config.uri_stylesheets }}{{ config.default_stylesheet.1 }}">{% endif %} {% if config.default_stylesheet.1 != '' %}<link rel="stylesheet" type="text/css" id="stylesheet" href="{{ config.uri_stylesheets }}{{ config.default_stylesheet.1 }}">{% endif %}
{% if not nojavascript %} {% if not nojavascript %}
@ -35,10 +35,10 @@
{% 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 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 %} {% 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 %}
<header> <header>
<h1>{{ board.url }} - {{ board.name }}</h1> <h1>{{ board.url }} - {{ board.title|e }}</h1>
<div class="subtitle"> <div class="subtitle">
{% if board.title %} {% if board.subtitle %}
{{ board.title }} {{ board.subtitle|e }}
{% endif %} {% endif %}
{% if mod %}<p><a href="?/">{% trans %}Return to dashboard{% endtrans %}</a></p>{% endif %} {% if mod %}<p><a href="?/">{% trans %}Return to dashboard{% endtrans %}</a></p>{% endif %}
</div> </div>

View File

@ -0,0 +1,91 @@
{% if post and board %}
{% set action = '?/' ~ board ~ '/ban/' ~ post %}
{% else %}
{% set action = '?/ban' %}
{% endif %}
<form action="{{ action }}" method="post">
{% if redirect %}
<input type="hidden" name="redirect" value="{{ redirect|e }}">
{% endif %}
{% if post and board %}
<input type="hidden" name="delete" value="{% if delete %}1{% else %}0{% endif %}">
{% endif %}
<table>
<tr>
<th>
<label for="ip">{% trans 'IP' %} <span class="unimportant">{% trans '(or subnet)' %}</span></label>
</th>
<td>
{% if not hide_ip %}
<input type="text" name="ip" id="ip" size="30" maxlength="40" value="{{ ip }}">
{% else %}
<em>{% trans 'hidden' %}</em>
{% endif %}
</td>
</tr>
<tr>
<th>
<label for="reason">{% trans 'Reason' %}</label>
</th>
<td>
<textarea name="reason" id="reason" rows="5" cols="30">{{ reason|e }}</textarea>
</td>
</tr>
{% if post and board and not delete %}
<tr>
<th>
<label for="reason">{% trans 'Message' %}</label>
</th>
<td>
<input type="checkbox" id="public_message" name="public_message">
<input type="text" name="message" id="message" size="35" maxlength="200" value="{{ config.mod.default_ban_message|e }}">
<span class="unimportant">({% trans 'public; attached to post' %})</span>
<script type="text/javascript">
document.getElementById('message').disabled = true;
document.getElementById('public_message').onchange = function() {
document.getElementById('message').disabled = !this.checked;
}
</script>
</td>
</tr>
{% endif %}
<tr>
<th>
<label for="length">{% trans 'Length' %}</label>
</th>
<td>
<input type="text" name="length" id="length" size="20" maxlength="40">
<span class="unimportant">(eg. "2d1h30m" or "2 days")</span></td>
</tr>
<tr>
<th>{% trans 'Board' %}</th>
<td>
<ul style="list-style:none;padding:2px 5px">
<li>
<input type="radio" name="board" value="*" id="ban-allboards" checked>
<label style="display:inline" for="ban-allboards">
<em>{% trans 'all boards' %}</em>
</label>
</li>
{% for board in boards %}
<li>
<input type="radio" name="board" value="{{ board.uri }}" id="ban-board-{{ board.uri }}">
<label style="display:inline" for="ban-board-{{ board.uri }}">
{{ config.board_abbreviation|sprintf(board.uri) }} - {{ board.title|e }}
</label>
</li>
{% endfor %}
</ul>
</td>
</tr>
<tr>
<td></td>
<td><input name="new_ban" type="submit" value="{% trans 'New Ban' %}"></td>
</tr>
</table>
</form>

View File

@ -0,0 +1,95 @@
{% if bans|count == 0 %}
<p style="text-align:center" class="unimportant">({% trans 'There are no active bans.' %})</p>
{% else %}
<form action="" method="post">
<table class="mod" style="width:100%">
<tr>
<th>{% trans 'IP address/mask' %}</th>
<th>{% trans 'Reason' %}</th>
<th>{% trans 'Board' %}</th>
<th>{% trans 'Set' %}</th>
<th>{% trans 'Duration' %}</th>
<th>{% trans 'Expires' %}</th>
<th>{% trans 'Staff' %}</th>
</tr>
{% for ban in bans %}
<tr{% if ban.expires != 0 and ban.expires < time() %} style="text-decoration:line-through"{% endif %}>
<td style="white-space: nowrap">
<input type="checkbox" name="ban_{{ ban.id }}">
{% if ban.real_ip %}
<a href="?/IP/{{ ban.ip }}">{{ ban.ip }}</a>
{% else %}
{{ ban.ip|e }}
{% endif %}
</td>
<td>
{% if ban.reason %}
{{ ban.reason }}
{% else %}
-
{% endif %}
</td>
<td style="white-space: nowrap">
{% if ban.board %}
{{ config.board_abbreviation|sprintf(ban.board) }}
{% else %}
<em>{% trans 'all boards' %}</em>
{% endif %}
</td>
<td style="white-space: nowrap">
<span title="{{ ban.set|date(config.post_date) }}">
{{ ban.set|ago }} ago
</span>
</td>
<td style="white-space: nowrap">
{% if ban.expires == 0 %}
-
{% else %}
{{ (ban.expires - ban.set + time()) | until }}
{% endif %}
</td>
<td style="white-space: nowrap">
{% if ban.expires == 0 %}
<em>{% trans 'never' %}</em>
{% else %}
{{ ban.expires|date(config.post_date) }}
{% if ban.expires > time() %}
<small>(in {{ ban.expires|until }})</small>
{% endif %}
{% endif %}
</td>
<td>
{% if ban.username %}
{% if mod|hasPermission(config.mod.view_banstaff) %}
<a href="?/new_PM/{{ ban.username|e }}">{{ ban.username|e }}</a>
{% else %}
{% if mod|hasPermission(config.mod.view_banquestionmark) %}
<em>?</em>
{% else %}
{% endif %}
{% endif %}
{% elseif ban.mod == -1 %}
<em>system</em>
{% else %}
<em>{% trans 'deleted?' %}</em>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
<p style="text-align:center">
<input type="submit" name="unban" value="{% trans 'Unban selected' %}">
</p>
</form>
{% endif %}
{% if count > bans|count %}
<p class="unimportant" style="text-align:center;word-wrap:break-word">
{% for i in range(0, (count - 1) / config.mod.modlog_page) %}
<a href="?/bans/{{ i + 1 }}">[{{ i + 1 }}]</a>
{% endfor %}
</p>
{% endif %}

44
templates/mod/board.html Normal file
View File

@ -0,0 +1,44 @@
{% if new %}
{% set action = '?/new-board' %}
{% else %}
{% set action = '?/edit/' ~ board.uri %}
{% endif %}
<form action="{{ action }}" method="post">
<table>
<tr>
<th>{% trans 'URI' %}</th>
<td>
{% if not new %}
{{ config.board_abbreviation|sprintf(board.uri) }}
{% else %}
/<input size="10" maxlength="255" type="text" name="uri" autocomplete="off">/
{% endif %}
</td>
</tr>
<tr>
<th>{% trans 'Title' %}</th>
<td>
<input size="25" type="text" name="title" value="{{ board.title|e }}" autocomplete="off">
</td>
</tr>
<tr>
<th>{% trans 'Subtitle' %}</th>
<td>
<input size="25" type="text" name="subtitle" value="{{ board.subtitle|e }}" autocomplete="off">
</td>
</tr>
</table>
<ul style="padding:0;text-align:center;list-style:none">
{% if new %}
<li><input type="submit" value="{% trans 'Create board' %}"></li>
{% else %}
<li><input type="submit" value="{% trans 'Save changes' %}"></li>
{% if mod|hasPermission(config.mod.deleteboard) %}
<li><input name="delete" onclick="return confirm('Are you sure you want to permanently delete this board?');" type="submit" value="{% trans 'Delete board' %}"></li>
{% endif %}
{% endif %}
</ul>
</form>

View File

@ -0,0 +1,56 @@
<form method="post" action="">
<table class="mod config-editor">
<tr>
<th class="minimal">Name</th>
<th>Value</th>
<th class="minimal">Type</th>
<th>Description</th>
</tr>
{% for var in conf if var.type != 'array' %}
{% if var.name|count == 1 %}
{% set name = 'cf_' ~ var.name %}
{% else %}
{% set name = 'cf_' ~ var.name|join('/') %}
{% endif %}
<tr>
<th class="minimal">
{% if var.name|count == 1 %}
{{ var.name }}
{% else %}
{{ var.name|join(' &rarr; ') }}
{% endif %}
</th>
<td>
{% if var.type == 'string' %}
<input name="{{ name }}" type="text" value="{{ var.value|e }}">
{% elseif var.type == 'integer' %}
<input name="{{ name }}" type="number" value="{{ var.value|e }}">
{% elseif var.type == 'boolean' %}
<input name="{{ name }}" type="checkbox" {% if var.value %}checked{% endif %}>
{% else %}
?
{% endif %}
{% if var.type == 'integer' or var.type == 'boolean' %}
<small>Default: <code>{{ var.default }}</code></small>
{% endif %}
</td>
<td class="minimal">
{{ var.type|e }}
</td>
<td>
{{ var.comment|join('<br>') }}
</td>
</tr>
{% endfor %}
</table>
<ul style="padding:0;text-align:center;list-style:none">
<li><input name="save" type="submit" value="{% trans 'Save changes' %}"></li>
</ul>
</form>

View File

@ -0,0 +1,7 @@
<p style="text-align:center;font-size:1.1em">
{% trans 'Are you sure you want to do that?' %} <a href="?/{{ request }}">{% trans 'Click to proceed to' %} ?/{{ request }}</a>.
</p>
<p class="unimportant" style="text-align:center">
{% trans 'You are seeing this message because we were unable to serve a confirmation dialog, probably due to Javascript being disabled.' %}
</p>

View File

@ -0,0 +1,139 @@
<fieldset>
<legend>{% trans 'Boards' %}</legend>
<ul>
{% for board in boards %}
<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; {{ board.subtitle|e }}</small>
{% endif %}
{% if mod|hasPermission(config.mod.manageboards) %}
<a href="?/edit/{{ board.uri }}"><small>[{% trans 'edit' %}]</small></a>
{% endif %}
</li>
{% 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>
<legend>{% trans 'Messages' %}</legend>
<ul>
{% if mod|hasPermission(config.mod.noticeboard) %}
{% if noticeboard|count > 0 %}
<li>
{% trans 'Noticeboard' %}:
<ul>
{% for post in noticeboard %}
<li>
<a href="?/noticeboard#{{ post.id }}">
{% if post.subject %}
{{ post.subject|e }}
{% else %}
<em>{% trans 'no subject' %}</em>
{% endif %}
</a>
<small class="unimportant">
&mdash; by
{% if post.username %}
{{ post.username|e }}
{% else %}
<em>deleted?</em>
{% endif %}
at
{{ post.time|date(config.post_date) }}
</small>
</li>
{% endfor %}
</ul>
</li>
{% endif %}
<li><a href="?/noticeboard">{% trans 'View all entries' %}</a></li>
{% endif %}
<li><a href="?/news">{% trans 'News' %}</a></li>
<li>
<a href="?/inbox">
{% trans 'PM inbox' %}
{% if unread_pms > 0 %}<strong>{%endif %}({{ unread_pms }} unread){% if unread_pms > 0 %}</strong>{%endif %}
</a>
</li>
</ul>
</fieldset>
<fieldset>
<legend>{% trans 'Administration' %}</legend>
<ul>
{% if mod|hasPermission(config.mod.reports) %}
<li>
{% if reports > 0 %}<strong>{% endif %}
<a href="?/reports">{% trans 'Report queue' %} ({{ reports }})</a>
{% if reports > 0 %}</strong>{% endif %}
</li>
{% endif %}
{% if mod|hasPermission(config.mod.view_banlist) %}
<li><a href="?/bans">{% trans 'Ban list' %}</a></li>
{% endif %}
{% if mod|hasPermission(config.mod.manageusers) %}
<li><a href="?/users">{% trans 'Manage users' %}</a></li>
{% elseif mod|hasPermission(config.mod.change_password) %}
<li><a href="?/users/{{ mod.id }}">{% trans 'Change password' %}</a></li>
{% endif %}
{% if mod|hasPermission(config.mod.themes) %}
<li><a href="?/themes">{% trans 'Manage themes' %}</a></li>
{% endif %}
{% if mod|hasPermission(config.mod.modlog) %}
<li><a href="?/log">{% trans 'Moderation log' %}</a></li>
{% endif %}
{% if mod|hasPermission(config.mod.rebuild) %}
<li><a href="?/rebuild">{% trans 'Rebuild' %}</a></li>
{% endif %}
{% if mod|hasPermission(config.mod.show_config) %}
<li><a href="?/config">{% trans 'Configuration' %}</a></li>
{% endif %}
</ul>
</fieldset>
<fieldset>
<legend>{% trans 'Search' %}</legend>
{# TODO #}
</fieldset>
{% if config.debug %}
<fieldset>
<legend>{% trans 'Debug' %}</legend>
<ul>
<li><a href="?/debug/antispam">{% trans 'Anti-spam' %}</a></li>
</ul>
</fieldset>
{% endif %}
{% if newer_release %}
<fieldset>
<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 %}
<fieldset>
<legend>{% trans 'User account' %}</legend>
<ul>
<li><a href="?/logout">{% trans 'Logout' %}</a></li>
</ul>
</fieldset>

View File

@ -0,0 +1,65 @@
<p style="text-align:center">
Most used (in active):
</p>
<table class="modlog" style="width:700px;margin:auto">
<tr>
<th>Board</th>
<th>Thread</th>
<th>Hash (SHA1)</th>
<th>Created</th>
<th>Expires</th>
<th>Passed</th>
</tr>
{% for hash in top %}
<tr>
<td>{{ config.board_abbreviation|sprintf(hash.board) }}</td>
<td>
{% if hash.thread %}
{{ hash.thread }}
{% else %}
-
{% endif %}</td>
<td>
<small><code>{{ hash.hash }}</code></small>
</td>
<td>
<span title="{{ hash.created|date(config.post_date) }}">{{ hash.created|ago }} ago</span>
</td>
<td>
{% if hash.expires %}
<span title="{{ hash.expires|date(config.post_date) }}">
{{ hash.expires|until }}
</span>
{% else %}
-
{% endif %}
</td>
<td>{{ hash.passed }}</td>
</tr>
{% endfor %}
</table>
<p style="text-align:center">
Total: <strong>{{ total }}</strong> (<strong>{{ expiring }}</strong> set to expire)
</p>
<form method="post" action="?/debug/antispam">
<table class="modlog" style="width:1%;white-space:nowrap;margin:auto">
<tr>
<th>Board</th>
<th>Thread</th>
<th></th>
</tr>
<tr>
<td>
<input type="text" name="board" style="width:90px" value="{{ board }}">
</td>
<td>
<input type="text" name="thread" style="width:90px" value="{{ thread }}">
</td>
<td>
<input type="submit" name="filter" value="Filter">
<input type="submit" name="purge" value="Purge">
</td>
</tr>
</table>
</form>

36
templates/mod/inbox.html Normal file
View File

@ -0,0 +1,36 @@
{% if messages|count == 0 %}
<p style="text-align:center" class="unimportant">({% trans 'No private messages for you.' %})</p>
{% else %}
<table class="modlog">
<tr>
<th>{% trans 'ID' %}</th>
<th>{% trans 'From' %}</th>
<th>{% trans 'Date' %}</th>
<th>{% trans 'Message snippet' %}</th>
</tr>
{% for message in messages %}
<tr{% if message.unread %} style="font-weight:bold"{% endif %}>
<td class="minimal">
<a href="?/PM/{{ message.id }}">
{{ message.id }}
</a>
</td>
<td class="minimal">
{% if message.username %}
<a href="?/new_PM/{{ message.username|e }}">{{ message.username|e }}</a>
{% else %}
<em>{% trans 'deleted?' %}</em>
{% endif %}
</td>
<td class="minimal">
{{ message.time|date(config.post_date) }}
</td>
<td>
<a href="?/PM/{{ message.id }}">
{{ message.snippet }}
</a>
</td>
</tr>
{% endfor %}
</table>
{% endif %}

47
templates/mod/log.html Normal file
View File

@ -0,0 +1,47 @@
<table class="modlog">
<tr>
<th>{% trans 'Staff' %}</th>
<th>{% trans 'IP address' %}</th>
<th>{% trans 'Time' %}</th>
<th>{% trans 'Board' %}</th>
<th>{% trans 'Action' %}</th>
</tr>
{% for log in logs %}
<tr>
<td class="minimal">
{% if log.username %}
<a href="?/new_PM/{{ log.username|e }}">{{ log.username|e }}</a>
{% elseif log.mod == -1 %}
<em>system</em>
{% else %}
<em>{% trans 'deleted?' %}</em>
{% endif %}
</td>
<td class="minimal">
<a href="?/IP/{{ log.ip }}">{{ log.ip }}</a>
</td>
<td class="minimal">
<span title="{{ log.time|date(config.post_date) }}">{{ log.time|ago }}</span>
</td>
<td class="minimal">
{% if log.board %}
<a href="?/{{ config.board_path|sprintf(log.board) }}{{ config.file_index }}">{{ config.board_abbreviation|sprintf(log.board) }}</a>
{% else %}
-
{% endif %}
</td>
<td>
{{ log.text }}
</td>
</tr>
{% endfor %}
</table>
{% if count > logs|count %}
<p class="unimportant" style="text-align:center;word-wrap:break-word">
{% for i in range(0, (count - 1) / config.mod.modlog_page) %}
<a href="?/log/{{ i + 1 }}">[{{ i + 1 }}]</a>
{% endfor %}
</p>
{% endif %}

View File

@ -1,18 +1,17 @@
{% if error %}<h2 style="text-align:center">{{ error }}</h2>{% endif %} {% if error %}<h2 style="text-align:center">{{ error }}</h2>{% endif %}
<form action="" method="post"> <form action="" method="post">
{% if redirect %}<input type="hidden" name="redirect" value="{{ redirect }}" />{% endif %}
<table style="margin-top:25px;"> <table style="margin-top:25px;">
<tr> <tr>
<th> <th>
{% trans %}Username{% endtrans %} {% trans 'Username' %}
</th> </th>
<td> <td>
<input type="text" name="username" size="20" maxlength="30" value="{{ username }}"> <input type="text" name="username" size="20" maxlength="30" value="{{ username|e }}">
</td> </td>
</tr> </tr>
<tr> <tr>
<th> <th>
{% trans %}Password{% endtrans %} {% trans 'Password' %}
</th> </th>
<td> <td>
<input type="password" name="password" size="20" maxlength="30" value=""> <input type="password" name="password" size="20" maxlength="30" value="">

41
templates/mod/move.html Normal file
View File

@ -0,0 +1,41 @@
<form action="?/{{ board }}/move/{{ post }}" method="post">
<table>
<tr>
<th>
{% trans 'Thread ID' %}
</th>
<td>
&gt;&gt;&gt;{{ config.board_abbreviation|sprintf(board) }}{{ post }}
</td>
</tr>
<tr>
<th>
{% trans 'Leave shadow thread' %}
</th>
<td>
<input type="checkbox" name="shadow" checked>
<span class="unimportant">({% trans 'locks thread; replies to it with a link.' %})</span>
</td>
</tr>
<tr>
<th>{% trans 'Target board' %}</th>
<td>
<ul style="list-style:none;padding:0">
{% for targetboard in boards if targetboard.uri != board %}
<li>
<input type="radio" name="board" value="{{ targetboard.uri }}" id="ban-board-{{ targetboard.uri }}">
<label style="display:inline" for="ban-board-{{ targetboard.uri }}">
{{ config.board_abbreviation|sprintf(targetboard.uri) }} - {{ targetboard.title|e }}
</label>
</li>
{% endfor %}
</ul>
</td>
</tr>
</table>
<ul style="padding:0;text-align:center;list-style:none">
<li><input type="submit" value="{% trans 'Move thread' %}"></li>
</ul>
</form>

18
templates/mod/new_pm.html Normal file
View File

@ -0,0 +1,18 @@
<form action="?/new_PM/{{ username|e }}" method="post">
<table>
<tr>
<th>To</th>
{% if mod|hasPermission(config.mod.editusers) %}
<td><a href="?/users/{{ id }}">{{ username|e }}</a></td>
{% else %}
<td>{{ username|e }}</td>
{% endif %}
</tr>
<tr>
<th>Message</th>
<td><textarea name="message" rows="10" cols="40">{{ message }}</textarea></td>
</tr>
</table>
<p style="text-align:center"><input type="submit" value="{% trans 'Send message' %}"></p>
</form>

70
templates/mod/news.html Normal file
View File

@ -0,0 +1,70 @@
{% if mod|hasPermission(config.mod.news) %}
<fieldset>
<legend>{% trans 'New post' %}</legend>
<form style="margin:0" action="" method="post">
<table>
<tr>
<th>
{% if mod|hasPermission(config.mod.news_custom) %}
<label for="name">{% trans 'Name' %}</label>
{% else %}
{% trans 'Name' %}
{% endif %}
</th>
<td>
{% if mod|hasPermission(config.mod.news_custom) %}
<input type="text" size="55" name="name" id="name" value="{{ mod.username|e }}">
{% else %}
{{ mod.username|e }}
{% endif %}
</td>
</tr>
<tr>
<th><label for="subject">{% trans 'Subject' %}</label></th>
<td><input type="text" size="55" name="subject" id="subject"></td>
</tr>
<tr>
<th><label for="body">{% trans 'Body' %}</label></th>
<td><textarea name="body" id="body" style="width:100%;height:100px"></textarea></td>
</tr>
</table>
<p style="text-align:center">
<input type="submit" value="{% trans 'Post news entry' %}">
</p>
</form>
</fieldset>
{% endif %}
{% for post in news %}
<div class="ban">
{% if mod|hasPermission(config.mod.news_delete) %}
<span style="float:right;padding:2px">
<a class="unimportant" href="?/news/delete/{{ post.id }}">[{% trans 'delete' %}]</a>
</span>
{% endif %}
<h2 id="{{ post.id }}">
<small class="unimportant">
<a href="#{{ post.id }}">#</a>
</small>
{% if post.subject %}
{{ post.subject|e }}
{% else %}
<em>{% trans 'no subject' %}</em>
{% endif %}
<small class="unimportant">
&mdash; {% trans 'by' %} {{ post.name }} {% trans 'at' %} {{ notice.time|date(config.post_date) }}
</small>
</h2>
<p>
{{ post.body }}
</p>
</div>
{% endfor %}
{% if count > news|count %}
<p class="unimportant" style="text-align:center;word-wrap:break-word">
{% for i in range(0, (count - 1) / config.mod.news_page) %}
<a href="?/news/{{ i + 1 }}">[{{ i + 1 }}]</a>
{% endfor %}
</p>
{% endif %}

View File

@ -0,0 +1,65 @@
{% if mod|hasPermission(config.mod.noticeboard_post) %}
<fieldset>
<legend>{% trans 'New post' %}</legend>
<form style="margin:0" action="" method="post">
<table>
<tr>
<th>{% trans 'Name' %}</th>
<td>{{ mod.username|e }}</td>
</tr>
<tr>
<th><label for="subject">{% trans 'Subject' %}</label></th>
<td><input type="text" size="55" name="subject" id="subject" /></td>
</tr>
<tr>
<th>{% trans 'Body' %}</th>
<td><textarea name="body" style="width:100%;height:100px"></textarea></td>
</tr>
</table>
<p style="text-align:center">
<input type="submit" value="{% trans 'Post to noticeboard' %}" />
</p>
</form>
</fieldset>
{% endif %}
{% for post in noticeboard %}
<div class="ban">
{% if mod|hasPermission(config.mod.noticeboard_delete) %}
<span style="float:right;padding:2px">
<a class="unimportant" href="?/noticeboard/delete/{{ post.id }}">[{% trans 'delete' %}]</a>
</span>
{% endif %}
<h2 id="{{ post.id }}">
<small class="unimportant">
<a href="#{{ post.id }}">#</a>
</small>
{% if post.subject %}
{{ post.subject|e }}
{% else %}
<em>{% trans 'no subject' %}</em>
{% endif %}
<small class="unimportant">
&mdash; {% trans 'by' %}
{% if post.username %}
{{ post.username|e }}
{% else %}
<em>{% trans 'deleted?' %}</em>
{% endif %}
{% trans 'at' %}
{{ post.time|date(config.post_date) }}
</small>
</h2>
<p>
{{ post.body }}
</p>
</div>
{% endfor %}
{% if count > noticeboard|count %}
<p class="unimportant" style="text-align:center;word-wrap:break-word">
{% for i in range(0, (count - 1) / config.mod.noticeboard_page) %}
<a href="?/noticeboard/{{ i + 1 }}">[{{ i + 1 }}]</a>
{% endfor %}
</p>
{% endif %}

44
templates/mod/pm.html Normal file
View File

@ -0,0 +1,44 @@
<form action="" method="post">
<table>
<tr>
<th>{% trans 'From' %}</th>
{% if username %}
<td><a href="?/new_PM/{{ username|e }}">{{ username|e }}</a></td>
{% else %}
<td><em>{% trans 'deleted?' %}</em></td>
{% endif %}
</tr>
{% if to != mod.id %}
<tr>
<th>To</th>
{% if to_username %}
<td><a href="?/new_PM/{{ to_username|e }}">{{ to_username|e }}</a></td>
{% else %}
<td><em>{% trans 'deleted?' %}</em></td>
{% endif %}
</tr>
{% endif %}
<tr>
<th>{% trans 'Date' %}</th>
<td>{{ time|date(config.post_date) }} <small>({{ time|ago }} ago)</small></td>
</tr>
<tr>
<th>{% trans 'Message' %}</th>
<td>{{ message }}</td>
</tr>
</table>
<ul style="list-style:none;text-align:center;padding:0">
<li style="padding:5px 0">
<input type="submit" name="delete" value="{% trans 'Delete forever' %}">
</li>
{% if mod|hasPermission(config.mod.create_pm) %}
<li style="padding:5px 0">
<a href="?/PM/{{ id }}/reply">
{% trans 'Reply with quote' %}
</a>
</li>
{% endif %}
</ul>
</form>

View File

@ -0,0 +1,71 @@
<form style="width:300px;margin:auto" action="?/rebuild" method="post">
<ul id="rebuild">
<li style="margin-bottom:8px">
<input type="checkbox" name="rebuild_all" id="rebuild_all" onchange="toggleall(this.checked)">
<label for="rebuild_all"><strong>{% trans 'Toggle all' %}</strong></label>
<script>
function toggleall(val) {
/* TODO: something more suitable for all browsers? */
var elements = document.getElementById('rebuild').querySelectorAll('input');
for (i in elements) {
elements[i].checked = val;
}
}
</script>
</li>
<li>
<input type="checkbox" name="rebuild_cache" id="rebuild_cache" checked>
<label for="rebuild_cache">{% trans 'Flush cache' %}</label>
</li>
<li>
<input type="checkbox" name="rebuild_javascript" id="rebuild_javascript" checked>
<label for="rebuild_javascript">{% trans 'Rebuild Javascript' %}</label>
</li>
<li>
<input type="checkbox" name="rebuild_index" id="rebuild_index" checked>
<label for="rebuild_index">{% trans 'Rebuild index pages' %}</label>
</li>
<li>
<input type="checkbox" name="rebuild_thread" id="rebuild_thread" checked>
<label for="rebuild_thread">{% trans 'Rebuild thread pages' %}</label>
</li>
<li>
<input type="checkbox" name="rebuild_themes" id="rebuild_themes" checked>
<label for="rebuild_themes">{% trans 'Rebuild themes' %}</label>
</li>
<li>
<input type="checkbox" name="rebuild_posts" id="rebuild_posts">
<label for="rebuild_posts">{% trans 'Rebuild replies' %}</label>
</li>
</ul>
<hr>
<ul id="boards">
<li style="margin-bottom:8px">
<input type="checkbox" name="boards_all" id="boards_all" onchange="toggleallboards(this.checked)" checked>
<label for="boards_all"><strong>{% trans 'All boards' %}</strong></label>
<script>
function toggleallboards(val) {
/* TODO: something more suitable for all browsers? */
var elements = document.getElementById('boards').querySelectorAll('input');
for (i in elements) {
elements[i].checked = val;
}
}
</script>
</li>
{% for board in boards %}
<li>
<input type="checkbox" name="board_{{ board.uri }}" id="board-{{ board.uri }}" checked>
<label for="board-{{ board.uri }}">
{{ config.board_abbreviation|sprintf(board.uri) }} - {{ board.title|e }}
</label>
</li>
{% endfor %}
</ul>
<p style="text-align:center">
<input type="submit" value="{% trans 'Rebuild' %}" name="rebuild">
</p>
</form>

View File

@ -0,0 +1,12 @@
<div class="ban">
<h2>{% trans 'Rebuilt' %}</h2>
<ul>
{% for log in logs %}
<li>{{ log }}</li>
{% endfor %}
</ul>
<p>
<a href="?/rebuild">{% trans 'Go back and rebuild again' %}</a>.
</p>
</div>

26
templates/mod/report.html Normal file
View File

@ -0,0 +1,26 @@
<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">Dismiss</a>
{% endif %}
{% if mod|hasPermission(config.mod.report_dismiss_ip, report.board) %}
{% if mod|hasPermission(config.mod.report_dismiss, report.board) %}
|
{% endif %}
<a title="{% trans 'Discard all abuse reports by this IP address' %}" href="?/reports/{{ report.id }}/dismissall">Dismiss+</a>
{% endif %}
{% endif %}
</div>

View File

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

View File

@ -0,0 +1,27 @@
<form action="" method="post">
{% if not config %}
<p style="text-align:center" class="unimportant">(No configuration required.)</p>
{% else %}
<table>
{% for conf in theme.config %}
<tr>
<th>{{ conf.title }}</th>
<td>
<input type="text" name="{{ conf.name }}"
{% if settings[conf.name] %}value="{{ settings[conf.name] }}"{% else %}{% if conf.default %}value="{{ conf.default }}"{% endif %}{% endif %}
{% if conf.size %}
size="{{ conf.size }}"
{% endif %}
/>
{% if conf.comment %}
<span class="unimportant">{{ conf.comment }}</span>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% endif %}
<p style="text-align:center">
<input name="install" type="submit" value="{% trans 'Install theme' %}" />
</p>
</form>

View File

@ -0,0 +1,9 @@
{% if message %}
<div style="border:1px dashed maroon;padding:20px;margin:auto;max-width:800px">{{ message }}</div>
{% endif %}
{% if result %}
<p style="text-align:center">{% trans 'Successfully installed and built theme.' %}</p>
{% endif %}
<p style="text-align:center"><a href="?/themes">{% trans 'Go back to themes' %}</a>.</p>

View File

@ -0,0 +1,2 @@
<p style="text-align:center">{{ 'Successfully rebuilt the <strong>%s</strong> theme.'|trans|sprintf(theme_name) }}</p>
<p style="text-align:center"><a href="?/themes">{% trans 'Go back to themes' %}</a>.</p>

40
templates/mod/themes.html Normal file
View File

@ -0,0 +1,40 @@
{% if themes|count == 0 %}
<p style="text-align:center" class="unimportant">({% trans 'There are no themes available.' %})</p>
{% else %}
<table class="modlog">
{% for theme_name, theme in themes %}
<tr>
<th class="minimal">{% trans 'Name' %}</th>
<td>{{ theme.name }}</td>
</tr>
<tr>
<th class="minimal">{% trans 'Version' %}</th>
<td>{{ theme.version }}</td>
</tr>
<tr>
<th class="minimal">{% trans 'Description' %}</th>
<td>{{ theme.description }}</td>
</tr>
<tr>
<th class="minimal">{% trans 'Thumbnail' %}</th>
<td>
<img style="float:none;margin:4px{% if theme_name in themes_in_use %};border:2px solid red;padding:4px{% endif %}" src="{{ config.dir.themes_uri }}/{{ theme_name }}/thumb.png" />
</td>
</tr>
<tr>
<th class="minimal">{% trans 'Actions' %}</th>
<td><ul style="padding:0 20px">
<li><a title=" {% trans 'Use theme' %}" href="?/themes/{{ theme_name }}">
{% if theme_name in themes_in_use %}{% trans 'Reconfigure' %}{% else %}{% trans 'Install' %}{% endif %}
</a></li>
{% if theme_name in themes_in_use %}
<li><a href="?/themes/{{ theme_name }}/rebuild">{% trans 'Rebuild' %}</a></li>
<li><a href="?/themes/{{ theme_name }}/uninstall" onclick="return confirm('Are you sure you want to uninstall this theme?');">{% trans 'Uninstall' %}</a></li>
{% endif %}
</ul></td>
</tr>
<tr style="height:40px"><td colspan="2"><hr/></td></tr>
</tr>
{% endfor %}
</table>
{% endif %}

125
templates/mod/user.html Normal file
View File

@ -0,0 +1,125 @@
{% if new %}
{% set action = '?/users/new' %}
{% else %}
{% set action = '?/users/' ~ user.id %}
{% endif %}
<form action="{{ action }}" method="post">
<table>
<tr>
<th>{% trans 'Username' %}</th>
<td>
{% if new or mod|hasPermission(config.mod.editusers) %}
<input size="20" maxlength="30" type="text" name="username" value="{{ user.username|e }}" autocomplete="off">
{% else %}
{{ user.username|e }}
{% endif %}
</td>
</tr>
<tr>
<th>{% trans 'Password' %}{% if not new %} <small style="font-weight:normal">({% trans 'new; optional' %})</small>{% endif %}</th>
<td>
{% if new or (mod|hasPermission(config.mod.editusers) or (mod|hasPermission(config.mod.change_password) and user.id == mod.id)) %}
<input size="20" maxlength="30" type="password" name="password" value="" autocomplete="off">
{% else %}
-
{% endif %}
</td>
</tr>
{% if new %}
<tr>
<th>{% trans 'Class' %}</th>
<td>
<ul style="padding:5px 8px;list-style:none">
<li>
<input type="radio" name="type" id="janitor" value="{{ constant('JANITOR') }}">
<label for="janitor">{% trans 'Janitor' %}</label>
</li>
<li>
<input type="radio" name="type" id="mod" value="{{ constant('MOD') }}" checked>
<label for="mod">{% trans 'Mod' %}</label>
</li>
<li>
<input type="radio" name="type" id="admin" value="{{ constant('Admin') }}">
<label for="admin">{% trans 'Admin' %}</label>
</li>
</ul>
</td>
</tr>
{% endif %}
<tr>
<th>{% trans 'Boards' %}</th>
<td>
<ul style="padding:0 5px;list-style:none">
<li>
<input type="checkbox" id="allboards" name="allboards"
{% if '*' in user.boards %} checked{% endif %}
{% if not mod|hasPermission(config.mod.editusers) %}
disabled
{% endif %}
>
<label for="allboards">"*" - {% trans 'All boards' %}</label>
</li>
{% for board in boards %}
<li>
<input type="checkbox" id="board_{{ board.uri }}" name="board_{{ board.uri }}"
{% if board.uri in user.boards %} checked{% endif %}
{% if not mod|hasPermission(config.mod.editusers) %}
disabled
{% endif %}
>
<label for="board_{{ board.uri }}">
{{ config.board_abbreviation|sprintf(board.uri) }}
-
{{ board.title|e }}
</label>
</li>
{% endfor %}
</ul>
</td>
</tr>
</table>
<ul style="padding:0;text-align:center;list-style:none">
{% if new %}
<li><input type="submit" value="{% trans 'Create user' %}"></li>
{% else %}
<li><input type="submit" value="{% trans 'Save changes' %}"></li>
{% if mod|hasPermission(config.mod.deleteusers) %}
<li><input name="delete" onclick="return confirm('Are you sure you want to permanently delete this user?');" type="submit" value="{% trans 'Delete user' %}"></li>
{% endif %}
{% endif %}
</ul>
</form>
{% if logs|count > 0 %}
<table class="modlog" style="width:600px">
<tr>
<th>{% trans 'IP address' %}</th>
<th>{% trans 'Time' %}</th>
<th>{% trans 'Board' %}</th>
<th>{% trans 'Action' %}</th>
</tr>
{% for log in logs %}
<tr>
<td class="minimal">
<a href="?/IP/{{ log.ip }}">{{ log.ip }}</a>
</td>
<td class="minimal">
<span title="{{ log.time|date(config.post_date) }}">{{ log.time|ago }}</span>
</td>
<td class="minimal">
{% if log.board %}
<a href="?/{{ config.board_path|sprintf(log.board) }}{{ config.file_index }}">{{ config.board_abbreviation|sprintf(log.board) }}</a>
{% else %}
-
{% endif %}
</td>
<td>
{{ log.text }}
</td>
</tr>
{% endfor %}
</table>
{% endif %}

71
templates/mod/users.html Normal file
View File

@ -0,0 +1,71 @@
<table class="modlog" style="width:auto">
<tr>
<th>{% trans 'ID' %}</th>
<th>{% trans 'Username' %}</th>
<th>{% trans 'Type' %}</th>
<th>{% trans 'Boards' %}</th>
{% if mod|hasPermission(config.mod.modlog) %}
<th>{% trans 'Last action' %}</th>
{% endif %}
<th>&hellip;</th>
</tr>
{% for user in users %}
<tr>
<td><small>{{ user.id }}</small></td>
<td>{{ user.username|e }}</td>
<td>
{% if user.type == constant('JANITOR') %}{% trans 'Janitor' %}
{% elseif user.type == constant('MOD') %}{% trans 'Mod' %}
{% elseif user.type == constant('ADMIN') %}{% trans 'Admin' %}
{% endif %}
</td>
<td>
{% if user.boards == '' %}
<em>{% trans 'none' %}</em>
{% elseif user.boards == '*' %}
<em>{% trans 'all boards' %}</em>
{% else %}
{# This is really messy, but IMO it beats doing it in PHP. #}
{% set boards = user.boards|split(',') %}
{% set _boards = [] %}
{% for board in boards %}
{% set _boards = _boards|push(board == '*' ? '*' : config.board_abbreviation|sprintf(board)) %}
{% endfor %}
{% set _boards = _boards|sort %}
{{ _boards|join(', ') }}
{% endif %}
</td>
{% if mod|hasPermission(config.mod.modlog) %}
<td>
{% if user.last %}
<span title="{{ user.action|e }}">{{ user.last|ago }}</span>
{% else %}
<em>{% trans 'never' %}</em>
{% endif %}
</td>
{% endif %}
<td>
{% if mod|hasPermission(config.mod.promoteusers) and user.type < constant('ADMIN') %}
<a style="float:left;text-decoration:none" href="?/users/{{ user.id }}/promote" title="{% trans 'Promote' %}">&#9650;</a>
{% endif %}
{% if mod|hasPermission(config.mod.promoteusers) and user.type > constant('JANITOR') %}
<a style="float:left;text-decoration:none" href="?/users/{{ user.id }}/demote" title="{% trans 'Demote' %}">&#9660;</a>
{% endif %}
{% if mod|hasPermission(config.mod.editusers) or (mod|hasPermission(config.mod.change_password) and mod.id == user.id) %}
<a class="unimportant" style="margin-left:5px;float:right" href="?/users/{{ user.id }}">[{% trans 'edit' %}]</a>
{% endif %}
{% if mod|hasPermission(config.mod.create_pm) %}
<a class="unimportant" style="margin-left:5px;float:right" href="?/new_PM/{{ user.username|e }}">[{% trans 'PM' %}]</a>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% if mod|hasPermission(config.mod.createusers) %}
<p style="text-align:center">
<a href="?/users/new">Create a new user</a>
</p>
{% endif %}

163
templates/mod/view_ip.html Normal file
View File

@ -0,0 +1,163 @@
{% for board_posts in posts %}
<fieldset>
<legend>
<a href="?/{{ config.board_path|sprintf(board_posts.board.uri) }}{{ config.file_index }}">
{{ config.board_abbreviation|sprintf(board_posts.board.uri) }}
-
{{ board_posts.board.title|e }}
</a>
</legend>
{{ board_posts.posts|join('<hr>') }}
</fieldset>
{% endfor %}
{% if mod|hasPermission(config.mod.view_notes) %}
<fieldset id="notes">
<legend>
{% set notes_on_record = 'note' ~ (notes|count != 1 ? 's' : '') ~ ' on record' %}
<legend>{{ notes|count }} {% trans notes_on_record %}</legend>
</legend>
{% if notes|count > 0 %}
<table class="modlog">
<tr>
<th>{% trans 'Staff' %}</th>
<th>{% trans 'Note' %}</th>
<th>{% trans 'Date' %}</th>
{% if mod|hasPermission(config.mod.remove_notes) %}
<th>{% trans 'Actions' %}</th>
{% endif %}
</tr>
{% for note in notes %}
<tr>
<td class="minimal">
{% if note.username %}
<a href="?/new_PM/{{ note.username|e }}">{{ note.username|e }}</a>
{% else %}
<em>{% trans 'deleted?' %}</em>
{% endif %}
</td>
<td>
{{ note.body }}
</td>
<td class="minimal">
{{ note.time|date(config.post_date) }}
</td>
{% if mod|hasPermission(config.mod.remove_notes) %}
<td class="minimal">
<a href="?/IP/{{ ip }}/remove_note/{{ note.id }}">
<small>[{% trans 'remove' %}]</small>
</a>
</td>
{% endif %}
</tr>
{% endfor %}
</table>
{% endif %}
{% if mod|hasPermission(config.mod.create_notes) %}
<form action="" method="post" style="margin:0">
<table>
<tr>
<th>{% trans 'Staff' %}</th>
<td>{{ mod.username|e }}</td>
</tr>
<tr>
<th>
<label for="note">{% trans 'Note' %}</label>
</th>
<td>
<textarea id="note" name="note" rows="5" cols="30"></textarea>
</td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="{% trans 'New note' %}"></td>
</tr>
</table>
</form>
{% endif %}
</fieldset>
{% endif %}
{% if bans|count > 0 and mod|hasPermission(config.mod.view_ban) %}
<fieldset id="bans">
{% set bans_on_record = 'ban' ~ (bans|count != 1 ? 's' : '') ~ ' on record' %}
<legend>{{ bans|count }} {% trans bans_on_record %}</legend>
{% for ban in bans %}
<form action="" method="post" style="text-align:center">
<table style="width:400px;margin-bottom:10px;border-bottom:1px solid #ddd;padding:5px">
<tr>
<th>{% trans 'Status' %}</th>
<td>
{% if config.mod.view_banexpired and ban.expires != 0 and ban.expires < time() %}
{% trans 'Expired' %}
{% else %}
{% trans 'Active' %}
{% endif %}
</td>
</tr>
<tr>
<th>{% trans 'IP' %}</th>
<td>{{ ban.ip }}</td>
</tr>
<tr>
<th>{% trans 'Reason' %}</th>
<td>
{% if ban.reason %}
{{ ban.reason }}
{% else %}
<em>{% trans 'no reason' %}</em>
{% endif %}
</td>
</tr>
<tr>
<th>{% trans 'Board' %}</th>
<td>
{% if ban.board %}
{{ config.board_abbreviation|sprintf(ban.board) }}
{% else %}
<em>{% trans 'all boards' %}</em>
{% endif %}
</td>
</tr>
<tr>
<th>{% trans 'Set' %}</th>
<td>{{ ban.set|date(config.post_date) }}</td>
</tr>
<tr>
<th>{% trans 'Expires' %}</th>
<td>
{% if ban.expires %}
{{ ban.expires|date(config.post_date) }}
{% else %}
<em>{% trans 'never' %}</em>
{% endif %}
</td>
</tr>
<tr>
<th>{% trans 'Staff' %}</th>
<td>
{% if ban.username %}
{{ ban.username|e }}
{% else %}
<em>{% trans 'deleted?' %}</em>
{% endif %}
</td>
</tr>
</table>
<input type="hidden" name="ban_id" value="{{ ban.id }}">
<input type="submit" name="unban" value="{% trans 'Remove ban' %}">
</form>
{% endfor %}
</fieldset>
{% endif %}
{% if mod|hasPermission(config.mod.ban) %}
<fieldset>
<legend>{% trans 'New ban' %}</legend>
{% set redirect = '?/IP/' ~ ip ~ '#bans' %}
{% include 'mod/ban_form.html' %}
</fieldset>
{% endif %}

View File

@ -1,11 +1,11 @@
<!doctype html> <!doctype html>
<html> <html>
<head> <head>
<meta charset="utf-8">
<link rel="stylesheet" media="screen" href="{{ config.url_stylesheet }}"> <link rel="stylesheet" media="screen" href="{{ config.url_stylesheet }}">
{% if config.url_favicon %}<link rel="shortcut icon" href="{{ config.url_favicon }}">{% endif %} {% if config.url_favicon %}<link rel="shortcut icon" href="{{ config.url_favicon }}">{% endif %}
<title>{{ title }}</title> <title>{{ title }}</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=yes">
{% if config.default_stylesheet.1 != '' %}<link rel="stylesheet" type="text/css" id="stylesheet" href="{{ config.uri_stylesheets }}{{ config.default_stylesheet.1 }}">{% endif %} {% if config.default_stylesheet.1 != '' %}<link rel="stylesheet" type="text/css" id="stylesheet" href="{{ config.uri_stylesheets }}{{ config.default_stylesheet.1 }}">{% endif %}
{% if not nojavascript %}<script type="text/javascript" src="{{ config.url_javascript }}"></script>{% endif %} {% if not nojavascript %}<script type="text/javascript" src="{{ config.url_javascript }}"></script>{% endif %}
</head> </head>
@ -17,7 +17,7 @@
{% if subtitle %} {% if subtitle %}
{{ subtitle }} {{ subtitle }}
{% endif %} {% endif %}
{% if mod %}<p><a href="?/">{% trans %}Return to dashboard{% endtrans %}</a></p>{% endif %} {% if mod and not hide_dashboard_link %}<p><a href="?/">{% trans %}Return to dashboard{% endtrans %}</a></p>{% endif %}
</div> </div>
</header> </header>
{{ body }} {{ body }}

View File

@ -31,7 +31,7 @@
</legend> </legend>
{% for board in boards %} {% for board in boards %}
<li> <li>
<a href="{{ board.uri }}">{{ board.title }}</a> <a href="{{ board.uri }}">{{ board.title|e }}</a>
</li> </li>
{% endfor %} {% endfor %}
</fieldset> </fieldset>

View File

@ -30,7 +30,7 @@
{% for board in boards %} {% for board in boards %}
<li> <li>
<a href="{{ config.board_path|sprintf(board.uri) }}"> <a href="{{ config.board_path|sprintf(board.uri) }}">
{{ board.title }} {{ board.title|e }}
</a> </a>
</li> </li>
{% endfor %} {% endfor %}

View File

@ -163,11 +163,15 @@
$c++; $c++;
$cc = 0; $cc = 0;
} }
if($c>4) $c = 0; if ($c > 4)
$c = 0;
if($red>3) $red = 0; if ($red > 3)
if($green>3) $green = 0; $red = 0;
if($blue>3) $blue = 0; if ($green > 3)
$green = 0;
if ($blue > 3)
$blue = 0;
} }
$options[] = 'HRULE:0#000000'; $options[] = 'HRULE:0#000000';

View File

@ -1,11 +1,11 @@
<!doctype html> <!doctype html>
<html> <html>
<head> <head>
<meta charset="utf-8">
<link rel="stylesheet" media="screen" href="{{ config.url_stylesheet }}"> <link rel="stylesheet" media="screen" href="{{ config.url_stylesheet }}">
{% if config.url_favicon %}<link rel="shortcut icon" href="{{ config.url_favicon }}">{% endif %} {% if config.url_favicon %}<link rel="shortcut icon" href="{{ config.url_favicon }}">{% endif %}
<title>{{ board.url }} - {{ board.name }}</title> <title>{{ board.url }} - {{ board.name }}</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=yes">
{% if config.meta_keywords %}<meta name="keywords" content="{{ config.meta_keywords }}">{% endif %} {% if config.meta_keywords %}<meta name="keywords" content="{{ config.meta_keywords }}">{% endif %}
{% if config.default_stylesheet.1 != '' %}<link rel="stylesheet" type="text/css" id="stylesheet" href="{{ config.uri_stylesheets }}{{ config.default_stylesheet.1 }}">{% endif %} {% if config.default_stylesheet.1 != '' %}<link rel="stylesheet" type="text/css" id="stylesheet" href="{{ config.uri_stylesheets }}{{ config.default_stylesheet.1 }}">{% endif %}
{% if not nojavascript %} {% if not nojavascript %}
@ -29,17 +29,16 @@
display: block; display: block;
} }
{% endraw %}</style>{% endif %} {% endraw %}</style>{% endif %}
</head> </head>
<body> <body>
{{ boardlist.top }} {{ 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 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 %} {% 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 %}
<header> <header>
<h1>{{ board.url }} - {{ board.name }}</h1> <h1>{{ board.url }} - {{ board.title|e }}</h1>
<div class="subtitle"> <div class="subtitle">
{% if board.title %} {% if board.subtitle %}
{{ board.title }} {{ board.subtitle|e }}
{% endif %} {% endif %}
{% if mod %}<p><a href="?/">{% trans %}Return to dashboard{% endtrans %}</a></p>{% endif %} {% if mod %}<p><a href="?/">{% trans %}Return to dashboard{% endtrans %}</a></p>{% endif %}
</div> </div>