Browse Source

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
pull/40/head
Michael Save 12 years ago
parent
commit
6a705fd8c2
  1. 7
      inc/anti-bot.php
  2. 2
      inc/cache.php
  3. 31
      inc/config.php
  4. 2
      inc/filters.php
  5. 54
      inc/functions.php
  6. 49
      inc/lib/Twig/Extensions/Extension/Tinyboard.php
  7. 287
      inc/mod-old.php
  8. 278
      inc/mod.php
  9. 152
      inc/mod/auth.php
  10. 95
      inc/mod/ban.php
  11. 75
      inc/mod/config-editor.php
  12. 1796
      inc/mod/pages.php
  13. 4
      inc/template.php
  14. 4
      install.php
  15. 3128
      mod-old.php
  16. 3194
      mod.php
  17. 30
      post.php
  18. 39
      stylesheets/style.css
  19. 71
      templates/generic_page.html
  20. 10
      templates/index.html
  21. 91
      templates/mod/ban_form.html
  22. 95
      templates/mod/ban_list.html
  23. 44
      templates/mod/board.html
  24. 56
      templates/mod/config-editor.html
  25. 7
      templates/mod/confirm.html
  26. 139
      templates/mod/dashboard.html
  27. 65
      templates/mod/debug/antispam.html
  28. 36
      templates/mod/inbox.html
  29. 47
      templates/mod/log.html
  30. 7
      templates/mod/login.html
  31. 41
      templates/mod/move.html
  32. 18
      templates/mod/new_pm.html
  33. 70
      templates/mod/news.html
  34. 65
      templates/mod/noticeboard.html
  35. 44
      templates/mod/pm.html
  36. 71
      templates/mod/rebuild.html
  37. 12
      templates/mod/rebuilt.html
  38. 26
      templates/mod/report.html
  39. 6
      templates/mod/reports.html
  40. 27
      templates/mod/theme_config.html
  41. 9
      templates/mod/theme_installed.html
  42. 2
      templates/mod/theme_rebuilt.html
  43. 40
      templates/mod/themes.html
  44. 125
      templates/mod/user.html
  45. 71
      templates/mod/users.html
  46. 163
      templates/mod/view_ip.html
  47. 6
      templates/page.html
  48. 4
      templates/themes/basic/info.php
  49. 2
      templates/themes/basic/theme.php
  50. 4
      templates/themes/categories/info.php
  51. 2
      templates/themes/categories/sidebar.html
  52. 12
      templates/themes/categories/theme.php
  53. 2
      templates/themes/frameset/sidebar.html
  54. 6
      templates/themes/frameset/theme.php
  55. 6
      templates/themes/recent/info.php
  56. 28
      templates/themes/recent/theme.php
  57. 16
      templates/themes/rrdtool/info.php
  58. 54
      templates/themes/rrdtool/theme.php
  59. 11
      templates/thread.html

7
inc/anti-bot.php

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

2
inc/cache.php

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

31
inc/config.php

@ -38,7 +38,6 @@
$config['check_updates_time'] = 43200; // 12 hours
// Shows some extra information at the bottom of pages. Good for debugging development.
// Also experimental.
$config['debug'] = false;
// For development purposes. Turns 'display_errors' on. Not recommended for production.
$config['verbose_errors'] = true;
@ -362,7 +361,7 @@
$config['markup'][] = array("/'''(.+?)'''/", "<strong>\$1</strong>");
$config['markup'][] = array("/''(.+?)''/", "<em>\$1</em>");
$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+)
// $config['markup'][] = array(
@ -816,8 +815,6 @@
// Do a DNS lookup on IP addresses to get their hostname on the IP summary page
$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
$config['mod']['ip_recentposts'] = 5;
@ -826,12 +823,17 @@
// How many actions to show per page in the moderation log
$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
$config['mod']['search_results'] = 75;
// Maximum number of notices to display on the moderator noticeboard
$config['mod']['noticeboard_display'] = 50;
// How many entries to show per page in the moderator noticeboard
$config['mod']['noticeboard_page'] = 50;
// Number of entries to summarize and display on the dashboard
$config['mod']['noticeboard_dashboard'] = 5;
@ -867,7 +869,20 @@
* Mod permissions
* ====================
*/
// 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.
// Don't worry about per-board moderators. Let all mods moderate any board.
@ -1043,6 +1058,4 @@
// 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\-~.#\/?=&;:+%!*\[\]@$\'()+,|\^]+)?)?' . '/';
// 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}))|:)))(%.+)?';

