diff --git a/inc/config.php b/inc/config.php index 7adcecd9..ab052f58 100644 --- a/inc/config.php +++ b/inc/config.php @@ -366,6 +366,10 @@ $config['field_disable_name'] = false; // When true, no email will be able to be set. $config['field_disable_email'] = false; + // When true, no subject will be able to be set. + $config['field_disable_subject'] = false; + // When true, no subject will be able to be set in replies. + $config['field_disable_reply_subject'] = false; // When true, a blank password will be used for files (not usable for deletion). $config['field_disable_password'] = false; @@ -468,8 +472,10 @@ // Maximum image dimensions $config['max_width'] = 10000; $config['max_height'] = $config['max_width']; // 1:1 - // Reject dupliate image uploads + // Reject duplicate image uploads $config['image_reject_repost'] = true; + // Reject duplicate image uploads within the same thread. Doesn't change anything if image_reject_repost is true. + $config['image_reject_repost_in_thread'] = false; // Display the aspect ratio in a post's file info $config['show_ratio'] = false; @@ -704,6 +710,7 @@ $config['error']['maxsize'] = _('The file was too big.'); $config['error']['invalidzip'] = _('Invalid archive!'); $config['error']['fileexists'] = _('That file already exists!'); + $config['error']['fileexistsinthread'] = _('That file already exists in this thread!'); $config['error']['delete_too_soon'] = _('You\'ll have to wait another %s before deleting that.'); $config['error']['mime_exploit'] = _('MIME type detection XSS exploit (IE) detected; post discarded.'); $config['error']['invalid_embed'] = _('Couldn\'t make sense of the URL of the video you tried to embed.'); diff --git a/inc/display.php b/inc/display.php index 2418b874..34795ce6 100644 --- a/inc/display.php +++ b/inc/display.php @@ -155,6 +155,11 @@ function truncate($body, $url, $max_lines = false, $max_chars = false) { $max_lines = $config['body_truncate']; if ($max_chars === false) $max_chars = $config['body_truncate_char']; + + // We don't want to risk truncating in the middle of an HTML comment. + // It's easiest just to remove them all first. + $body = preg_replace('//s', '', $body); + $original_body = $body; $lines = substr_count($body, '
'); @@ -165,7 +170,7 @@ function truncate($body, $url, $max_lines = false, $max_chars = false) { $body = $m[0]; } - $body = substr($body, 0, $max_chars); + $body = mb_substr($body, 0, $max_chars); if ($body != $original_body) { // Remove any corrupt tags at the end @@ -190,9 +195,12 @@ function truncate($body, $url, $max_lines = false, $max_chars = false) { // remove broken HTML entity at the end (if existent) $body = preg_replace('/&[^;]+$/', '', $body); + $tags_no_close_needed = array("colgroup", "dd", "dt", "li", "optgroup", "option", "p", "tbody", "td", "tfoot", "th", "thead", "tr", "br", "img"); + // Close any open tags foreach ($tags as &$tag) { - $body .= ""; + if (!in_array($tag, $tags_no_close_needed)) + $body .= ""; } } else { // remove broken HTML entity at the end (if existent) @@ -208,7 +216,7 @@ function truncate($body, $url, $max_lines = false, $max_chars = false) { function secure_link_confirm($text, $title, $confirm_message, $href) { global $config; - return '' . $text . ''; + return '' . $text . ''; } function secure_link($href) { return $href . '/' . make_secure_link_token($href); @@ -342,8 +350,8 @@ class Thread { // Fix internal links // Very complicated regex $this->body = preg_replace( - '/= :floodtime) OR (`ip` = :ip AND `body` != '' AND `body` = :body AND `time` >= :floodsameiptime) OR (`body` != '' AND `body` = :body AND `time` >= :floodsametime) LIMIT 1", $board['uri'])); $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']); - $query->bindValue(':body', $post['body'], PDO::PARAM_INT); + $query->bindValue(':body', $post['body']); $query->bindValue(':floodtime', time()-$config['flood_time'], PDO::PARAM_INT); $query->bindValue(':floodsameiptime', time()-$config['flood_time_ip'], PDO::PARAM_INT); $query->bindValue(':floodsametime', time()-$config['flood_time_same'], PDO::PARAM_INT); @@ -697,7 +697,7 @@ function threadExists($id) { function post(array $post) { global $pdo, $board; - $query = prepare(sprintf("INSERT INTO `posts_%s` VALUES ( NULL, :thread, :subject, :email, :name, :trip, :capcode, :body, :body_nomarkup, :time, :time, :thumb, :thumbwidth, :thumbheight, :file, :width, :height, :filesize, :filename, :filehash, :password, :ip, :sticky, :locked, 0, :embed)", $board['uri'])); + $query = prepare(sprintf("INSERT INTO `posts_%s` (`id`, `thread`, `subject`, `email`, `name`, `trip`, `capcode`, `body`, `body_nomarkup`, `time`, `bump`, `thumb`, `thumbwidth`, `thumbheight`, `file`, `filewidth`, `fileheight`, `filesize`, `filename`, `filehash`, `password`, `ip`, `sticky`, `locked`, `sage`, `embed`) VALUES ( NULL, :thread, :subject, :email, :name, :trip, :capcode, :body, :body_nomarkup, :time, :time, :thumb, :thumbwidth, :thumbheight, :file, :width, :height, :filesize, :filename, :filehash, :password, :ip, :sticky, :locked, 0, :embed)", $board['uri'])); // Basic stuff if (!empty($post['subject'])) { @@ -1651,6 +1651,20 @@ function getPostByHash($hash) { return false; } +function getPostByHashInThread($hash, $thread) { + global $board; + $query = prepare(sprintf("SELECT `id`,`thread` FROM `posts_%s` WHERE `filehash` = :hash AND ( `thread` = :thread OR `id` = :thread )", $board['uri'])); + $query->bindValue(':hash', $hash, PDO::PARAM_STR); + $query->bindValue(':thread', $thread, PDO::PARAM_INT); + $query->execute() or error(db_error($query)); + + if ($post = $query->fetch()) { + return $post; + } + + return false; +} + function undoImage(array $post) { if (!$post['has_file']) return; diff --git a/inc/mod/pages.php b/inc/mod/pages.php index a1837e23..d9334a0d 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -64,6 +64,7 @@ function mod_confirm($request) { } function mod_logout() { + global $config; destroyCookies(); header('Location: ?/', true, $config['redirect_http']); @@ -706,7 +707,7 @@ function mod_sticky($board, $unsticky, $post) { $query->bindValue(':sticky', $unsticky ? 0 : 1); $query->execute() or error(db_error($query)); if ($query->rowCount()) { - modLog(($unlock ? 'Unstickied' : 'Stickied') . " thread #{$post}"); + modLog(($unsticky ? 'Unstickied' : 'Stickied') . " thread #{$post}"); buildThread($post); buildIndex(); } @@ -728,7 +729,7 @@ function mod_bumplock($board, $unbumplock, $post) { $query->bindValue(':bumplock', $unbumplock ? 0 : 1); $query->execute() or error(db_error($query)); if ($query->rowCount()) { - modLog(($unlock ? 'Unbumplocked' : 'Bumplocked') . " thread #{$post}"); + modLog(($unbumplock ? 'Unbumplocked' : 'Bumplocked') . " thread #{$post}"); buildThread($post); buildIndex(); } @@ -1067,13 +1068,6 @@ function mod_deletefile($board, $post) { // Record the action modLog("Deleted file from post #{$post}"); - $query = prepare(sprintf('SELECT `thread` FROM `posts_%s` WHERE `id` = :id', $board)); - $query->bindValue(':id', $post); - $query->execute() or error(db_error($query)); - $thread = $query->fetchColumn(); - - // Rebuild thread - buildThread($thread ? $thread : $post); // Rebuild board buildIndex(); @@ -1106,7 +1100,7 @@ function mod_deletebyip($boardName, $post, $global = false) { $query = ''; foreach ($boards as $_board) { - $query .= sprintf("SELECT `id`, '%s' AS `board` FROM `posts_%s` WHERE `ip` = :ip UNION ALL ", $_board['uri'], $_board['uri']); + $query .= sprintf("SELECT `thread`, `id`, '%s' AS `board` FROM `posts_%s` WHERE `ip` = :ip UNION ALL ", $_board['uri'], $_board['uri']); } $query = preg_replace('/UNION ALL $/', '', $query); @@ -1117,18 +1111,27 @@ function mod_deletebyip($boardName, $post, $global = false) { if ($query->rowCount() < 1) error($config['error']['invalidpost']); - $boards = array(); + set_time_limit($config['mod']['rebuild_timelimit']); + + $threads_to_rebuild = array(); + $threads_deleted = array(); while ($post = $query->fetch()) { openBoard($post['board']); - $boards[] = $post['board']; - deletePost($post['id'], false); + deletePost($post['id'], false, false); + + if ($post['thread']) + $threads_to_rebuild[$post['board']][$post['thread']] = true; + else + $threads_deleted[$post['board']][$post['id']] = true; } - $boards = array_unique($boards); - - foreach ($boards as $_board) { + foreach ($threads_to_rebuild as $_board => $_threads) { openBoard($_board); + foreach ($_threads as $_thread => $_dummy) { + if ($_dummy && !isset($threads_deleted[$_board][$_thread])) + buildThread($_thread); + } buildIndex(); } @@ -1460,6 +1463,8 @@ function mod_rebuild() { error($config['error']['noaccess']); if (isset($_POST['rebuild'])) { + set_time_limit($config['mod']['rebuild_timelimit']); + $log = array(); $boards = listBoards(); $rebuilt_scripts = array(); diff --git a/install.php b/install.php index 7d27940b..1447c723 100644 --- a/install.php +++ b/install.php @@ -1,7 +1,7 @@ $config['max_width'] || $size[1] > $config['max_height']) { - - error($config['error']['maxsize']); - } - } else { - // GD failed - // TODO? - } - } else { - // find dimensions of an image using GD - if (!$size = @getimagesize($upload)) { - error($config['error']['invalidimg']); - } - if ($size[0] > $config['max_width'] || $size[1] > $config['max_height']) { - error($config['error']['maxsize']); - } + // find dimensions of an image using GD + if (!$size = @getimagesize($upload)) { + error($config['error']['invalidimg']); + } + if ($size[0] > $config['max_width'] || $size[1] > $config['max_height']) { + error($config['error']['maxsize']); } // create image object @@ -506,17 +490,34 @@ if (isset($_POST['delete'])) { } } - if ($post['has_file'] && $config['image_reject_repost'] && $p = getPostByHash($post['filehash'])) { - undoImage($post); - error(sprintf($config['error']['fileexists'], - $post['mod'] ? $config['root'] . $config['file_mod'] . '?/' : $config['root'] . - $board['dir'] . $config['dir']['res'] . - ($p['thread'] ? - $p['thread'] . '.html#' . $p['id'] - : - $p['id'] . '.html' - ) - )); + if ($post['has_file']) { + if ($config['image_reject_repost']) { + if ($p = getPostByHash($post['filehash'])) { + undoImage($post); + error(sprintf($config['error']['fileexists'], + $post['mod'] ? $config['root'] . $config['file_mod'] . '?/' : $config['root'] . + $board['dir'] . $config['dir']['res'] . + ($p['thread'] ? + $p['thread'] . '.html#' . $p['id'] + : + $p['id'] . '.html' + ) + )); + } + } else if (!$post['op'] && $config['image_reject_repost_in_thread']) { + if ($p = getPostByHashInThread($post['filehash'], $post['thread'])) { + undoImage($post); + error(sprintf($config['error']['fileexistsinthread'], + $post['mod'] ? $config['root'] . $config['file_mod'] . '?/' : $config['root'] . + $board['dir'] . $config['dir']['res'] . + ($p['thread'] ? + $p['thread'] . '.html#' . $p['id'] + : + $p['id'] . '.html' + ) + )); + } + } } if (!hasPermission($config['mod']['postunoriginal'], $board['uri']) && $config['robot_enable'] && checkRobot($post['body_nomarkup'])) { diff --git a/templates/post_form.html b/templates/post_form.html index 30bf9224..1727250b 100644 --- a/templates/post_form.html +++ b/templates/post_form.html @@ -27,12 +27,18 @@ {% endif %} - + {% if not (config.field_disable_subject or (id and config.field_disable_reply_subject)) or (mod and post.mod|hasPermission(config.mod.bypass_field_disable, board.uri)) %} {% trans %}Subject{% endtrans %} {{ antibot.html() }} + {% else %} + {% trans %}Submit{% endtrans %} + {{ antibot.html() }} + + + {% endif %} {% if config.spoiler_images %} {% endif %} diff --git a/templates/post_reply.html b/templates/post_reply.html index f4a20f04..73ff2c15 100644 --- a/templates/post_reply.html +++ b/templates/post_reply.html @@ -63,7 +63,7 @@ , {{ post.ratio }} {% endif %} {% endif %} - {% if config.show_filename %} + {% if config.show_filename and post.filename %} , {% if post.filename|length > config.max_filename_display %} {{ post.filename|truncate(config.max_filename_display) }} diff --git a/templates/post_thread.html b/templates/post_thread.html index 7d4194ba..04ce1b0d 100644 --- a/templates/post_thread.html +++ b/templates/post_thread.html @@ -20,7 +20,7 @@ , {{ post.ratio }} {% endif %} {% endif %} - {% if config.show_filename %} + {% if config.show_filename and post.filename %} , {% if post.filename|length > config.max_filename_display %} {{ post.filename|truncate(config.max_filename_display) }} diff --git a/templates/posts.sql b/templates/posts.sql index d10749f8..c766d38e 100644 --- a/templates/posts.sql +++ b/templates/posts.sql @@ -26,7 +26,7 @@ CREATE TABLE IF NOT EXISTS `posts_{{ board }}` ( `sage` int(1) NOT NULL, `embed` text, UNIQUE KEY `id` (`id`), - KEY `thread` (`thread`), + KEY `thread_id` (`thread`, `id`), KEY `time` (`time`), FULLTEXT KEY `body` (`body`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;