diff --git a/inc/config.php b/inc/config.php index 4a413fd6..e5715cae 100644 --- a/inc/config.php +++ b/inc/config.php @@ -326,6 +326,11 @@ // Reply limit (stops bumping thread when this is reached) $config['reply_limit'] = 250; + // Image hard limit (stops allowing new image replies when this is reached if not zero) + $config['image_hard_limit'] = 0; + // Reply hard limit (stops allowing new replies when this is reached if not zero) + $config['reply_hard_limit'] = 0; + // Strip repeating characters when making hashes $config['robot_enable'] = false; $config['robot_strip_repeating'] = true; @@ -696,6 +701,8 @@ $config['error']['noboard'] = _('Invalid board!'); $config['error']['nonexistant'] = _('Thread specified does not exist.'); $config['error']['locked'] = _('Thread locked. You may not reply at this time.'); + $config['error']['reply_hard_limit'] = _('Thread has reached its maximum reply limit.'); + $config['error']['image_hard_limit'] = _('Thread has reached its maximum image limit.'); $config['error']['nopost'] = _('You didn\'t make a post.'); $config['error']['flood'] = _('Flood detected; Post discarded.'); $config['error']['spam'] = _('Your request looks automated; Post discarded.'); @@ -723,6 +730,7 @@ $config['error']['captcha'] = _('You seem to have mistyped the verification.'); // Moderator errors + $config['error']['toomanyunban'] = _('You are only allowed to unban %s users at a time. You tried to unban %u users.'); $config['error']['invalid'] = _('Invalid username and/or password.'); $config['error']['notamod'] = _('You are not a mod…'); $config['error']['invalidafter'] = _('Invalid username and/or password. Your user may have been deleted or changed.'); @@ -810,6 +818,9 @@ * Mod settings * ==================== */ + + // Limit how many bans can be removed via the ban list. (Set too -1 to remove limit.) + $config['mod']['unban_limit'] = 5; // Whether or not to lock moderator sessions to the IP address that was logged in with. $config['mod']['lock_ip'] = true; diff --git a/inc/display.php b/inc/display.php index 9be772e4..30560d3a 100644 --- a/inc/display.php +++ b/inc/display.php @@ -118,7 +118,7 @@ function pm_snippet($body, $len=null) { // calculate strlen() so we can add "..." after if needed $strlen = mb_strlen($body); - $body = substr($body, 0, $len); + $body = mb_substr($body, 0, $len); // Re-escape the characters. return '' . utf8tohtml($body) . ($strlen > $len ? '…' : '') . ''; @@ -204,7 +204,7 @@ function truncate($body, $url, $max_lines = false, $max_chars = false) { } } else { // remove broken HTML entity at the end (if existent) - $body = preg_replace('/&[^;]+$/', '', $body); + $body = preg_replace('/&[^;]*$/', '', $body); } $body .= 'Post too long. Click here to view the full text.'; @@ -213,6 +213,39 @@ function truncate($body, $url, $max_lines = false, $max_chars = false) { return $body; } +function bidi_cleanup($str){ + # Closes all embedded RTL and LTR unicode formatting blocks in a string so that + # it can be used inside another without controlling its direction. + # More info: http://www.iamcal.com/understanding-bidirectional-text/ + # + # LRE - U+202A - 0xE2 0x80 0xAA + # RLE - U+202B - 0xE2 0x80 0xAB + # LRO - U+202D - 0xE2 0x80 0xAD + # RLO - U+202E - 0xE2 0x80 0xAE + # + # PDF - U+202C - 0xE2 0x80 0xAC + # + $explicits = '\xE2\x80\xAA|\xE2\x80\xAB|\xE2\x80\xAD|\xE2\x80\xAE'; + $pdf = '\xE2\x80\xAC'; + + $stack = 0; + $str = preg_replace_callback("!(?$explicits)|(?$pdf)!", function($match) use (&$stack) { + if (isset($match['explicits']) && $match['explicits']) { + $stack++; + } else { + if ($stack) + $stack--; + else + return ''; + } + return $match[0]; + }, $str); + for ($i=0; $i<$stack; $i++){ + $str .= "\xE2\x80\xAC"; + } + return $str; +} + function secure_link_confirm($text, $title, $confirm_message, $href) { global $config; diff --git a/inc/functions.php b/inc/functions.php index 6e98283d..b3471469 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -328,11 +328,19 @@ function setupBoard($array) { } function openBoard($uri) { + $board = getBoardInfo($uri); + if ($board) { + setupBoard($board); + return true; + } + return false; +} + +function getBoardInfo($uri) { global $config; if ($config['cache']['enabled'] && ($board = cache::get('board_' . $uri))) { - setupBoard($board); - return true; + return $board; } $query = prepare("SELECT * FROM `boards` WHERE `uri` = :uri LIMIT 1"); @@ -342,27 +350,16 @@ function openBoard($uri) { if ($board = $query->fetch()) { if ($config['cache']['enabled']) cache::set('board_' . $uri, $board); - setupBoard($board); - return true; + return $board; } return false; } function boardTitle($uri) { - global $config; - if ($config['cache']['enabled'] && ($board = cache::get('board_' . $uri))) { + $board = getBoardInfo($uri); + if ($board) return $board['title']; - } - - $query = prepare("SELECT `title` FROM `boards` WHERE `uri` = :uri LIMIT 1"); - $query->bindValue(':uri', $uri); - $query->execute() or error(db_error($query)); - - if ($title = $query->fetch()) { - return $title['title']; - } - return false; } @@ -725,13 +722,13 @@ function post(array $post) { $query->bindValue(':password', $post['password']); $query->bindValue(':ip', isset($post['ip']) ? $post['ip'] : $_SERVER['REMOTE_ADDR']); - if ($post['op'] && $post['mod'] && $post['sticky']) { + if ($post['op'] && $post['mod'] && isset($post['sticky']) && $post['sticky']) { $query->bindValue(':sticky', 1, PDO::PARAM_INT); } else { $query->bindValue(':sticky', 0, PDO::PARAM_INT); } - if ($post['op'] && $post['mod'] && $post['locked']) { + if ($post['op'] && $post['mod'] && isset($post['locked']) && $post['locked']) { $query->bindValue(':locked', 1, PDO::PARAM_INT); } else { $query->bindValue(':locked', 0, PDO::PARAM_INT); @@ -986,12 +983,8 @@ function index($page, $mod=false) { $replies = array_reverse($posts->fetchAll(PDO::FETCH_ASSOC)); if (count($replies) == ($th['sticky'] ? $config['threads_preview_sticky'] : $config['threads_preview'])) { - $count = prepare(sprintf("SELECT COUNT(`id`) as `num` FROM `posts_%s` WHERE `thread` = :thread UNION ALL SELECT COUNT(`id`) FROM `posts_%s` WHERE `file` IS NOT NULL AND `thread` = :thread", $board['uri'], $board['uri'])); - $count->bindValue(':thread', $th['id'], PDO::PARAM_INT); - $count->execute() or error(db_error($count)); - $count = $count->fetchAll(PDO::FETCH_COLUMN); - - $omitted = array('post_count' => $count[0], 'image_count' => $count[1]); + $count = numPosts($th['id']); + $omitted = array('post_count' => $count['replies'], 'image_count' => $count['images']); } else { $omitted = false; } @@ -1134,14 +1127,19 @@ function checkRobot($body) { return false; } +// Returns an associative array with 'replies' and 'images' keys function numPosts($id) { global $board; - $query = prepare(sprintf("SELECT COUNT(*) as `count` FROM `posts_%s` WHERE `thread` = :thread", $board['uri'])); + $query = prepare(sprintf("SELECT COUNT(*) as `num` FROM `posts_%s` WHERE `thread` = :thread UNION ALL SELECT COUNT(*) FROM `posts_%s` WHERE `file` IS NOT NULL AND `thread` = :thread", $board['uri'], $board['uri'])); $query->bindValue(':thread', $id, PDO::PARAM_INT); $query->execute() or error(db_error($query)); - $result = $query->fetch(); - return $result['count']; + $num_posts = $query->fetch(); + $num_posts = $num_posts['num']; + $num_images = $query->fetch(); + $num_images = $num_images['num']; + + return array('replies' => $num_posts, 'images' => $num_images); } function muteTime() { @@ -1365,8 +1363,8 @@ function unicodify($body) { // En and em- dashes are rendered exactly the same in // most monospace fonts (they look the same in code // editors). - $body = str_replace('--', '–', $body); // en dash $body = str_replace('---', '—', $body); // em dash + $body = str_replace('--', '–', $body); // en dash return $body; } diff --git a/inc/lib/Twig/Extensions/Extension/Tinyboard.php b/inc/lib/Twig/Extensions/Extension/Tinyboard.php index 0a128e7b..61cf9fc9 100644 --- a/inc/lib/Twig/Extensions/Extension/Tinyboard.php +++ b/inc/lib/Twig/Extensions/Extension/Tinyboard.php @@ -25,6 +25,7 @@ class Twig_Extensions_Extension_Tinyboard extends Twig_Extension 'until' => new Twig_Filter_Function('until'), 'split' => new Twig_Filter_Function('twig_split_filter'), 'push' => new Twig_Filter_Function('twig_push_filter'), + 'bidi_cleanup' => new Twig_Filter_Function('bidi_cleanup'), 'addslashes' => new Twig_Filter_Function('addslashes') ); } @@ -57,8 +58,7 @@ class Twig_Extensions_Extension_Tinyboard extends Twig_Extension } function twig_timezone_function() { - // there's probably a much easier way of doing this - return sprintf("%s%02d", ($hr = (int)floor(($tz = date('Z')) / 3600)) > 0 ? '+' : '-', abs($hr)) . ':' . sprintf("%02d", (($tz / 3600) - $hr) * 60); + return 'Z'; } function twig_split_filter($str, $delim) { @@ -75,7 +75,7 @@ function twig_remove_whitespace_filter($data) { } function twig_date_filter($date, $format) { - return strftime($format, $date); + return gmstrftime($format, $date); } function twig_hasPermission_filter($mod, $permission, $board = null) { diff --git a/inc/mod/auth.php b/inc/mod/auth.php index 73a24c44..173190ab 100644 --- a/inc/mod/auth.php +++ b/inc/mod/auth.php @@ -128,7 +128,7 @@ if (isset($_COOKIE[$config['cookies']['mod']])) { function create_pm_header() { global $mod, $config; - if ($config['cache']['enabled'] && ($header = cache::get('pm_unread_' . $mod['id'])) !== false) { + if ($config['cache']['enabled'] && ($header = cache::get('pm_unread_' . $mod['id'])) != false) { if ($header === true) return false; diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 116008d5..f8a730d0 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -92,7 +92,7 @@ function mod_dashboard() { } } - if (!$config['cache']['enabled'] || ($args['unread_pms'] = cache::get('pm_unreadcount_' . $mod['id'])) === false) { + if (!$config['cache']['enabled'] || ($args['unread_pms'] = cache::get('pm_unreadcount_' . $mod['id'])) == false) { $query = prepare('SELECT COUNT(*) FROM `pms` WHERE `to` = :id AND `unread` = 1'); $query->bindValue(':id', $mod['id']); $query->execute() or error(db_error($query)); @@ -651,7 +651,8 @@ function mod_bans($page_no = 1) { if (preg_match('/^ban_(\d+)$/', $name, $match)) $unban[] = $match[1]; } - + if (isset($config['mod']['unban_limit'])){ + if (count($unban) <= $config['mod']['unban_limit'] || $config['mod']['unban_limit'] == -1){ if (!empty($unban)) { query('DELETE FROM `bans` WHERE `id` = ' . implode(' OR `id` = ', $unban)) or error(db_error()); @@ -659,7 +660,21 @@ function mod_bans($page_no = 1) { modLog("Removed ban #{$id}"); } } + } else { + error(sprintf($config['error']['toomanyunban'], $config['mod']['unban_limit'], count($unban) )); + } + + } else { + + if (!empty($unban)) { + query('DELETE FROM `bans` WHERE `id` = ' . implode(' OR `id` = ', $unban)) or error(db_error()); + foreach ($unban as $id) { + modLog("Removed ban #{$id}"); + } + } + + } header('Location: ?/bans', true, $config['redirect_http']); } @@ -1855,6 +1870,7 @@ function mod_theme_configure($theme_name) { 'result' => $result, 'message' => $message, )); + return; } $settings = themeSettings($theme_name); diff --git a/js/auto-reload.js b/js/auto-reload.js index c0c63056..5d96146a 100644 --- a/js/auto-reload.js +++ b/js/auto-reload.js @@ -16,6 +16,9 @@ $(document).ready(function(){ if($('div.banner').length == 0) return; // not index + + if($(".post.op").size() != 1) + return; //not thread page var poll_interval; diff --git a/js/post-hover.js b/js/post-hover.js index 2f25540b..48f0ed96 100644 --- a/js/post-hover.js +++ b/js/post-hover.js @@ -20,6 +20,8 @@ onready(function(){ if(id = $link.text().match(/^>>(\d+)$/)) { id = id[1]; + } else { + return; } var $post = false; diff --git a/mod.php b/mod.php index dc666996..8d6db402 100644 --- a/mod.php +++ b/mod.php @@ -105,7 +105,7 @@ $new_pages = array(); foreach ($pages as $key => $callback) { if (preg_match('/^secure /', $callback)) $key .= '(/(?P[a-f0-9]{8}))?'; - $new_pages[@$key[0] == '!' ? $key : "!^$key$!"] = $callback; + $new_pages[@$key[0] == '!' ? $key : '!^' . $key . '(?:&[^&=]+=[^&]*)*$!'] = $callback; } $pages = $new_pages; diff --git a/post.php b/post.php index 06a6c65d..710bb178 100644 --- a/post.php +++ b/post.php @@ -310,13 +310,21 @@ if (isset($_POST['delete'])) { } } - // Check if thread is locked - // but allow mods to post - if (!$post['op'] && !hasPermission($config['mod']['postinlocked'], $board['uri'])) { - if ($thread['locked']) + if (!$post['op']) { + // Check if thread is locked + // but allow mods to post + if ($thread['locked'] && !hasPermission($config['mod']['postinlocked'], $board['uri'])) error($config['error']['locked']); + + $numposts = numPosts($post['thread']); + + if ($config['reply_hard_limit'] != 0 && $config['reply_hard_limit'] <= $numposts['replies']) + error($config['error']['reply_hard_limit']); + + if ($post['has_file'] && $config['image_hard_limit'] != 0 && $config['image_hard_limit'] <= $numposts['images']) + error($config['error']['image_hard_limit']); } - + if ($post['has_file']) { $size = $_FILES['file']['size']; if ($size > $config['max_filesize']) @@ -644,7 +652,7 @@ if (isset($_POST['delete'])) { buildThread($post['op'] ? $id : $post['thread']); - if (!$post['op'] && strtolower($post['email']) != 'sage' && !$thread['sage'] && ($config['reply_limit'] == 0 || numPosts($post['thread']) < $config['reply_limit'])) { + if (!$post['op'] && strtolower($post['email']) != 'sage' && !$thread['sage'] && ($config['reply_limit'] == 0 || $numposts['replies']+1 < $config['reply_limit'])) { bumpThread($post['thread']); } diff --git a/templates/main.js b/templates/main.js index b0a96393..9ec0025f 100644 --- a/templates/main.js +++ b/templates/main.js @@ -105,7 +105,7 @@ function generatePassword() { function dopost(form) { if (form.elements['name']) { - localStorage.name = form.elements['name'].value.replace(/ ##.+$/, ''); + localStorage.name = form.elements['name'].value.replace(/( |^)## .+$/, ''); } if (form.elements['email'] && form.elements['email'].value != 'sage') { localStorage.email = form.elements['email'].value; diff --git a/templates/post_reply.html b/templates/post_reply.html index 73ff2c15..450f8246 100644 --- a/templates/post_reply.html +++ b/templates/post_reply.html @@ -7,14 +7,14 @@