2
inc/filters.php

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

54
inc/functions.php

@ -32,7 +32,30 @@ function loadConfig() {
if (!isset($_SERVER['REMOTE_ADDR']))
$_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();
foreach ($arrays as $key) {
@ -277,10 +300,13 @@ function setupBoard($array) {
$board = array(
'uri' => $array['uri'],
'name' => $array['title'],
'title' => $array['subtitle']
'title' => $array['title'],
'subtitle' => $array['subtitle']
);
// older versions
$board['name'] = &$board['title'];
$board['dir'] = sprintf($config['board_path'], $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(':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);
} else {
$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);
} else {
$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->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
if ($query->rowCount() < 1) {
if (!$post = $query->fetch())
error($config['error']['invalidpost']);
}
$post = $query->fetch();
if ($post['file'] == 'deleted' && !$post['thread'])
return; // Can't delete OP's image completely.
@ -801,13 +823,14 @@ function deleteFile($id, $remove_entirely_if_already=true) {
// Set file to 'deleted'
$query->bindValue(':file', 'deleted', PDO::PARAM_INT);
}
// Update database
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
if ($post['thread'])
buildThread($post['thread']);
else
buildThread($id);
}
// rebuild post (markup)
@ -1179,6 +1202,7 @@ function buildIndex() {
global $board, $config;
$pages = getPages();
$antibot = create_antibot($board['uri']);
$page = 1;
while ($page <= $config['max_pages'] && $content = index($page)) {
@ -1188,7 +1212,7 @@ function buildIndex() {
$content['pages'] = $pages;
$content['pages'][$page-1]['selected'] = true;
$content['btn'] = getPageButtons($content['pages']);
$content['antibot'] = create_antibot($board['uri']);
$content['antibot'] = $antibot;
file_write($filename, Element('index.html', $content));
if (isset($md5) && $md5 == md5_file($filename)) {
@ -1370,8 +1394,10 @@ function markup(&$body, $track_cites = false) {
if ($config['auto_unicode']) {
$body = unicodify($body);
foreach ($markup_urls as &$url) {
$body = str_replace(unicodify($url), $url, $body);
if ($config['markup_urls']) {
foreach ($markup_urls as &$url) {
$body = str_replace(unicodify($url), $url, $body);
}
}
}

49
inc/lib/Twig/Extensions/Extension/Tinyboard.php

@ -10,19 +10,22 @@ class Twig_Extensions_Extension_Tinyboard extends Twig_Extension
public function getFilters()
{
return Array(
'filesize' => new Twig_Filter_Function('format_bytes', Array('needs_environment' => false)),
'truncate' => new Twig_Filter_Function('twig_truncate_filter', array('needs_environment' => false)),
'truncate_body' => new Twig_Filter_Function('truncate', array('needs_environment' => false)),
'extension' => new Twig_Filter_Function('twig_extension_filter', array('needs_environment' => false)),
'sprintf' => new Twig_Filter_Function('sprintf', array('needs_environment' => false)),
'capcode' => new Twig_Filter_Function('capcode', array('needs_environment' => false)),
'hasPermission' => new Twig_Filter_Function('twig_hasPermission_filter', array('needs_environment' => false)),
'date' => new Twig_Filter_Function('twig_date_filter', array('needs_environment' => false)),
'poster_id' => new Twig_Filter_Function('poster_id', array('needs_environment' => false)),
'remove_whitespace' => new Twig_Filter_Function('twig_remove_whitespace_filter', array('needs_environment' => false)),
'count' => new Twig_Filter_Function('count', array('needs_environment' => false)),
'until' => new Twig_Filter_Function('until', array('needs_environment' => false)),
'addslashes' => new Twig_Filter_Function('addslashes', array('needs_environment' => false)),
'filesize' => new Twig_Filter_Function('format_bytes'),
'truncate' => new Twig_Filter_Function('twig_truncate_filter'),
'truncate_body' => new Twig_Filter_Function('truncate'),
'extension' => new Twig_Filter_Function('twig_extension_filter'),
'sprintf' => new Twig_Filter_Function('sprintf'),
'capcode' => new Twig_Filter_Function('capcode'),
'hasPermission' => new Twig_Filter_Function('twig_hasPermission_filter'),
'date' => new Twig_Filter_Function('twig_date_filter'),
'poster_id' => new Twig_Filter_Function('poster_id'),
'remove_whitespace' => new Twig_Filter_Function('twig_remove_whitespace_filter'),
'count' => new Twig_Filter_Function('count'),
'ago' => new Twig_Filter_Function('ago'),
'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()
{
return Array(
'time' => new Twig_Filter_Function('time', array('needs_environment' => false)),
'timezone' => new Twig_Filter_Function('twig_timezone_function', array('needs_environment' => false)),
'hiddenInputs' => new Twig_Filter_Function('hiddenInputs', array('needs_environment' => false)),
'hiddenInputsHash' => new Twig_Filter_Function('hiddenInputsHash', array('needs_environment' => false))
'time' => new Twig_Filter_Function('time'),
'floor' => new Twig_Filter_Function('floor'),
'timezone' => new Twig_Filter_Function('twig_timezone_function'),
'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);
}
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) {
return preg_replace('/[\t\r\n]/', '', $data);
}
@ -65,7 +78,7 @@ function twig_date_filter($date, $format) {
return strftime($format, $date);
}
function twig_hasPermission_filter($mod, $permission, $board) {
function twig_hasPermission_filter($mod, $permission, $board = null) {
return hasPermission($permission, $board, $mod);
}

287
inc/mod-old.php

@ -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'])
);
}

278
inc/mod.php

@ -4,284 +4,12 @@
* 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__)) {
// 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'])
);
}
require 'inc/mod/auth.php';

152
inc/mod/auth.php

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

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

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

File diff suppressed because it is too large

4
inc/template.php

@ -27,7 +27,7 @@ function load_twig() {
$twig = new Twig_Environment($loader, array(
'autoescape' => false,
'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_I18n());
@ -39,7 +39,7 @@ function Element($templateFile, array $options) {
if (!$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();
}

4
install.php

@ -1,7 +1,7 @@
<?php
// Installation/upgrade file
define('VERSION', 'v0.9.6-dev-4');
define('VERSION', 'v0.9.6-dev-5');
require 'inc/functions.php';
@ -210,6 +210,8 @@ if (file_exists($config['has_installed'])) {
}
case 'v0.9.6-dev-3':
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:
// Update version number
file_write($config['has_installed'], VERSION);

3128
mod-old.php

File diff suppressed because it is too large

3194
mod.php

File diff suppressed because it is too large

30
post.php

@ -281,7 +281,7 @@ if (isset($_POST['delete'])) {
$post['name'] = $_POST['name'] != '' ? $_POST['name'] : $config['anonymous'];
$post['subject'] = $_POST['subject'];
$post['email'] = utf8tohtml($_POST['email']);
$post['email'] = str_replace(' ', '%20', htmlspecialchars($_POST['email']));
$post['body'] = $_POST['body'];
$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'] != ''));
@ -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'];
$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;
}
}
} else {
$post['capcode'] = false;
}
$trip = generate_tripcode($post['name']);
@ -527,7 +537,11 @@ if (isset($_POST['delete'])) {
}
$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'])) {
incrementSpamHash($post['antispam_hash']);

39
stylesheets/style.css

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

71
templates/generic_page.html

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

10
templates/index.html

@ -1,11 +1,11 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<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">
<meta name="viewport" content="width=device-width, initial-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 %}
@ -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 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>
<h1>{{ board.url }} - {{ board.title|e }}</h1>
<div class="subtitle">
{% if board.title %}
{{ board.title }}
{% if board.subtitle %}
{{ board.subtitle|e }}
{% endif %}
{% if mod %}<p><a href="?/">{% trans %}Return to dashboard{% endtrans %}</a></p>{% endif %}
</div>

91
templates/mod/ban_form.html

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

95
templates/mod/ban_list.html

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

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

56
templates/mod/config-editor.html

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

7
templates/mod/confirm.html

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

139
templates/mod/dashboard.html

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

65
templates/mod/debug/antispam.html

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

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

@ -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 %}

7
templates/login.html → templates/mod/login.html

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

41
templates/mod/move.html

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

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

@ -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 %}

65
templates/mod/noticeboard.html

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

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

71
templates/mod/rebuild.html

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

12
templates/mod/rebuilt.html

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

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

6
templates/mod/reports.html

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

27
templates/mod/theme_config.html

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

9
templates/mod/theme_installed.html

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

2
templates/mod/theme_rebuilt.html

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

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

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

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

@ -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 %}

6
templates/page.html

@ -1,11 +1,11 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" media="screen" href="{{ config.url_stylesheet }}">
{% if config.url_favicon %}<link rel="shortcut icon" href="{{ config.url_favicon }}">{% endif %}
<title>{{ title }}</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">
<meta name="viewport" content="width=device-width, initial-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 not nojavascript %}<script type="text/javascript" src="{{ config.url_javascript }}"></script>{% endif %}
</head>
@ -17,7 +17,7 @@
{% if subtitle %}
{{ subtitle }}
{% 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>
</header>
{{ body }}

4
templates/themes/basic/info.php

@ -44,9 +44,9 @@
$theme['build_function'] = 'basic_build';
$theme['install_callback'] = 'build_install';
if(!function_exists('build_install')) {
if (!function_exists('build_install')) {
function build_install($settings) {
if(!is_numeric($settings['no_recent']) || $settings['no_recent'] < 0)
if (!is_numeric($settings['no_recent']) || $settings['no_recent'] < 0)
return Array(false, '<strong>' . utf8tohtml($settings['no_recent']) . '</strong> is not a non-negative integer.');
}
}

2
templates/themes/basic/theme.php

@ -15,7 +15,7 @@
public static function build($action, $settings) {
global $config;
if($action == 'all' || $action == 'news')
if ($action == 'all' || $action == 'news')
file_write($config['dir']['home'] . $settings['file'], Basic::homepage($settings));
}

4
templates/themes/categories/info.php

@ -54,11 +54,11 @@ Requires $config[\'categories\'].';
$theme['build_function'] = 'categories_build';
$theme['install_callback'] = 'categories_install';
if(!function_exists('categories_install')) {
if (!function_exists('categories_install')) {
function categories_install($settings) {
global $config;
if(!isset($config['categories'])) {
if (!isset($config['categories'])) {
return Array(false, '<h2>Prerequisites not met!</h2>' .
'This theme requires $config[\'boards\'] and $config[\'categories\'] to be set.');
}

2
templates/themes/categories/sidebar.html

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

12
templates/themes/categories/theme.php

@ -15,13 +15,13 @@
public static function build($action, $settings) {
global $config;
if($action == 'all')
if ($action == 'all')
file_write($config['dir']['home'] . $settings['file_main'], Categories::homepage($settings));
if($action == 'all' || $action == 'boards')
if ($action == 'all' || $action == 'boards')
file_write($config['dir']['home'] . $settings['file_sidebar'], Categories::sidebar($settings));
if($action == 'all' || $action == 'news')
if ($action == 'all' || $action == 'news')
file_write($config['dir']['home'] . $settings['file_news'], Categories::news($settings));
}
@ -52,10 +52,10 @@
$categories = $config['categories'];
foreach($categories as &$boards) {
foreach($boards as &$board) {
foreach ($categories as &$boards) {
foreach ($boards as &$board) {
$title = boardTitle($board);
if(!$title)
if (!$title)
$title = $board; // board doesn't exist, but for some reason you want to display it anyway
$board = Array('title' => $title, 'uri' => sprintf($config['board_path'], $board));
}

2
templates/themes/frameset/sidebar.html

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

6
templates/themes/frameset/theme.php

@ -15,13 +15,13 @@
public static function build($action, $settings) {
global $config;
if($action == 'all')
if ($action == 'all')
file_write($config['dir']['home'] . $settings['file_main'], Frameset::homepage($settings));
if($action == 'all' || $action == 'boards')
if ($action == 'all' || $action == 'boards')
file_write($config['dir']['home'] . $settings['file_sidebar'], Frameset::sidebar($settings));
if($action == 'all' || $action == 'news')
if ($action == 'all' || $action == 'news')
file_write($config['dir']['home'] . $settings['file_news'], Frameset::news($settings));
}

6
templates/themes/recent/info.php

@ -60,11 +60,11 @@
$theme['build_function'] = 'recentposts_build';
$theme['install_callback'] = 'recentposts_install';
if(!function_exists('recentposts_install')) {
if (!function_exists('recentposts_install')) {
function recentposts_install($settings) {
if(!is_numeric($settings['limit_images']) || $settings['limit_images'] < 0)
if (!is_numeric($settings['limit_images']) || $settings['limit_images'] < 0)
return Array(false, '<strong>' . utf8tohtml($settings['limit_images']) . '</strong> is not a non-negative integer.');
if(!is_numeric($settings['limit_posts']) || $settings['limit_posts'] < 0)
if (!is_numeric($settings['limit_posts']) || $settings['limit_posts'] < 0)
return Array(false, '<strong>' . utf8tohtml($settings['limit_posts']) . '</strong> is not a non-negative integer.');
}
}

28
templates/themes/recent/theme.php

@ -17,13 +17,13 @@
public function build($action, $settings) {
global $config, $_theme;
if($action == 'all') {
if ($action == 'all') {
copy('templates/themes/recent/recent.css', $config['dir']['home'] . $settings['css']);
}
$this->excluded = explode(' ', $settings['exclude']);
if($action == 'all' || $action == 'post')
if ($action == 'all' || $action == 'post')
file_write($config['dir']['home'] . $settings['html'], $this->homepage($settings));
}
@ -38,15 +38,15 @@
$boards = listBoards();
$query = '';
foreach($boards as &$_board) {
if(in_array($_board['uri'], $this->excluded))
foreach ($boards as &$_board) {
if (in_array($_board['uri'], $this->excluded))
continue;
$query .= sprintf("SELECT *, '%s' AS `board` FROM `posts_%s` WHERE `file` IS NOT NULL AND `file` != 'deleted' AND `thumb` != 'spoiler' UNION ALL ", $_board['uri'], $_board['uri']);
}
$query = preg_replace('/UNION ALL $/', 'ORDER BY `time` DESC LIMIT ' . (int)$settings['limit_images'], $query);
$query = query($query) or error(db_error());
while($post = $query->fetch()) {
while ($post = $query->fetch()) {
openBoard($post['board']);
// board settings won't be available in the template file, so generate links now
@ -58,15 +58,15 @@
$query = '';
foreach($boards as &$_board) {
if(in_array($_board['uri'], $this->excluded))
foreach ($boards as &$_board) {
if (in_array($_board['uri'], $this->excluded))
continue;
$query .= sprintf("SELECT *, '%s' AS `board` FROM `posts_%s` UNION ALL ", $_board['uri'], $_board['uri']);
}
$query = preg_replace('/UNION ALL $/', 'ORDER BY `time` DESC LIMIT ' . (int)$settings['limit_posts'], $query);
$query = query($query) or error(db_error());
while($post = $query->fetch()) {
while ($post = $query->fetch()) {
openBoard($post['board']);
$post['link'] = $config['root'] . $board['dir'] . $config['dir']['res'] . sprintf($config['file_page'], ($post['thread'] ? $post['thread'] : $post['id'])) . '#' . $post['id'];
@ -78,8 +78,8 @@
// Total posts
$query = 'SELECT SUM(`top`) AS `count` FROM (';
foreach($boards as &$_board) {
if(in_array($_board['uri'], $this->excluded))
foreach ($boards as &$_board) {
if (in_array($_board['uri'], $this->excluded))
continue;
$query .= sprintf("SELECT MAX(`id`) AS `top` FROM `posts_%s` UNION ALL ", $_board['uri']);
}
@ -90,8 +90,8 @@
// Unique IPs
$query = 'SELECT COUNT(DISTINCT(`ip`)) AS `count` FROM (';
foreach($boards as &$_board) {
if(in_array($_board['uri'], $this->excluded))
foreach ($boards as &$_board) {
if (in_array($_board['uri'], $this->excluded))
continue;
$query .= sprintf("SELECT `ip` FROM `posts_%s` UNION ALL ", $_board['uri']);
}
@ -102,8 +102,8 @@
// Active content
$query = 'SELECT SUM(`filesize`) AS `count` FROM (';
foreach($boards as &$_board) {
if(in_array($_board['uri'], $this->excluded))
foreach ($boards as &$_board) {
if (in_array($_board['uri'], $this->excluded))
continue;
$query .= sprintf("SELECT `filesize` FROM `posts_%s` UNION ALL ", $_board['uri']);
}

16
templates/themes/rrdtool/info.php

@ -28,7 +28,7 @@
$__boards = listBoards();
$__default_boards = Array();
foreach($__boards as $__board)
foreach ($__boards as $__board)
$__default_boards[] = $__board['uri'];
$theme['config'][] = Array(
@ -83,31 +83,31 @@
);
$theme['install_callback'] = 'rrdtool_install';
if(!function_exists('rrdtool_install')) {
if (!function_exists('rrdtool_install')) {
function rrdtool_install($settings) {
global $config;
if(!is_numeric($settings['interval']) || $settings['interval'] < 1 || $settings['interval'] > 86400)
if (!is_numeric($settings['interval']) || $settings['interval'] < 1 || $settings['interval'] > 86400)
return Array(false, 'Invalid interval: <strong>' . $settings['interval'] . '</strong>. Must be an integer greater than 1 and less than 86400.');
if(!is_numeric($settings['width']) || $settings['width'] < 1)
if (!is_numeric($settings['width']) || $settings['width'] < 1)
return Array(false, 'Invalid width: <strong>' . $settings['width'] . '</strong>!');
if(!is_numeric($settings['height']) || $settings['height'] < 1)
if (!is_numeric($settings['height']) || $settings['height'] < 1)
return Array(false, 'Invalid height: <strong>' . $settings['height'] . '</strong>!');
if(!in_array($settings['rate'], Array('second', 'minute', 'day', 'hour', 'week', 'month', 'year')))
if (!in_array($settings['rate'], Array('second', 'minute', 'day', 'hour', 'week', 'month', 'year')))
return Array(false, 'Invalid rate: <strong>' . $settings['rate'] . '</strong>!');
$job = '*/' . $settings['interval'] . ' * * * * php -q ' . str_replace('\\', '/', dirname(__FILE__)) . '/cron.php' . PHP_EOL;
if(function_exists('system')) {
if (function_exists('system')) {
$crontab = tempnam($config['tmp'], 'tinyboard-rrdtool');
file_write($crontab, $job);
@system('crontab ' . escapeshellarg($crontab), $ret);
unlink($crontab);
if($ret === 0)
if ($ret === 0)
return ''; // it seems to install okay?
}

54
templates/themes/rrdtool/theme.php

@ -17,7 +17,7 @@
public function build($action, $settings) {
global $config, $_theme, $argv;
if(!$settings) {
if (!$settings) {
error('This theme is not currently installed.');
}
@ -26,18 +26,18 @@
// exclude boards from the "combined" graph
$this->combined_exclude = Array();
if($action == 'cron') {
if(!file_exists($settings['path']))
if ($action == 'cron') {
if (!file_exists($settings['path']))
mkdir($settings['path']);
if(!file_exists($settings['images']))
if (!file_exists($settings['images']))
mkdir($settings['images']);
foreach($this->boards as &$board) {
foreach ($this->boards as &$board) {
$file = $settings['path'] . '/' . $board . '.rrd';
if(!file_exists($file)) {
if (!file_exists($file)) {
// Create graph
if(!rrd_create($file, Array(
if (!rrd_create($file, Array(
'-s 60',
'DS:posts:COUNTER:86400:0:10000',
@ -59,22 +59,22 @@
}
// debug just the graphing (not updating) with the --debug switch
if(!isset($argv[1]) || $argv[1] != '--debug') {
if (!isset($argv[1]) || $argv[1] != '--debug') {
// Update graph
$query = query(sprintf("SELECT MAX(`id`) AS `count` FROM `posts_%s`", $board));
$count = $query->fetch();
$count = $count['count'];
if(!rrd_update($file, Array(
if (!rrd_update($file, Array(
'-t',
'posts',
'N:' . $count)))
error('RRDtool failed: ' . htmlentities(rrd_error()));
}
foreach($this->spans as &$span) {
foreach ($this->spans as &$span) {
// Graph graph
if(!rrd_graph($settings['images'] . '/' . $board . '-' . $span . '.png', Array(
if (!rrd_graph($settings['images'] . '/' . $board . '-' . $span . '.png', Array(
'-s -1' . $span,
'-t Posts on ' . sprintf($config['board_abbreviation'], $board) .' this ' . $span,
'--lazy',
@ -105,7 +105,7 @@
}
// combined graph
foreach($this->spans as &$span) {
foreach ($this->spans as &$span) {
$options = Array(
'-s -1' . $span,
'-t Posts this ' . $span,
@ -129,8 +129,8 @@
$c = 1;
$cc = 0;
$red = 2;
foreach($this->boards as &$board) {
if(in_array($board, $this->combined_exclude))
foreach ($this->boards as &$board) {
if (in_array($board, $this->combined_exclude))
continue;
$color = str_pad(dechex($red*85), 2, '0', STR_PAD_LEFT) .
str_pad(dechex($green*85), 2, '0', STR_PAD_LEFT) .
@ -147,31 +147,35 @@
sprintf($config['board_abbreviation'], $board);
// Randomize colors using this horrible undocumented algorithm I threw together while debugging
if($c == 0)
if ($c == 0)
$red++;
elseif($c == 1)
elseif ($c == 1)
$green++;
elseif($c == 2)
elseif ($c == 2)
$blue++;
elseif($c == 3)
elseif ($c == 3)
$green--;
elseif($c == 4)
elseif ($c == 4)
$red--;
$cc++;
if($cc > 2) {
if ($cc > 2) {
$c++;
$cc = 0;
}
if($c>4) $c = 0;
if ($c > 4)
$c = 0;
if($red>3) $red = 0;
if($green>3) $green = 0;
if($blue>3) $blue = 0;
if ($red > 3)
$red = 0;
if ($green > 3)
$green = 0;
if ($blue > 3)
$blue = 0;
}
$options[] = 'HRULE:0#000000';
if(!rrd_graph($settings['images'] . '/combined-' . $span . '.png', $options))
if (!rrd_graph($settings['images'] . '/combined-' . $span . '.png', $options))
error('RRDtool failed: ' . htmlentities(rrd_error()));
}
}

11
templates/thread.html

@ -1,11 +1,11 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<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">
<meta name="viewport" content="width=device-width, initial-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 %}
@ -29,17 +29,16 @@
display: block;
}
{% endraw %}</style>{% endif %}
</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>
<h1>{{ board.url }} - {{ board.title|e }}</h1>
<div class="subtitle">
{% if board.title %}
{{ board.title }}
{% if board.subtitle %}
{{ board.subtitle|e }}
{% endif %}
{% if mod %}<p><a href="?/">{% trans %}Return to dashboard{% endtrans %}</a></p>{% endif %}
</div>

Loading…
Cancel
Save