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