From 7c3126866cd291d515ab5cc83d4635c3c95c6d85 Mon Sep 17 00:00:00 2001 From: czaks Date: Thu, 5 May 2016 06:43:22 +0200 Subject: ease the migration process for the previous security patch (by introducing another migration); restore php 5.4 compatibility (introducing a polyfill system) --- inc/functions.php | 2 ++ 1 file changed, 2 insertions(+) (limited to 'inc/functions.php') diff --git a/inc/functions.php b/inc/functions.php index 515e3e55..3f5ed3b0 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -18,6 +18,8 @@ require_once 'inc/template.php'; require_once 'inc/database.php'; require_once 'inc/events.php'; require_once 'inc/api.php'; +require_once 'inc/polyfill.php'; + if (!extension_loaded('gettext')) { require_once 'inc/lib/gettext/gettext.inc'; } -- cgit v1.3 From 36b78e5f98302cb8fb3d4a00d4be61d6775d74a3 Mon Sep 17 00:00:00 2001 From: czaks Date: Thu, 5 May 2016 08:40:13 +0200 Subject: fix for editor highlighting --- inc/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'inc/functions.php') diff --git a/inc/functions.php b/inc/functions.php index 3f5ed3b0..fd4c910d 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -138,7 +138,7 @@ function loadConfig() { $configstr .= file_get_contents($board['dir'] . '/config.php'); } $matches = array(); - preg_match_all('/[^\/*#]\$config\s*\[\s*[\'"]locale[\'"]\s*\]\s*=\s*([\'"])(.*?)\1/', $configstr, $matches); + preg_match_all('/[^\/#*]\$config\s*\[\s*[\'"]locale[\'"]\s*\]\s*=\s*([\'"])(.*?)\1/', $configstr, $matches); if ($matches && isset ($matches[2]) && $matches[2]) { $matches = $matches[2]; $config['locale'] = $matches[count($matches)-1]; -- cgit v1.3 From a42256b296f14c5f82889f9e222d94a416aa1924 Mon Sep 17 00:00:00 2001 From: czaks Date: Thu, 5 May 2016 08:43:34 +0200 Subject: locale cache: fix a bug when perms are done wrong --- inc/functions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'inc/functions.php') diff --git a/inc/functions.php b/inc/functions.php index fd4c910d..462bc173 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -127,7 +127,7 @@ function loadConfig() { // So, we may store the locale in a tmp/ filesystem. if (file_exists($fn = 'tmp/cache/locale_' . $boardsuffix ) ) { - $config['locale'] = file_get_contents($fn); + $config['locale'] = @file_get_contents($fn); } else { $config['locale'] = 'en'; @@ -144,7 +144,7 @@ function loadConfig() { $config['locale'] = $matches[count($matches)-1]; } - file_put_contents($fn, $config['locale']); + @file_put_contents($fn, $config['locale']); } if ($config['locale'] != $current_locale) { -- cgit v1.3 From 77176faececfceceb4c35eb4bfa80a99ed76a677 Mon Sep 17 00:00:00 2001 From: czaks Date: Thu, 5 May 2016 09:56:54 +0200 Subject: enable javascript in mod panel --- inc/functions.php | 2 +- inc/mod/pages.php | 4 ++-- templates/page.html | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) (limited to 'inc/functions.php') diff --git a/inc/functions.php b/inc/functions.php index 462bc173..c50336ae 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -880,7 +880,7 @@ function displayBan($ban) { Element('page.html', array( 'title' => _('Banned!'), 'config' => $config, - 'nojavascript' => true, + 'boardlist' => createBoardlist($mod), 'body' => Element('banned.html', array( 'config' => $config, 'ban' => $ban, diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 8b6f73c4..8380cfd3 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -15,7 +15,7 @@ function mod_page($title, $template, $args, $subtitle = false) { 'hide_dashboard_link' => $template == 'mod/dashboard.html', 'title' => $title, 'subtitle' => $subtitle, - 'nojavascript' => true, + 'boardlist' => createBoardlist($mod), 'body' => Element($template, array_merge( array('config' => $config, 'mod' => $mod), @@ -846,7 +846,7 @@ function mod_page_ip($ip) { $args['security_token'] = make_secure_link_token('IP/' . $ip); - mod_page(sprintf('%s: %s', _('IP'), $ip), 'mod/view_ip.html', $args, $args['hostname']); + mod_page(sprintf('%s: %s', _('IP'), htmlspecialchars($ip)), 'mod/view_ip.html', $args, $args['hostname']); } function mod_ban() { diff --git a/templates/page.html b/templates/page.html index 13753c03..3522702f 100644 --- a/templates/page.html +++ b/templates/page.html @@ -9,6 +9,8 @@ {{ title }} + {{ boardlist.top }} + {% if pm %}
You have an unread PM{% if pm.waiting > 0 %}, plus {{ pm.waiting }} more waiting{% endif %}.

{% endif %}

{{ title }}

-- cgit v1.3 From 6b04b3c671fdcaa85be97c14336219217b193cc5 Mon Sep 17 00:00:00 2001 From: 8chan Date: Mon, 20 Apr 2015 04:20:17 -0700 Subject: Fix post deletion --- inc/functions.php | 2 ++ js/fix-report-delete-submit.js | 5 ++--- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'inc/functions.php') diff --git a/inc/functions.php b/inc/functions.php index c50336ae..c46807bb 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -1130,6 +1130,8 @@ function deleteFile($id, $remove_entirely_if_already=true, $file=null) { $files = json_decode($post['files']); $file_to_delete = $file !== false ? $files[(int)$file] : (object)array('file' => false); + if (!$files[0]) error(_('That post has no files.')); + if ($files[0]->file == 'deleted' && $post['num_files'] == 1 && !$post['thread']) return; // Can't delete OP's image completely. diff --git a/js/fix-report-delete-submit.js b/js/fix-report-delete-submit.js index c6122db7..47a04f16 100644 --- a/js/fix-report-delete-submit.js +++ b/js/fix-report-delete-submit.js @@ -11,7 +11,7 @@ if (active_page == 'thread' || active_page == 'index') { $(document).ready(function(){ -if ($('.delete #password').length) { +if ($('#delete-fields #password').length) { Menu.add_item("delete_post_menu", _("Delete post")); Menu.add_item("delete_file_menu", _("Delete file")); Menu.onclick(function(e, $buf) { @@ -53,9 +53,8 @@ Menu.onclick(function(e, $buf) { }); $(document).on('new_post', function(){ - $('div.delete').hide(); $('input.delete').hide(); }); -$('div.delete').hide(); $('input.delete').hide(); +$('#post-moderation-fields').hide(); })} -- cgit v1.3 From 7a7574bdcace8ca5526ed10fe675cbbabced6ecd Mon Sep 17 00:00:00 2001 From: 8chan Date: Wed, 24 Dec 2014 06:08:19 -0800 Subject: SECURITY / XSS : ?/edit allowed arbitrary HTML to be added by any user thru addition of 1 This allowed ANY user with ?/edit privilege to also have raw_html regardless of whether they had $config['mod']['rawhtml'] Now, any changes to markup modifiers via ?/edit are not allowed. They are removed at read time, and before write they are removed again and the ones in the database (which should be clean...) are inserted instead. Please immediately apply this patch to your instance if you are running any version of 8chan/infinity. --- inc/functions.php | 6 +++++- inc/mod/pages.php | 20 +++++++++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) (limited to 'inc/functions.php') diff --git a/inc/functions.php b/inc/functions.php index c46807bb..85b61b2e 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -1849,7 +1849,11 @@ function extract_modifiers($body) { return $modifiers; } -function markup(&$body, $track_cites = false) { +function remove_modifiers($body) { + return preg_replace('@(.+?)@usm', '', $body); +} + +function markup(&$body, $track_cites = false, $op = false) { global $board, $config, $markup_urls; $modifiers = extract_modifiers($body); diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 8380cfd3..a90fbbd6 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -1473,6 +1473,15 @@ function mod_edit_post($board, $edit_raw_html, $postID) { error($config['error']['404']); if (isset($_POST['name'], $_POST['email'], $_POST['subject'], $_POST['body'])) { + // Remove any modifiers they may have put in + $_POST['body'] = remove_modifiers($_POST['body']); + + // Add back modifiers in the original post + $modifiers = extract_modifiers($post['body_nomarkup']); + foreach ($modifiers as $key => $value) { + $_POST['body'] .= "$value"; + } + if ($edit_raw_html) $query = prepare(sprintf('UPDATE ``posts_%s`` SET `name` = :name, `email` = :email, `subject` = :subject, `body` = :body, `body_nomarkup` = :body_nomarkup WHERE `id` = :id', $board)); else @@ -1501,15 +1510,20 @@ function mod_edit_post($board, $edit_raw_html, $postID) { header('Location: ?/' . sprintf($config['board_path'], $board) . $config['dir']['res'] . link_for($post) . '#' . $postID, true, $config['redirect_http']); } else { + // Remove modifiers + $post['body_nomarkup'] = remove_modifiers($post['body_nomarkup']); + + $post['body_nomarkup'] = utf8tohtml($post['body_nomarkup']); + $post['body'] = utf8tohtml($post['body']); if ($config['minify_html']) { - $post['body_nomarkup'] = str_replace("\n", ' ', utf8tohtml($post['body_nomarkup'])); - $post['body'] = str_replace("\n", ' ', utf8tohtml($post['body'])); + $post['body_nomarkup'] = str_replace("\n", ' ', $post['body_nomarkup']); + $post['body'] = str_replace("\n", ' ', $post['body']); $post['body_nomarkup'] = str_replace("\r", '', $post['body_nomarkup']); $post['body'] = str_replace("\r", '', $post['body']); $post['body_nomarkup'] = str_replace("\t", ' ', $post['body_nomarkup']); $post['body'] = str_replace("\t", ' ', $post['body']); } - + mod_page(_('Edit post'), 'mod/edit_post_form.html', array('token' => $security_token, 'board' => $board, 'raw' => $edit_raw_html, 'post' => $post)); } } -- cgit v1.3 From 126ee42b9dffe262457177c5dbb7ffa2db170763 Mon Sep 17 00:00:00 2001 From: czaks Date: Fri, 6 May 2016 14:34:42 +0200 Subject: better rules for stripping combined chars, based on 45c0d327619 by @ctrlcctrlv --- inc/functions.php | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) (limited to 'inc/functions.php') diff --git a/inc/functions.php b/inc/functions.php index 85b61b2e..d28d018d 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -2152,16 +2152,7 @@ function strip_combining_chars($str) { $o = 0; $ord = ordutf8($char, $o); - if ($ord >= 768 && $ord <= 879) - continue; - - if ($ord >= 7616 && $ord <= 7679) - continue; - - if ($ord >= 8400 && $ord <= 8447) - continue; - - if ($ord >= 65056 && $ord <= 65071) + if ( ($ord >= 768 && $ord <= 879) || ($ord >= 1536 && $ord <= 1791) || ($ord >= 3655 && $ord <= 3659) || ($ord >= 7616 && $ord <= 7679) || ($ord >= 8400 && $ord <= 8447) || ($ord >= 65056 && $ord <= 65071)) continue; $str .= $char; -- cgit v1.3 From b476b660073adbe827836a7e7860b0fb3a061ff0 Mon Sep 17 00:00:00 2001 From: Fredrick Brennan Date: Sun, 23 Aug 2015 09:04:37 +0800 Subject: [BUG] Image reject repost board option now also affects YT embeds --- inc/functions.php | 47 +++++++++++++++++++++++++++++++++++++++++++++++ post.php | 28 +++++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 1 deletion(-) (limited to 'inc/functions.php') diff --git a/inc/functions.php b/inc/functions.php index d28d018d..cbe0bc62 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -2438,6 +2438,53 @@ function getPostByHashInThread($hash, $thread) { return false; } +function getPostByEmbed($embed) { + global $board, $config; + $matches = array(); + foreach ($config['embedding'] as &$e) { + if (preg_match($e[0], $embed, $matches) && isset($matches[1]) && !empty($matches[1])) { + $embed = '%'.$matches[1].'%'; + break; + } + } + + if (!isset($embed)) return false; + + $query = prepare(sprintf("SELECT `id`,`thread` FROM ``posts_%s`` WHERE `embed` LIKE :embed", $board['uri'])); + $query->bindValue(':embed', $embed, PDO::PARAM_STR); + $query->execute() or error(db_error($query)); + + if ($post = $query->fetch(PDO::FETCH_ASSOC)) { + return $post; + } + + return false; +} + +function getPostByEmbedInThread($embed, $thread) { + global $board, $config; + $matches = array(); + foreach ($config['embedding'] as &$e) { + if (preg_match($e[0], $embed, $matches) && isset($matches[1]) && !empty($matches[1])) { + $embed = '%'.$matches[1].'%'; + break; + } + } + + if (!isset($embed)) return false; + + $query = prepare(sprintf("SELECT `id`,`thread` FROM ``posts_%s`` WHERE `embed` = :embed AND ( `thread` = :thread OR `id` = :thread )", $board['uri'])); + $query->bindValue(':embed', $embed, PDO::PARAM_STR); + $query->bindValue(':thread', $thread, PDO::PARAM_INT); + $query->execute() or error(db_error($query)); + + if ($post = $query->fetch(PDO::FETCH_ASSOC)) { + return $post; + } + + return false; +} + function undoImage(array $post) { if (!$post['has_file'] || !isset($post['files'])) return; diff --git a/post.php b/post.php index 3b4f3311..06a9dc74 100644 --- a/post.php +++ b/post.php @@ -294,6 +294,32 @@ if (isset($_POST['delete'])) { if (!isset($post['embed'])) { error($config['error']['invalid_embed']); } + + if ($config['image_reject_repost']) { + if ($p = getPostByEmbed($post['embed'])) { + 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 = getPostByEmbedInThread($post['embed'], $post['thread'])) { + 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']['bypass_field_disable'], $board['uri'])) { @@ -798,7 +824,7 @@ if (isset($_POST['delete'])) { )); } } - } + } if (!hasPermission($config['mod']['postunoriginal'], $board['uri']) && $config['robot_enable'] && checkRobot($post['body_nomarkup'])) { undoImage($post); -- cgit v1.3 From 7831da83fc09ff90a0f99641bde5e2d8c08e84c5 Mon Sep 17 00:00:00 2001 From: 8chan Date: Mon, 16 Feb 2015 16:21:30 -0800 Subject: New event: rebuildpost, allows you to bind events to ?/edit --- inc/functions.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'inc/functions.php') diff --git a/inc/functions.php b/inc/functions.php index cbe0bc62..4746b034 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -1166,19 +1166,22 @@ function deleteFile($id, $remove_entirely_if_already=true, $file=null) { // rebuild post (markup) function rebuildPost($id) { - global $board; + global $board, $mod; - $query = prepare(sprintf("SELECT `body_nomarkup`, `thread` FROM ``posts_%s`` WHERE `id` = :id", $board['uri'])); + $query = prepare(sprintf("SELECT * FROM ``posts_%s`` WHERE `id` = :id", $board['uri'])); $query->bindValue(':id', $id, PDO::PARAM_INT); $query->execute() or error(db_error($query)); if ((!$post = $query->fetch(PDO::FETCH_ASSOC)) || !$post['body_nomarkup']) return false; - markup($body = &$post['body_nomarkup']); + markup($post['body'] = &$post['body_nomarkup']); + $post = (object)$post; + event('rebuildpost', $post); + $post = (array)$post; $query = prepare(sprintf("UPDATE ``posts_%s`` SET `body` = :body WHERE `id` = :id", $board['uri'])); - $query->bindValue(':body', $body); + $query->bindValue(':body', $post['body']); $query->bindValue(':id', $id, PDO::PARAM_INT); $query->execute() or error(db_error($query)); -- cgit v1.3 From ce3ce4f1b65198083c232897c169313640f23038 Mon Sep 17 00:00:00 2001 From: 8chan Date: Mon, 16 Feb 2015 16:24:54 -0800 Subject: Fix *0 secure tripcodes caused by accidentally feeding + signs to crypt() --- inc/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'inc/functions.php') diff --git a/inc/functions.php b/inc/functions.php index 4746b034..6a34e7a8 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -2374,7 +2374,7 @@ function generate_tripcode($name) { if (isset($config['custom_tripcode']["##{$trip}"])) $trip = $config['custom_tripcode']["##{$trip}"]; else - $trip = '!!' . substr(crypt($trip, '_..A.' . substr(base64_encode(sha1($trip . $config['secure_trip_salt'], true)), 0, 4)), -10); + $trip = '!!' . substr(crypt($trip, str_replace('+', '.', '_..A.' . substr(base64_encode(sha1($trip . $config['secure_trip_salt'], true)), 0, 4))), -10); } else { if (isset($config['custom_tripcode']["#{$trip}"])) $trip = $config['custom_tripcode']["#{$trip}"]; -- cgit v1.3 From 6dd1420f91410531bb2dac506f821d959bf74d0a Mon Sep 17 00:00:00 2001 From: 8chan Date: Wed, 18 Feb 2015 16:55:03 -0800 Subject: Add event to quote backlinks --- inc/functions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'inc/functions.php') diff --git a/inc/functions.php b/inc/functions.php index 6a34e7a8..8f650828 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -1952,7 +1952,7 @@ function markup(&$body, $track_cites = false, $op = false) { } if (isset($cited_posts[$cite])) { - $replacement = '' . '>>' . $cite . @@ -2051,7 +2051,7 @@ function markup(&$body, $track_cites = false, $op = false) { $replacement = '' . '>>>/' . $_board . '/' . $cite . ''; -- cgit v1.3 From 7911c374e86ef8fa69c4545867b9d9d3de12d5c6 Mon Sep 17 00:00:00 2001 From: 8chan Date: Wed, 25 Feb 2015 17:21:49 -0800 Subject: Public action logs commit (log.php) Note: In a previous commit, I began making inc/mod/auth.php more modular with the check_login() function. Including it does NOT check mod login by default anymore like it does on vichan. You have to call check_login(). I've finally included it in inc/functions.php. If you have any custom pages that use inc/mod/auth.php, just including functions.php is enough now. =================================== Also: backports 351375185e5 (early 404) --- inc/config.php | 22 +++++++++++++++++----- inc/functions.php | 22 ++++++++++++++++++++-- inc/mod/auth.php | 2 +- inc/mod/pages.php | 36 ++++++++++++++++++++++++++++++++++++ install.sql | 1 + log.php | 24 ++++++++++++++++++++++++ mod.php | 6 +++--- post.php | 5 +++-- templates/mod/log.html | 16 ++++++++++++++-- tools/inc/cli.php | 1 - 10 files changed, 119 insertions(+), 16 deletions(-) create mode 100644 log.php (limited to 'inc/functions.php') diff --git a/inc/config.php b/inc/config.php index a3087162..b6bc3b69 100644 --- a/inc/config.php +++ b/inc/config.php @@ -511,6 +511,13 @@ // The timeout for the above, in seconds. $config['upload_by_url_timeout'] = 15; + // Enable early 404? With default settings, a thread would 404 if it was to leave page 3, if it had less + // than 3 replies. + $config['early_404'] = false; + + $config['early_404_page'] = 3; + $config['early_404_replies'] = 5; + // A wordfilter (sometimes referred to as just a "filter" or "censor") automatically scans users’ posts // as they are submitted and changes or censors particular words or phrases. @@ -1537,25 +1544,30 @@ /* * ==================== - * Public post search + * Public pages * ==================== */ + + // Public post search settings $config['search'] = array(); // Enable the search form $config['search']['enable'] = false; // Maximal number of queries per IP address per minutes - $config['search']['queries_per_minutes'] = Array(15, 2); + $config['search']['queries_per_minutes'] = Array(15, 2); // Global maximal number of queries per minutes - $config['search']['queries_per_minutes_all'] = Array(50, 2); + $config['search']['queries_per_minutes_all'] = Array(50, 2); // Limit of search results - $config['search']['search_limit'] = 100; + $config['search']['search_limit'] = 100; // Boards for searching - //$config['search']['boards'] = array('a', 'b', 'c', 'd', 'e'); + //$config['search']['boards'] = array('a', 'b', 'c', 'd', 'e'); + + // Enable public logs? 0: NO, 1: YES, 2: YES, but drop names + $config['public_logs'] = 0; /* * ==================== diff --git a/inc/functions.php b/inc/functions.php index 8f650828..378e40b3 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -18,6 +18,7 @@ require_once 'inc/template.php'; require_once 'inc/database.php'; require_once 'inc/events.php'; require_once 'inc/api.php'; +require_once 'inc/mod/auth.php'; require_once 'inc/polyfill.php'; if (!extension_loaded('gettext')) { @@ -524,7 +525,8 @@ function setupBoard($array) { $board = array( 'uri' => $array['uri'], 'title' => $array['title'], - 'subtitle' => $array['subtitle'] + 'subtitle' => $array['subtitle'], + #'indexed' => $array['indexed'], ); // older versions @@ -1270,7 +1272,7 @@ function deletePost($id, $error_if_doesnt_exist=true, $rebuild_after=true) { return true; } -function clean() { +function clean($pid = false) { global $board, $config; $offset = round($config['max_pages']*$config['threads_per_page']); @@ -1281,6 +1283,22 @@ function clean() { $query->execute() or error(db_error($query)); while ($post = $query->fetch(PDO::FETCH_ASSOC)) { deletePost($post['id'], false, false); + if ($pid) modLog("Automatically deleting thread #{$post['id']} due to new thread #{$pid}"); + } + + // Bump off threads with X replies earlier, spam prevention method + if ($config['early_404']) { + $offset = round($config['early_404_page']*$config['threads_per_page']); + $query = prepare(sprintf("SELECT `id` AS `thread_id`, (SELECT COUNT(`id`) FROM ``posts_%s`` WHERE `thread` = `thread_id`) AS `reply_count` FROM ``posts_%s`` WHERE `thread` IS NULL ORDER BY `sticky` DESC, `bump` DESC LIMIT :offset, 9001", $board['uri'], $board['uri'])); + $query->bindValue(':offset', $offset, PDO::PARAM_INT); + $query->execute() or error(db_error($query)); + + while ($post = $query->fetch(PDO::FETCH_ASSOC)) { + if ($post['reply_count'] < $config['early_404_replies']) { + deletePost($post['thread_id'], false, false); + if ($pid) modLog("Automatically deleting thread #{$post['thread_id']} due to new thread #{$pid} (early 404 is set, #{$post['thread_id']} had {$post['reply_count']} replies)"); + } + } } } diff --git a/inc/mod/auth.php b/inc/mod/auth.php index 009e93ad..42f34196 100644 --- a/inc/mod/auth.php +++ b/inc/mod/auth.php @@ -130,7 +130,7 @@ function destroyCookies() { 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(':id', (isset($mod['id']) ? $mod['id'] : -1), PDO::PARAM_INT); $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']); $query->bindValue(':time', time(), PDO::PARAM_INT); $query->bindValue(':text', $action); diff --git a/inc/mod/pages.php b/inc/mod/pages.php index a90fbbd6..ca12eaf1 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -698,6 +698,42 @@ function mod_user_log($username, $page_no = 1) { mod_page(_('Moderation log'), 'mod/log.html', array('logs' => $logs, 'count' => $count, 'username' => $username)); } +function mod_board_log($board, $page_no = 1, $hide_names = false, $public = false) { + global $config; + + if ($page_no < 1) + error($config['error']['404']); + + if (!hasPermission($config['mod']['mod_board_log'], $board) && !$public) + error($config['error']['noaccess']); + + $query = prepare("SELECT `username`, `mod`, `ip`, `board`, `time`, `text` FROM ``modlogs`` LEFT JOIN ``mods`` ON `mod` = ``mods``.`id` WHERE `board` = :board ORDER BY `time` DESC LIMIT :offset, :limit"); + $query->bindValue(':board', $board); + $query->bindValue(':limit', $config['mod']['modlog_page'], PDO::PARAM_INT); + $query->bindValue(':offset', ($page_no - 1) * $config['mod']['modlog_page'], PDO::PARAM_INT); + $query->execute() or error(db_error($query)); + $logs = $query->fetchAll(PDO::FETCH_ASSOC); + + if (empty($logs) && $page_no > 1) + error($config['error']['404']); + + if (!hasPermission($config['mod']['show_ip'])) { + // Supports ipv4 only! + foreach ($logs as $i => &$log) { + $log['text'] = preg_replace_callback('/(?:)?(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?:<\/a>)?/', function($matches) { + return "xxxx";//less_ip($matches[1]); + }, $log['text']); + } + } + + $query = prepare("SELECT COUNT(*) FROM ``modlogs`` LEFT JOIN ``mods`` ON `mod` = ``mods``.`id` WHERE `board` = :board"); + $query->bindValue(':board', $board); + $query->execute() or error(db_error($query)); + $count = $query->fetchColumn(); + + mod_page(_('Board log'), 'mod/log.html', array('logs' => $logs, 'count' => $count, 'board' => $board, 'hide_names' => $hide_names, 'public' => $public)); +} + function mod_view_board($boardName, $page_no = 1) { global $config, $mod; diff --git a/install.sql b/install.sql index 7e661450..024cb349 100644 --- a/install.sql +++ b/install.sql @@ -65,6 +65,7 @@ CREATE TABLE IF NOT EXISTS `boards` ( `uri` varchar(58) CHARACTER SET utf8 NOT NULL, `title` tinytext NOT NULL, `subtitle` tinytext, + -- `indexed` boolean default true, PRIMARY KEY (`uri`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; diff --git a/log.php b/log.php new file mode 100644 index 00000000..1a660c4c --- /dev/null +++ b/log.php @@ -0,0 +1,24 @@ + {% if log.username %} - {{ log.username|e }} + {% if hide_names %} + hidden + {% else %} + {% if not mod|hasPermission(config.mod.modlog) %} + {{ log.username|e }} + {% else %} + {{ log.username|e }} + {% endif %} + {% endif %} {% elseif log.mod == -1 %} system {% else %} @@ -44,7 +52,11 @@ {% if count > logs|count %}

{% for i in range(0, (count - 1) / config.mod.modlog_page) %} - [{{ i + 1 }}] + {% if public %} + [{{ i + 1 }}] + {% else %} + [{{ i + 1 }}] + {% endif %} {% endfor %}

{% endif %} diff --git a/tools/inc/cli.php b/tools/inc/cli.php index f3e8824f..95d51573 100644 --- a/tools/inc/cli.php +++ b/tools/inc/cli.php @@ -39,7 +39,6 @@ if(!getenv('TINYBOARD_PATH')) { putenv('TINYBOARD_PATH=' . getcwd()); require 'inc/functions.php'; -require 'inc/mod/auth.php'; $mod = Array( 'id' => -1, -- cgit v1.3 From 95b1e103cb9f349d7d82dca071636d5cde855ead Mon Sep 17 00:00:00 2001 From: Fredrick Brennan Date: Sun, 29 Mar 2015 09:18:14 +0800 Subject: Edit static pages commit --- .gitmodules | 6 ++ inc/config.php | 6 ++ inc/functions.php | 43 ++++++++++++ inc/mod/pages.php | 162 +++++++++++++++++++++++++++++++++++++++++++ install.sql | 20 +++++- mod.php | 18 +++-- templates/mod/edit_page.html | 29 ++++++++ templates/mod/pages.html | 34 +++++++++ tools/import_rules.php | 16 +++++ 9 files changed, 328 insertions(+), 6 deletions(-) create mode 100644 templates/mod/edit_page.html create mode 100644 templates/mod/pages.html create mode 100644 tools/import_rules.php (limited to 'inc/functions.php') diff --git a/.gitmodules b/.gitmodules index 73f90e0a..df07fdf3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,9 @@ [submodule "js/wPaint"] path = js/wPaint url = https://github.com/vichan-devel/wPaint.git + branch = master + +[submodule "inc/lib/parsedown"] + path = inc/lib/parsedown + url = https://github.com/vichan-devel/parsedown + branch = master diff --git a/inc/config.php b/inc/config.php index b6bc3b69..3fdb8d2f 100644 --- a/inc/config.php +++ b/inc/config.php @@ -1498,6 +1498,9 @@ $config['mod']['ban_appeals'] = MOD; // View the recent posts page $config['mod']['recent'] = MOD; + // Create pages + $config['mod']['edit_pages'] = MOD; + $config['pages_max'] = 10; // Config editor permissions $config['mod']['config'] = array(); @@ -1702,3 +1705,6 @@ // Use CAPTCHA for reports? $config['report_captcha'] = false; + + // Allowed HTML tags in ?/edit_pages. + $config['allowed_html'] = 'a[href|title],p,br,li,ol,ul,strong,em,u,h2,b,i,tt,div,img[src|alt|title],hr'; diff --git a/inc/functions.php b/inc/functions.php index 378e40b3..7970d05a 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -20,6 +20,7 @@ require_once 'inc/events.php'; require_once 'inc/api.php'; require_once 'inc/mod/auth.php'; require_once 'inc/polyfill.php'; +//require_once 'inc/lib/parsedown/Parsedown.php'; // we don't need that right now, do we? if (!extension_loaded('gettext')) { require_once 'inc/lib/gettext/gettext.inc'; @@ -2739,3 +2740,45 @@ function link_for($post, $page50 = false, $foreignlink = false, $thread = false) return sprintf($tpl, $id, $slug); } + +function prettify_textarea($s){ + return str_replace("\t", ' ', str_replace("\n", ' ', htmlentities($s))); +} + +class HTMLPurifier_URIFilter_NoExternalImages extends HTMLPurifier_URIFilter { + public $name = 'NoExternalImages'; + public function filter(&$uri, $c, $context) { + global $config; + $ct = $context->get('CurrentToken'); + + if (!$ct || $ct->name !== 'img') return true; + + if (!isset($uri->host) && !isset($uri->scheme)) return true; + + if (!in_array($uri->scheme . '://' . $uri->host . '/', $config['allowed_offsite_urls'])) { + error('No off-site links in board announcement images.'); + } + + return true; + } +} + +function purify_html($s) { + global $config; + + $c = HTMLPurifier_Config::createDefault(); + $c->set('HTML.Allowed', $config['allowed_html']); + $uri = $c->getDefinition('URI'); + $uri->addFilter(new HTMLPurifier_URIFilter_NoExternalImages(), $c); + $purifier = new HTMLPurifier($c); + $clean_html = $purifier->purify($s); + return $clean_html; +} + +function markdown($s) { + $pd = new Parsedown(); + $pd->setMarkupEscaped(true); + $pd->setimagesEnabled(false); + + return $pd->text($s); +} diff --git a/inc/mod/pages.php b/inc/mod/pages.php index ca12eaf1..328380d8 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -2628,6 +2628,167 @@ function mod_theme_rebuild($theme_name) { )); } +// This needs to be done for `secure` CSRF prevention compatibility, otherwise the $board will be read in as the token if editing global pages. +function delete_page_base($page = '', $board = false) { + global $config, $mod; + + if (empty($board)) + $board = false; + + if (!$board && $mod['boards'][0] !== '*') + error($config['error']['noaccess']); + + if (!hasPermission($config['mod']['edit_pages'], $board)) + error($config['error']['noaccess']); + + if ($board !== FALSE && !openBoard($board)) + error($config['error']['noboard']); + + if ($board) { + $query = prepare('DELETE FROM ``pages`` WHERE `board` = :board AND `name` = :name'); + $query->bindValue(':board', ($board ? $board : NULL)); + } else { + $query = prepare('DELETE FROM ``pages`` WHERE `board` IS NULL AND `name` = :name'); + } + $query->bindValue(':name', $page); + $query->execute() or error(db_error($query)); + + header('Location: ?/edit_pages' . ($board ? ('/' . $board) : ''), true, $config['redirect_http']); +} + +function mod_delete_page($page = '') { + delete_page_base($page); +} + +function mod_delete_page_board($page = '', $board = false) { + delete_page_base($page, $board); +} + +function mod_edit_page($id) { + global $config, $mod, $board; + + $query = prepare('SELECT * FROM ``pages`` WHERE `id` = :id'); + $query->bindValue(':id', $id); + $query->execute() or error(db_error($query)); + $page = $query->fetch(); + + if (!$page) + error(_('Could not find the page you are trying to edit.')); + + if (!$page['board'] && $mod['boards'][0] !== '*') + error($config['error']['noaccess']); + + if (!hasPermission($config['mod']['edit_pages'], $page['board'])) + error($config['error']['noaccess']); + + if ($page['board'] && !openBoard($page['board'])) + error($config['error']['noboard']); + + if (isset($_POST['method'], $_POST['content'])) { + $content = $_POST['content']; + $method = $_POST['method']; + $page['type'] = $method; + + if (!in_array($method, array('markdown', 'html', 'infinity'))) + error(_('Unrecognized page markup method.')); + + switch ($method) { + case 'markdown': + $write = markdown($content); + break; + case 'html': + if (hasPermission($config['mod']['rawhtml'])) { + $write = $content; + } else { + $write = purify_html($content); + } + break; + case 'infinity': + $c = $content; + markup($content); + $write = $content; + $content = $c; + } + + if (!isset($write) or !$write) + error(_('Failed to mark up your input for some reason...')); + + $query = prepare('UPDATE ``pages`` SET `type` = :method, `content` = :content WHERE `id` = :id'); + $query->bindValue(':method', $method); + $query->bindValue(':content', $content); + $query->bindValue(':id', $id); + $query->execute() or error(db_error($query)); + + $fn = ($board['uri'] ? ($board['uri'] . '/') : '') . $page['name'] . '.html'; + $body = "
$write
"; + $html = Element('page.html', array('config' => $config, 'body' => $body, 'title' => utf8tohtml($page['title']))); + file_write($fn, $html); + } + + if (!isset($content)) { + $query = prepare('SELECT `content` FROM ``pages`` WHERE `id` = :id'); + $query->bindValue(':id', $id); + $query->execute() or error(db_error($query)); + $content = $query->fetchColumn(); + } + + mod_page(sprintf(_('Editing static page: %s'), $page['name']), 'mod/edit_page.html', array('page' => $page, 'token' => make_secure_link_token("edit_page/$id"), 'content' => prettify_textarea($content), 'board' => $board)); +} + +function mod_pages($board = false) { + global $config, $mod, $pdo; + + if (empty($board)) + $board = false; + + if (!$board && $mod['boards'][0] !== '*') + error($config['error']['noaccess']); + + if (!hasPermission($config['mod']['edit_pages'], $board)) + error($config['error']['noaccess']); + + if ($board !== FALSE && !openBoard($board)) + error($config['error']['noboard']); + + if ($board) { + $query = prepare('SELECT * FROM ``pages`` WHERE `board` = :board'); + $query->bindValue(':board', $board); + } else { + $query = query('SELECT * FROM ``pages`` WHERE `board` IS NULL'); + } + $query->execute() or error(db_error($query)); + $pages = $query->fetchAll(PDO::FETCH_ASSOC); + + if (isset($_POST['page'])) { + if ($board and sizeof($pages) > $config['pages_max']) + error(sprintf(_('Sorry, this site only allows %d pages per board.'), $config['pages_max'])); + + if (!preg_match('/^[a-z0-9]{1,255}$/', $_POST['page'])) + error(_('Page names must be < 255 chars and may only contain lowercase letters A-Z and digits 1-9.')); + + foreach ($pages as $i => $p) { + if ($_POST['page'] === $p['name']) + error(_('Refusing to create a new page with the same name as an existing one.')); + } + + $title = ($_POST['title'] ? $_POST['title'] : NULL); + + $query = prepare('INSERT INTO ``pages``(board, title, name) VALUES(:board, :title, :name)'); + $query->bindValue(':board', ($board ? $board : NULL)); + $query->bindValue(':title', $title); + $query->bindValue(':name', $_POST['page']); + $query->execute() or error(db_error($query)); + + $pages[] = array('id' => $pdo->lastInsertId(), 'name' => $_POST['page'], 'board' => $board, 'title' => $title); + } + + foreach ($pages as $i => &$p) { + $p['delete_token'] = make_secure_link_token('edit_pages/delete/' . $p['name'] . ($board ? ('/' . $board) : '')); + } + + mod_page(_('Pages'), 'mod/pages.html', array('pages' => $pages, 'token' => make_secure_link_token('edit_pages' . ($board ? ('/' . $board) : '')), 'board' => $board)); +} + function mod_debug_antispam() { global $pdo, $config; @@ -2744,3 +2905,4 @@ function mod_debug_apc() { mod_page(_('Debug: APC'), 'mod/debug/apc.html', array('cached_vars' => $cached_vars)); } + diff --git a/install.sql b/install.sql index 024cb349..720e4ed5 100644 --- a/install.sql +++ b/install.sql @@ -245,7 +245,7 @@ CREATE TABLE IF NOT EXISTS `search_queries` ( `ip` varchar(39) NOT NULL, `time` int(11) NOT NULL, `query` text NOT NULL -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; -- -------------------------------------------------------- @@ -297,6 +297,24 @@ CREATE TABLE IF NOT EXISTS `ban_appeals` ( KEY `ban_id` (`ban_id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ; +-- -------------------------------------------------------- + +-- +-- Table structure for table `pages` +-- + +CREATE TABLE `pages` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `board` varchar(255) DEFAULT NULL, + `name` varchar(255) NOT NULL, + `title` varchar(255) DEFAULT NULL, + `type` varchar(255) DEFAULT NULL, + `content` text, + PRIMARY KEY (`id`), + UNIQUE KEY `u_pages` (`name`,`board`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; + +>>>>>>> 12fa8ec... Edit static pages commit /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/mod.php b/mod.php index a6ff00cf..fbe679ac 100644 --- a/mod.php +++ b/mod.php @@ -33,11 +33,19 @@ $pages = array( '/log' => 'log', // modlog '/log/(\d+)' => 'log', // modlog - '/log:([^/]+)' => 'user_log', // modlog - '/log:([^/]+)/(\d+)' => 'user_log', // modlog - '/news' => 'secure_POST news', // view news - '/news/(\d+)' => 'secure_POST news', // view news - '/news/delete/(\d+)' => 'secure news_delete', // delete from news + '/log:([^/:]+)' => 'user_log', // modlog + '/log:([^/:]+)/(\d+)' => 'user_log', // modlog + '/log:b:([^/]+)' => 'board_log', // modlog + '/log:b:([^/]+)/(\d+)' => 'board_log', // modlog + + '/edit_news' => 'secure_POST news', // view news + '/edit_news/(\d+)' => 'secure_POST news', // view news + '/edit_news/delete/(\d+)' => 'secure news_delete', // delete from news + + '/edit_pages(?:/?(\%b)?)' => 'secure_POST pages', + '/edit_page/(\d+)' => 'secure_POST edit_page', + '/edit_pages/delete/([a-z0-9]+)' => 'secure delete_page', + '/edit_pages/delete/([a-z0-9]+)/(\%b)' => 'secure delete_page_board', '/noticeboard' => 'secure_POST noticeboard', // view noticeboard '/noticeboard/(\d+)' => 'secure_POST noticeboard', // view noticeboard diff --git a/templates/mod/edit_page.html b/templates/mod/edit_page.html new file mode 100644 index 00000000..3d132767 --- /dev/null +++ b/templates/mod/edit_page.html @@ -0,0 +1,29 @@ +
+
+ + + + + + +
{% trans %}Markup method{% endtrans %} + {% set allowed_html = config.allowed_html %} + {% trans %}

"markdown" is provided by parsedown. Note: images disabled.

+

"html" allows the following tags:
{{ allowed_html }}

+

"infinity" is the same as what is used in posts.

+

This page will not convert between formats,
choose it once or do the conversion yourself!

{% endtrans %} +
+ +
{% trans %}Page content{% endtrans %} +
+ {% trans %}Page will appear at:{% endtrans %} + {% if board %} {{ config.domain }}/{{ board.uri }}/{{ page.name }}.html + {% else %} {{ config.site }}/{{ page.name }}.html + {% endif %}
+ +
+
diff --git a/templates/mod/pages.html b/templates/mod/pages.html new file mode 100644 index 00000000..c2395c02 --- /dev/null +++ b/templates/mod/pages.html @@ -0,0 +1,34 @@ + +
+

+{% if board %} +{% set page_max = config.pages_max %} +{% trans %}This page allows you to create static pages for your board. The limit is {{ page_max }} pages per board. You will still have to link to your pages somewhere in your board, for example in a sticky or in the board's announcement. To make links in the board's announcement, use <a> HTML tags.{% endtrans %} +{% else %} +{% trans %}This page allows you to create static pages for your imageboard.{% endtrans %} +{% endif %} +

{% trans %}Existing pages{% endtrans %}

+{% if pages %} +
+ + +{% for page in pages %} + +{% endfor %} +{% else %} +No pages yet! +{% endif %} +
{% trans %}URL{% endtrans %}{% trans %}Title{% endtrans %}{% trans %}Edit{% endtrans %}{% trans %}Delete{% endtrans %}
{{ page.name }}{{ page.title }}{% trans %}Edit{% endtrans %}{% trans %}Delete{% endtrans %}
+
+
+

{% trans %}Create a new page{% endtrans %}

+
+ + + + +
{% trans %}URL{% endtrans %}{% trans %}Title{% endtrans %}
+ +
+ +
diff --git a/tools/import_rules.php b/tools/import_rules.php new file mode 100644 index 00000000..f583f035 --- /dev/null +++ b/tools/import_rules.php @@ -0,0 +1,16 @@ + $b) { + $rules = @file_get_contents($b.'/rules.txt'); + if ($rules && !empty(trim($rules))) { + $query = prepare('INSERT INTO ``pages``(name, title, type, board, content) VALUES("rules", "Rules", "html", :board, :content)'); + $query->bindValue(':board', $b); + $query->bindValue(':content', $rules); + $query->execute() or error(db_error($query)); + } +} -- cgit v1.3 From d726eaf195a18a35c44fe0b08aa0960e4a79e272 Mon Sep 17 00:00:00 2001 From: czaks Date: Fri, 6 May 2016 16:07:21 +0200 Subject: we don't have a htmlpurifier yet ;_; --- inc/functions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'inc/functions.php') diff --git a/inc/functions.php b/inc/functions.php index 7970d05a..845c4cb3 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -2745,7 +2745,7 @@ function prettify_textarea($s){ return str_replace("\t", ' ', str_replace("\n", ' ', htmlentities($s))); } -class HTMLPurifier_URIFilter_NoExternalImages extends HTMLPurifier_URIFilter { +/*class HTMLPurifier_URIFilter_NoExternalImages extends HTMLPurifier_URIFilter { public $name = 'NoExternalImages'; public function filter(&$uri, $c, $context) { global $config; @@ -2761,7 +2761,7 @@ class HTMLPurifier_URIFilter_NoExternalImages extends HTMLPurifier_URIFilter { return true; } -} +}*/ function purify_html($s) { global $config; -- cgit v1.3 From ab02a4272514410fc053ced9c17a26bc0c8f1cf4 Mon Sep 17 00:00:00 2001 From: czaks Date: Fri, 6 May 2016 16:27:43 +0200 Subject: maybe we can try to load Parsedown, after all we can silence the error --- inc/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'inc/functions.php') diff --git a/inc/functions.php b/inc/functions.php index 845c4cb3..e9692d56 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -20,7 +20,7 @@ require_once 'inc/events.php'; require_once 'inc/api.php'; require_once 'inc/mod/auth.php'; require_once 'inc/polyfill.php'; -//require_once 'inc/lib/parsedown/Parsedown.php'; // we don't need that right now, do we? +@include_once 'inc/lib/parsedown/Parsedown.php'; // fail silently, this isn't a critical piece of code if (!extension_loaded('gettext')) { require_once 'inc/lib/gettext/gettext.inc'; -- cgit v1.3 From 505adffcdca1c1141b7b46f862b7939f2f4acf45 Mon Sep 17 00:00:00 2001 From: Fredrick Brennan Date: Fri, 3 Apr 2015 14:56:28 +0800 Subject: Cyclical threads ♺ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- inc/api.php | 1 + inc/config.php | 5 +++++ inc/functions.php | 8 +++++++- inc/mod/pages.php | 22 ++++++++++++++++++++++ install.php | 7 ++++++- mod.php | 1 + post.php | 11 ++++++++++- templates/post/post_controls.html | 8 +++++++- templates/post_thread.html | 13 ++++++++++--- templates/posts.sql | 1 + 10 files changed, 70 insertions(+), 7 deletions(-) (limited to 'inc/functions.php') diff --git a/inc/api.php b/inc/api.php index b2d8adec..b280c25b 100644 --- a/inc/api.php +++ b/inc/api.php @@ -32,6 +32,7 @@ class Api { 'images' => 'images', 'sticky' => 'sticky', 'locked' => 'locked', + 'cycle' => 'cyclical', 'bump' => 'last_modified', 'embed' => 'embed', ); diff --git a/inc/config.php b/inc/config.php index 3fdb8d2f..82fdc882 100644 --- a/inc/config.php +++ b/inc/config.php @@ -1245,6 +1245,8 @@ $config['mod']['link_bumpunlock'] = '[-Sage]'; $config['mod']['link_editpost'] = '[Edit]'; $config['mod']['link_move'] = '[Move]'; + $config['mod']['link_cycle'] = '[Cycle]'; + $config['mod']['link_uncycle'] = '[-Cycle]'; // Moderator capcodes. $config['capcode'] = ' ## %s'; @@ -1388,6 +1390,9 @@ $config['mod']['deletebyip_global'] = ADMIN; // Sticky a thread $config['mod']['sticky'] = MOD; + // Cycle a thread + $config['mod']['cycle'] = MOD; + $config['cycle_limit'] = &$config['reply_limit']; // Lock a thread $config['mod']['lock'] = MOD; // Post in a locked thread diff --git a/inc/functions.php b/inc/functions.php index e9692d56..1bd1f32f 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -1021,7 +1021,7 @@ function insertFloodPost(array $post) { 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, :files, :num_files, :filehash, :password, :ip, :sticky, :locked, 0, :embed, :slug)", $board['uri'])); + $query = prepare(sprintf("INSERT INTO ``posts_%s`` VALUES ( NULL, :thread, :subject, :email, :name, :trip, :capcode, :body, :body_nomarkup, :time, :time, :files, :num_files, :filehash, :password, :ip, :sticky, :locked, :cycle, 0, :embed, :slug)", $board['uri'])); // Basic stuff if (!empty($post['subject'])) { @@ -1061,6 +1061,12 @@ function post(array $post) { $query->bindValue(':locked', false, PDO::PARAM_INT); } + if ($post['op'] && $post['mod'] && isset($post['cycle']) && $post['cycle']) { + $query->bindValue(':cycle', true, PDO::PARAM_INT); + } else { + $query->bindValue(':cycle', false, PDO::PARAM_INT); + } + if ($post['mod'] && isset($post['capcode']) && $post['capcode']) { $query->bindValue(':capcode', $post['capcode'], PDO::PARAM_INT); } else { diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 6d1e20b4..303fa3dd 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -1089,6 +1089,28 @@ function mod_sticky($board, $unsticky, $post) { header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']); } +function mod_cycle($board, $uncycle, $post) { + global $config; + + if (!openBoard($board)) + error($config['error']['noboard']); + + if (!hasPermission($config['mod']['cycle'], $board)) + error($config['error']['noaccess']); + + $query = prepare(sprintf('UPDATE ``posts_%s`` SET `cycle` = :cycle WHERE `id` = :id AND `thread` IS NULL', $board)); + $query->bindValue(':id', $post); + $query->bindValue(':cycle', $uncycle ? 0 : 1); + $query->execute() or error(db_error($query)); + if ($query->rowCount()) { + modLog(($uncycle ? 'Made not cyclical' : 'Made cyclical') . " thread #{$post}"); + buildThread($post); + buildIndex(); + } + + header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']); +} + function mod_bumplock($board, $unbumplock, $post) { global $config; diff --git a/install.php b/install.php index 5a6512ba..de4d3dd0 100644 --- a/install.php +++ b/install.php @@ -1,7 +1,7 @@ vichan upgrade path. query("CREATE TABLE IF NOT EXISTS ``search_queries`` ( `ip` varchar(39) NOT NULL, `time` int(11) NOT NULL, `query` text NOT NULL) ENGINE=MyISAM DEFAULT CHARSET=utf8;") or error(db_error()); diff --git a/mod.php b/mod.php index fbe679ac..0378d55c 100644 --- a/mod.php +++ b/mod.php @@ -82,6 +82,7 @@ $pages = array( '/(\%b)/deletebyip/(\d+)(/global)?' => 'secure deletebyip', // delete all posts by IP address '/(\%b)/(un)?lock/(\d+)' => 'secure lock', // lock thread '/(\%b)/(un)?sticky/(\d+)' => 'secure sticky', // sticky thread + '/(\%b)/(un)?cycle/(\d+)' => 'secure cycle', // cycle thread '/(\%b)/bump(un)?lock/(\d+)' => 'secure bumplock', // "bumplock" thread '/themes' => 'themes_list', // manage themes diff --git a/post.php b/post.php index 0d3292bb..98aefe05 100644 --- a/post.php +++ b/post.php @@ -265,7 +265,7 @@ if (isset($_POST['delete'])) { //Check if thread exists if (!$post['op']) { - $query = prepare(sprintf("SELECT `sticky`,`locked`,`sage`,`slug` FROM ``posts_%s`` WHERE `id` = :id AND `thread` IS NULL LIMIT 1", $board['uri'])); + $query = prepare(sprintf("SELECT `sticky`,`locked`,`cycle`,`sage`,`slug` FROM ``posts_%s`` WHERE `id` = :id AND `thread` IS NULL LIMIT 1", $board['uri'])); $query->bindValue(':id', $post['thread'], PDO::PARAM_INT); $query->execute() or error(db_error()); @@ -867,6 +867,15 @@ if (isset($_POST['delete'])) { $post['slug'] = slugify($post); insertFloodPost($post); + + // Handle cyclical threads + if (!$post['op'] && isset($thread['cycle']) && $thread['cycle']) { + // Query is a bit weird due to "This version of MariaDB doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'" (MariaDB Ver 15.1 Distrib 10.0.17-MariaDB, for Linux (x86_64)) + $query = prepare(sprintf('DELETE FROM ``posts_%s`` WHERE `thread` = :thread AND `id` NOT IN (SELECT `id` FROM (SELECT `id` FROM ``posts_%s`` WHERE `thread` = :thread ORDER BY `id` DESC LIMIT :limit) i)', $board['uri'], $board['uri'])); + $query->bindValue(':thread', $post['thread']); + $query->bindValue(':limit', $config['cycle_limit'], PDO::PARAM_INT); + $query->execute() or error(db_error($query)); + } if (isset($post['antispam_hash'])) { incrementSpamHash($post['antispam_hash']); diff --git a/templates/post/post_controls.html b/templates/post/post_controls.html index 99a8484c..60dc6fc4 100644 --- a/templates/post/post_controls.html +++ b/templates/post/post_controls.html @@ -41,7 +41,6 @@ {% endif %} {% endif %} - {% if mod|hasPermission(config.mod.move, board.uri) %} {% if not post.thread %} {{ config.mod.link_move }}  @@ -49,6 +48,13 @@ {{ config.mod.link_move }}  {% endif %} {% endif %} +{% if mod|hasPermission(config.mod.cycle, board.uri) %} + {% if post.cycle %} + {{ config.mod.link_uncycle }}  + {% else %} + {{ config.mod.link_cycle }}  + {% endif %} +{% endif %} {% if mod|hasPermission(config.mod.editpost, board.uri) %} {{ config.mod.link_editpost }}  {% endif %} diff --git a/templates/post_thread.html b/templates/post_thread.html index 72ca2998..125d5f78 100644 --- a/templates/post_thread.html +++ b/templates/post_thread.html @@ -19,25 +19,32 @@ {{ post.id }} {% if post.sticky %} {% if config.font_awesome %} - + {% else %} Sticky {% endif %} {% endif %} {% if post.locked %} {% if config.font_awesome %} - + {% else %} Locked {% endif %} {% endif %} {% if post.bumplocked and (config.mod.view_bumplock < 0 or (post.mod and post.mod|hasPermission(config.mod.view_bumplock, board.uri))) %} {% if config.font_awesome %} - + {% else %} Bumplocked {% endif %} {% endif %} + {% if post.cycle %} + {% if config.font_awesome %} + + {% else %} + Cyclical + {% endif %} + {% endif %} {% if index %} [{% trans %}Reply{% endtrans %}] {% endif %} diff --git a/templates/posts.sql b/templates/posts.sql index 6b2249ef..070e687b 100644 --- a/templates/posts.sql +++ b/templates/posts.sql @@ -17,6 +17,7 @@ CREATE TABLE IF NOT EXISTS ``posts_{{ board }}`` ( `ip` varchar(39) CHARACTER SET ascii NOT NULL, `sticky` int(1) NOT NULL, `locked` int(1) NOT NULL, + `cycle` int(1) NOT NULL, `sage` int(1) NOT NULL, `embed` text, `slug` varchar(256) DEFAULT NULL, -- cgit v1.3 From b6f0317bde1b6d9a9ac5d81a660865a7aa9ad13d Mon Sep 17 00:00:00 2001 From: czaks Date: Sun, 8 May 2016 10:54:30 +0200 Subject: advanced build (1/2): a small refactor of index generating procedure; generation strategies --- inc/config.php | 70 ++++++++++++++++++++++--- inc/functions.php | 102 +++++++++++++++++++++++++++---------- inc/route.php | 5 +- smart_build.php | 9 +++- templates/themes/catalog/theme.php | 13 +++-- templates/themes/recent/theme.php | 5 +- templates/themes/sitemap/theme.php | 6 ++- templates/themes/ukko/theme.php | 6 ++- 8 files changed, 169 insertions(+), 47 deletions(-) (limited to 'inc/functions.php') diff --git a/inc/config.php b/inc/config.php index 82fdc882..91804429 100644 --- a/inc/config.php +++ b/inc/config.php @@ -1203,16 +1203,74 @@ // Try not to build pages when we shouldn't have to. $config['try_smarter'] = true; - // EXPERIMENTAL: Defer static HTML building to a moment, when a given file is actually accessed. - // Warning: This option won't run out of the box. You need to tell your webserver, that a file - // for serving 403 and 404 pages is /smart_build.php. Also, you need to turn off indexes. +/* + * ==================== + * Advanced build + * ==================== + */ + + // Strategies for file generation. Also known as an "advanced build". If you don't have performance + // issues, you can safely ignore that part, because it's hard to configure and won't even work on + // your free webhosting. + // + // A strategy is a function, that given the PHP environment and ($fun, $array) variable pair, returns + // an $action array or false. + // + // $fun - a controller function name, see inc/controller.php. This is named after functions, so that + // we can generate the files in daemon. + // + // $array - arguments to be passed + // + // $action - action to be taken. It's an array, and the first element of it is one of the following: + // * "immediate" - generate the page immediately + // * "defer" - defer page generation to a moment a worker daemon gets to build it (serving a stale + // page in the meantime). The remaining arguments are daemon-specific. Daemon isn't + // implemented yet :DDDD inb4 while(true) { generate(Queue::Get()) }; (which is probably it). + // * "build_on_load" - defer page generation to a moment, when the user actually accesses the page. + // This is a smart_build behaviour. You shouldn't use this one too much, if you + // use it for active boards, the server may choke due to a possible race condition. + // See my blog post: https://engine.vichan.net/blog/res/2.html + // + // So, let's assume we want to build a thread 1324 on board /b/, because a new post appeared there. + // We try the first strategy, giving it arguments: 'sb_thread', array('b', 1324). The strategy will + // now return a value $action, denoting an action to do. If $action is false, we try another strategy. + // + // As I said, configuration isn't easy. + // + // 1. chmod 0777 directories: tmp/locks/ and tmp/queue/. + // 2. serve 403 and 404 requests to go thru smart_build.php + // for nginx, this blog post contains config snippets: https://engine.vichan.net/blog/res/2.html + // 3. disable indexes in your webserver + // 4. launch any number of daemons (eg. twice your number of threads?) using the command: + // $ tools/worker.php + // You don't need to do that step if you are not going to use the "defer" option. + // 5. enable smart_build_helper (see below) + // 6. edit the strategies (see inc/functions.php for the builtin ones). You can use lambdas. I will test + // various ones and include one that works best for me. + $config['generation_strategies'] = array(); + // Add a sane strategy. It forces to immediately generate a page user is about to land on. Otherwise, + // it has no opinion, so it needs a fallback strategy. + $config['generation_strategies'][] = 'strategy_sane'; + // Add an immediate catch-all strategy. This is the default function of imageboards: generate all pages + // on post time. + $config['generation_strategies'][] = 'strategy_immediate'; + // NOT RECOMMENDED: Instead of an all-"immediate" strategy, you can use an all-"build_on_load" one (used + // to be initialized using $config['smart_build']; ) for all pages instead of those to be build + // immediately. A rebuild done in this mode should remove all your static files + // $config['generation_strategies'][1] = 'strategy_smart_build'; + + // Deprecated. Leave it false. See above. $config['smart_build'] = false; - // Smart build related: when a file doesn't exist, where should we redirect? + // Use smart_build.php for dispatching missing requests. It may be useful without smart_build or advanced + // build, for example it will regenerate the missing files. + $config['smart_build_helper'] = true; + + // smart_build.php: when a file doesn't exist, where should we redirect? $config['page_404'] = '/404.html'; - // Smart build related: extra entrypoints. - $config['smart_build_entrypoints'] = array(); + // Extra controller entrypoints. Controller is used only by smart_build and advanced build. + $config['controller_entrypoints'] = array(); /* * ==================== diff --git a/inc/functions.php b/inc/functions.php index 1bd1f32f..2957c680 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -1319,7 +1319,8 @@ function thread_find_page($thread) { return floor(($config['threads_per_page'] + $index) / $config['threads_per_page']); } -function index($page, $mod=false) { +// $brief means that we won't need to generate anything yet +function index($page, $mod=false, $brief = false) { global $board, $config, $debug; $body = ''; @@ -1350,6 +1351,7 @@ function index($page, $mod=false) { unset($cached); } } + if (!isset($cached)) { $posts = prepare(sprintf("SELECT * FROM ``posts_%s`` WHERE `thread` = :id ORDER BY `id` DESC LIMIT :limit", $board['uri'])); $posts->bindValue(':id', $th['id']); @@ -1389,7 +1391,10 @@ function index($page, $mod=false) { } $threads[] = $thread; - $body .= $thread->build(true); + + if (!$brief) { + $body .= $thread->build(true); + } } if ($config['file_board']) { @@ -1610,27 +1615,28 @@ function checkMute() { function buildIndex($global_api = "yes") { global $board, $config, $build_pages; - if (!$config['smart_build']) { - $pages = getPages(); - if (!$config['try_smarter']) - $antibot = create_antibot($board['uri']); + $catalog_api_action = generation_strategy('sb_api', array($board['uri'])); - if ($config['api']['enabled']) { - $api = new Api(); - $catalog = array(); - } + $pages = null; + $antibot = null; + + if ($config['api']['enabled']) { + $api = new Api(); + $catalog = array(); } for ($page = 1; $page <= $config['max_pages']; $page++) { $filename = $board['dir'] . ($page == 1 ? $config['file_index'] : sprintf($config['file_page'], $page)); $jsonFilename = $board['dir'] . ($page - 1) . '.json'; // pages should start from 0 - if ((!$config['api']['enabled'] || $global_api == "skip" || $config['smart_build']) && $config['try_smarter'] - && isset($build_pages) && !empty($build_pages) && !in_array($page, $build_pages) ) + $wont_build_this_page = $config['try_smarter'] && isset($build_pages) && !empty($build_pages) && !in_array($page, $build_pages); + + if ((!$config['api']['enabled'] || $global_api == "skip") && $wont_build_this_page) continue; - if (!$config['smart_build']) { - $content = index($page); + $action = generation_strategy('sb_board', array($board['uri'], $page)); + if ($action == 'rebuild' || $catalog_api_action == 'rebuild') { + $content = index($page, false, $wont_build_this_page); if (!$content) break; @@ -1641,17 +1647,21 @@ function buildIndex($global_api = "yes") { file_write($jsonFilename, $json); $catalog[$page-1] = $threads; - } - if ($config['api']['enabled'] && $global_api != "skip" && $config['try_smarter'] && isset($build_pages) - && !empty($build_pages) && !in_array($page, $build_pages) ) - continue; + if ($wont_build_this_page) continue; + } if ($config['try_smarter']) { $antibot = create_antibot($board['uri'], 0 - $page); $content['current_page'] = $page; } + elseif (!$antibot) { + create_antibot($board['uri']); + } $antibot->reset(); + if (!$pages) { + $pages = getPages(); + } $content['pages'] = $pages; $content['pages'][$page-1]['selected'] = true; $content['btn'] = getPageButtons($content['pages']); @@ -1659,13 +1669,14 @@ function buildIndex($global_api = "yes") { file_write($filename, Element('index.html', $content)); } - else { + elseif ($action == 'delete' || $catalog_api_action == 'delete') { file_unlink($filename); file_unlink($jsonFilename); } } - if (!$config['smart_build'] && $page < $config['max_pages']) { + // $action is an action for our last page + if (($catalog_api_action == 'rebuild' || $action == 'rebuild' || $action == 'delete') && $page < $config['max_pages']) { for (;$page<=$config['max_pages'];$page++) { $filename = $board['dir'] . ($page==1 ? $config['file_index'] : sprintf($config['file_page'], $page)); file_unlink($filename); @@ -1679,13 +1690,13 @@ function buildIndex($global_api = "yes") { // json api catalog if ($config['api']['enabled'] && $global_api != "skip") { - if ($config['smart_build']) { + if ($catalog_api_action == 'delete') { $jsonFilename = $board['dir'] . 'catalog.json'; file_unlink($jsonFilename); $jsonFilename = $board['dir'] . 'threads.json'; file_unlink($jsonFilename); } - else { + elseif ($catalog_api_action == 'rebuild') { $json = json_encode($api->translateCatalog($catalog)); $jsonFilename = $board['dir'] . 'catalog.json'; file_write($jsonFilename, $json); @@ -2204,7 +2215,9 @@ function buildThread($id, $return = false, $mod = false) { if ($config['try_smarter'] && !$mod) $build_pages[] = thread_find_page($id); - if (!$config['smart_build'] || $return || $mod) { + $action = generation_strategy('sb_thread', array($board['uri'], $id)); + + if ($action == 'rebuild' || $return || $mod) { $query = prepare(sprintf("SELECT * FROM ``posts_%s`` WHERE (`thread` IS NULL AND `id` = :id) OR `thread` = :id ORDER BY `thread`,`id`", $board['uri'])); $query->bindValue(':id', $id, PDO::PARAM_INT); $query->execute() or error(db_error($query)); @@ -2239,26 +2252,26 @@ function buildThread($id, $return = false, $mod = false) { )); // json api - if ($config['api']['enabled']) { + if ($config['api']['enabled'] && !$mod) { $api = new Api(); $json = json_encode($api->translateThread($thread)); $jsonFilename = $board['dir'] . $config['dir']['res'] . $id . '.json'; file_write($jsonFilename, $json); } } - else { + elseif($action == 'delete') { $jsonFilename = $board['dir'] . $config['dir']['res'] . $id . '.json'; file_unlink($jsonFilename); } - if ($config['smart_build'] && !$return && !$mod) { + if ($action == 'delete' && !$return && !$mod) { $noko50fn = $board['dir'] . $config['dir']['res'] . link_for(array('id' => $id), true); file_unlink($noko50fn); file_unlink($board['dir'] . $config['dir']['res'] . link_for(array('id' => $id))); - } else if ($return) { + } elseif ($return) { return $body; - } else { + } elseif ($action == 'rebuild') { $noko50fn = $board['dir'] . $config['dir']['res'] . link_for($thread, true); if ($hasnoko50 || file_exists($noko50fn)) { buildThread50($id, $return, $mod, $thread, $antibot); @@ -2788,3 +2801,36 @@ function markdown($s) { return $pd->text($s); } + +function generation_strategy($fun, $array=array()) { global $config; + $action = false; + + foreach ($config['generation_strategies'] as $s) { + if ($strategy = $s($fun, $array)) { + break; + } + } + + switch ($strategy[0]) { + case 'immediate': + return 'rebuild'; + case 'defer': + // Ok, it gets interesting here :) + Queue::add(serialize(array('build', $fun, $array))); + return 'ignore'; + case 'build_on_load': + return 'delete'; + } +} + +function strategy_immediate($fun, $array) { + return array('immediate'); +} + +function strategy_smart_build($fun, $array) { + return array('build_on_load'); +} + +function strategy_sane($fun, $array) { global $config; + return false; +} diff --git a/inc/route.php b/inc/route.php index 66602d77..2a5c1732 100644 --- a/inc/route.php +++ b/inc/route.php @@ -7,7 +7,7 @@ defined('TINYBOARD') or exit; -function route($path) { +function route($path) { global $config; $entrypoints = array(); $entrypoints['/%b/'] = 'sb_board'; @@ -33,8 +33,11 @@ function route($path) { $entrypoints['/*/index.html'] = 'sb_ukko'; $entrypoints['/recent.html'] = 'sb_recent'; $entrypoints['/%b/catalog.html'] = 'sb_catalog'; + $entrypoints['/%b/index.rss'] = 'sb_catalog'; $entrypoints['/sitemap.xml'] = 'sb_sitemap'; + $entrypoints = array_merge($entrypoints, $config['controller_entrypoints']); + $reached = false; list($request) = explode('?', $path); diff --git a/smart_build.php b/smart_build.php index 58596055..7ca5fcbf 100644 --- a/smart_build.php +++ b/smart_build.php @@ -3,14 +3,16 @@ require_once("inc/functions.php"); require_once("inc/route.php"); require_once("inc/controller.php"); -if (!$config['smart_build'] && !$config["smart_build_helper"]) { - die('You need to enable $config["smart_build"] or $config["smart_build_helper"]'); +if (!$config["smart_build_helper"]) { + die('You need to enable $config["smart_build_helper"]'); } $config['smart_build'] = false; // Let's disable it, so we can build the page for real +$config['generation_strategies'] = array('strategy_immediate'); function after_open_board() { global $config; $config['smart_build'] = false; + $config['generation_strategies'] = array('strategy_immediate'); }; $request = $_SERVER['REQUEST_URI']; @@ -59,6 +61,9 @@ if ($reached) { elseif (preg_match('/\.xml$/', $request)) { header("Content-Type", "application/xml"); } + elseif (preg_match('/\.rss$/', $request)) { + header("Content-Type", "application/rss+xml"); + } else { header("Content-Type", "text/html; charset=utf-8"); } diff --git a/templates/themes/catalog/theme.php b/templates/themes/catalog/theme.php index 239d4dff..4f512c03 100644 --- a/templates/themes/catalog/theme.php +++ b/templates/themes/catalog/theme.php @@ -16,20 +16,25 @@ if ($action == 'all') { foreach ($boards as $board) { $b = new Catalog(); - if ($config['smart_build']) { + + $action = generation_strategy("sb_catalog", array($board)); + if ($action == 'delete') { file_unlink($config['dir']['home'] . $board . '/catalog.html'); + file_unlink($config['dir']['home'] . $board . '/index.rss'); } - else { + elseif ($action == 'rebuild') { $b->build($settings, $board); } } } elseif ($action == 'post-thread' || ($settings['update_on_posts'] && $action == 'post') || ($settings['update_on_posts'] && $action == 'post-delete') && in_array($board, $boards)) { $b = new Catalog(); - if ($config['smart_build']) { + $action = generation_strategy("sb_catalog", array($board)); + if ($action == 'delete') { file_unlink($config['dir']['home'] . $board . '/catalog.html'); + file_unlink($config['dir']['home'] . $board . '/index.rss'); } - else { + elseif ($action == 'rebuild') { $b->build($settings, $board); } } diff --git a/templates/themes/recent/theme.php b/templates/themes/recent/theme.php index f44e2529..95921c35 100644 --- a/templates/themes/recent/theme.php +++ b/templates/themes/recent/theme.php @@ -25,10 +25,11 @@ $this->excluded = explode(' ', $settings['exclude']); if ($action == 'all' || $action == 'post' || $action == 'post-thread' || $action == 'post-delete') { - if ($config['smart_build']) { + $action = generation_strategy('sb_recent', array()); + if ($action == 'delete') { file_unlink($config['dir']['home'] . $settings['html']); } - else { + elseif ($action == 'rebuild') { file_write($config['dir']['home'] . $settings['html'], $this->homepage($settings)); } } diff --git a/templates/themes/sitemap/theme.php b/templates/themes/sitemap/theme.php index 52779d53..6bc035bb 100644 --- a/templates/themes/sitemap/theme.php +++ b/templates/themes/sitemap/theme.php @@ -23,10 +23,12 @@ } } - if ($config['smart_build']) { + $action = generation_strategy('sb_sitemap', array()); + + if ($action == 'delete') { file_unlink($settings['path']); } - else { + elseif ($action == 'rebuild') { $boards = explode(' ', $settings['boards']); $threads = array(); diff --git a/templates/themes/ukko/theme.php b/templates/themes/ukko/theme.php index d6fc303c..e572c467 100644 --- a/templates/themes/ukko/theme.php +++ b/templates/themes/ukko/theme.php @@ -11,10 +11,12 @@ return; } - if ($config['smart_build']) { + $action = generation_strategy('sb_ukko', array()); + + if ($action == 'delete') { file_unlink($settings['uri'] . '/index.html'); } - else { + elseif ($action == 'rebuild') { file_write($settings['uri'] . '/index.html', $ukko->build()); } } -- cgit v1.3 From e2653754750917035b79e62b07d286b3a56cf9ca Mon Sep 17 00:00:00 2001 From: czaks Date: Sun, 8 May 2016 10:59:36 +0200 Subject: fixup --- inc/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'inc/functions.php') diff --git a/inc/functions.php b/inc/functions.php index 2957c680..8329e774 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -1656,7 +1656,7 @@ function buildIndex($global_api = "yes") { $content['current_page'] = $page; } elseif (!$antibot) { - create_antibot($board['uri']); + $antibot = create_antibot($board['uri']); } $antibot->reset(); if (!$pages) { -- cgit v1.3 From 12e6aba5d4a2965591b3df76e4f6c19e35475700 Mon Sep 17 00:00:00 2001 From: czaks Date: Sun, 8 May 2016 13:20:00 +0200 Subject: (2/2) advanced build. implement a daemon that will build static pages. implement a queue and a lock. fix notice in bans. and it even works! the daemon is basic right now, it could work in a mode that it will defer building certain pages until a certain time. --- inc/bans.php | 2 +- inc/config.php | 9 ++++++++- inc/functions.php | 40 +++++++++++++++++++++++++++++++++++----- smart_build.php | 2 +- tmp/queue/generate/.gitkeep | 0 tools/worker.php | 31 +++++++++++++++++++++++++++++++ 6 files changed, 76 insertions(+), 8 deletions(-) create mode 100644 tmp/queue/generate/.gitkeep create mode 100755 tools/worker.php (limited to 'inc/functions.php') diff --git a/inc/bans.php b/inc/bans.php index 87e06e28..c468eb64 100644 --- a/inc/bans.php +++ b/inc/bans.php @@ -166,7 +166,7 @@ class Bans { if ($ban['post']) { $post = json_decode($ban['post']); - $ban['message'] = $post->body; + $ban['message'] = isset($post->body) ? $post->body : 0; } unset($ban['ipstart'], $ban['ipend'], $ban['post'], $ban['creator']); diff --git a/inc/config.php b/inc/config.php index 91804429..5926eb1d 100644 --- a/inc/config.php +++ b/inc/config.php @@ -103,7 +103,7 @@ /* * ==================== - * Cache settings + * Cache, lock and queue settings * ==================== */ @@ -120,6 +120,7 @@ // $config['cache']['enabled'] = 'apc'; // $config['cache']['enabled'] = 'memcached'; // $config['cache']['enabled'] = 'redis'; + // $config['cache']['enabled'] = 'fs'; // Timeout for cached objects such as posts and HTML. $config['cache']['timeout'] = 60 * 60 * 48; // 48 hours @@ -142,6 +143,12 @@ // (this file will be explicitly loaded during cache hit, but not during cache miss). $config['cache_config'] = false; + // Define a lock driver. + $config['lock']['enabled'] = 'fs'; + + // Define a queue driver. + $config['queue']['enabled'] = 'fs'; // xD + /* * ==================== * Cookie settings diff --git a/inc/functions.php b/inc/functions.php index 8329e774..0cfdb72f 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -19,6 +19,8 @@ require_once 'inc/database.php'; require_once 'inc/events.php'; require_once 'inc/api.php'; require_once 'inc/mod/auth.php'; +require_once 'inc/lock.php'; +require_once 'inc/queue.php'; require_once 'inc/polyfill.php'; @include_once 'inc/lib/parsedown/Parsedown.php'; // fail silently, this isn't a critical piece of code @@ -93,6 +95,8 @@ function loadConfig() { 'db', 'api', 'cache', + 'lock', + 'queue', 'cookies', 'error', 'dir', @@ -1749,7 +1753,6 @@ function buildJavascript() { function checkDNSBL() { global $config; - if (isIPv6()) return; // No IPv6 support yet. @@ -2806,17 +2809,17 @@ function generation_strategy($fun, $array=array()) { global $config; $action = false; foreach ($config['generation_strategies'] as $s) { - if ($strategy = $s($fun, $array)) { + if ($action = $s($fun, $array)) { break; } } - switch ($strategy[0]) { + switch ($action[0]) { case 'immediate': return 'rebuild'; case 'defer': // Ok, it gets interesting here :) - Queue::add(serialize(array('build', $fun, $array))); + get_queue('generate')->push(serialize(array('build', $fun, $array, $action))); return 'ignore'; case 'build_on_load': return 'delete'; @@ -2832,5 +2835,32 @@ function strategy_smart_build($fun, $array) { } function strategy_sane($fun, $array) { global $config; - return false; + // Well, ideally a sane strategy would involve a more stringent checking, + // but let's at least have something to get the ball rolling :^) + + if (php_sapi_name() == 'cli') return false; + else if (isset($_POST['mod']) || isset($_POST['json_response'])) return false; + else if ($fun == 'sb_thread' || ($fun == 'sb_board' && $array[1] == 1)) return array('immediate'); + else return false; +} + +// My first, test strategy. +function strategy_first($fun, $array) { + switch ($fun) { + case 'sb_thread': + return array('defer'); + case 'sb_board': + if ($array[1] > 8) return array('build_on_load'); + else return array('defer'); + case 'sb_api': + return array('defer'); + case 'sb_catalog': + return array('defer'); + case 'sb_recent': + return array('build_on_load'); + case 'sb_sitemap': + return array('build_on_load'); + case 'sb_ukko': + return array('defer'); + } } diff --git a/smart_build.php b/smart_build.php index 7ca5fcbf..cfac446a 100644 --- a/smart_build.php +++ b/smart_build.php @@ -24,7 +24,7 @@ if (!$route) { } else { list ($fun, $args) = $route; - $reached = call_user_func_array($route); + $reached = call_user_func_array($fun, $args); } function die_404() { global $config; diff --git a/tmp/queue/generate/.gitkeep b/tmp/queue/generate/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tools/worker.php b/tools/worker.php new file mode 100755 index 00000000..e19fe1c6 --- /dev/null +++ b/tools/worker.php @@ -0,0 +1,31 @@ +#!/usr/bin/php +pop(2); + foreach ($q as $v) { + list($__, $func, $ary, $action) = unserialize($v); + echo "Starting to generate $func ".implode(" ", $ary)."... "; + + call_user_func_array($func, $ary); + + echo "done!\n"; + } + if (!$q) usleep(20000); // 0.02s +} -- cgit v1.3 From f24e0f98143528c7b2cd5c504899c5492f5d75fd Mon Sep 17 00:00:00 2001 From: czaks Date: Sun, 8 May 2016 14:01:55 +0200 Subject: optimize out openboard when we don`t need it. a big performance improvement too 🏎 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit also, don't call dnsbl for local ip addresses --- inc/functions.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'inc/functions.php') diff --git a/inc/functions.php b/inc/functions.php index 0cfdb72f..35be56b9 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -556,14 +556,19 @@ function setupBoard($array) { } function openBoard($uri) { - global $config, $build_pages; + global $config, $build_pages, $board; if ($config['try_smarter']) $build_pages = array(); - $board = getBoardInfo($uri); - if ($board) { - setupBoard($board); + // And what if we don't really need to change a board we have opened? + if (isset ($board) && isset ($board['uri']) && $board['uri'] == $uri) { + return true; + } + + $b = getBoardInfo($uri); + if ($b) { + setupBoard($b); if (function_exists('after_open_board')) { after_open_board(); @@ -1759,6 +1764,9 @@ function checkDNSBL() { if (!isset($_SERVER['REMOTE_ADDR'])) return; // Fix your web server configuration + if (preg_match("/^(::(ffff:)?)?(127\.|192\.168\.|10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|0\.|255\.)/", $_SERVER['REMOTE_ADDR'])) + return; // It's pointless to check for local IP addresses in dnsbls, isn't it? + if (in_array($_SERVER['REMOTE_ADDR'], $config['dnsbl_exceptions'])) return; -- cgit v1.3 From 52fe9bc873e2950606afefad82cd102ad0a4db5c Mon Sep 17 00:00:00 2001 From: czaks Date: Sun, 15 May 2016 15:52:41 +0200 Subject: fix sane_strategy for advanced build. should fix the ajax.js problem. --- inc/functions.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'inc/functions.php') diff --git a/inc/functions.php b/inc/functions.php index 35be56b9..154386b0 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -2843,12 +2843,10 @@ function strategy_smart_build($fun, $array) { } function strategy_sane($fun, $array) { global $config; - // Well, ideally a sane strategy would involve a more stringent checking, - // but let's at least have something to get the ball rolling :^) - if (php_sapi_name() == 'cli') return false; - else if (isset($_POST['mod']) || isset($_POST['json_response'])) return false; - else if ($fun == 'sb_thread' || ($fun == 'sb_board' && $array[1] == 1)) return array('immediate'); + else if (isset($_POST['mod'])) return false; + // Thread needs to be done instantly. Same with a board page, but only if posting a new thread. + else if ($fun == 'sb_thread' || ($fun == 'sb_board' && $array[1] == 1 && isset ($_POST['page']))) return array('immediate'); else return false; } -- cgit v1.3 From 8a46c7a0d54864d7f640e1972a23e78018368cc2 Mon Sep 17 00:00:00 2001 From: czaks Date: Thu, 9 Jun 2016 11:08:29 +0200 Subject: tesseract OCR support for spamfilters --- inc/config.php | 13 +++++++++++++ inc/functions.php | 2 +- post.php | 43 ++++++++++++++++++++++++++++++++++++++----- tmp/tesseract/.gitkeep | 0 4 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 tmp/tesseract/.gitkeep (limited to 'inc/functions.php') diff --git a/inc/config.php b/inc/config.php index 5926eb1d..ebe17572 100644 --- a/inc/config.php +++ b/inc/config.php @@ -824,6 +824,15 @@ // Set this to true if you're using Linux and you can execute `md5sum` binary. $config['gnu_md5'] = false; + // Use Tesseract OCR to retrieve text from images, so you can use it as a spamfilter. + $config['tesseract_ocr'] = false; + + // Tesseract parameters + $config['tesseract_params'] = ''; + + // Tesseract preprocess command + $config['tesseract_preprocess_command'] = 'convert -monochrome %s -'; + // Number of posts in a "View Last X Posts" page $config['noko50_count'] = 50; // Number of posts a thread needs before it gets a "View Last X Posts" page. @@ -1015,6 +1024,10 @@ // Minify Javascript using http://code.google.com/p/minify/. $config['minify_js'] = false; + // Dispatch thumbnail loading and image configuration with JavaScript. It will need a certain javascript + // code to work. + $config['javascript_image_dispatch'] = false; + /* * ==================== * Video embedding diff --git a/inc/functions.php b/inc/functions.php index 154386b0..5a8f6000 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -2695,7 +2695,7 @@ function slugify($post) { elseif (isset ($post['body_nomarkup']) && $post['body_nomarkup']) $slug = $post['body_nomarkup']; elseif (isset ($post['body']) && $post['body']) - $slug = strip_html($post['body']); + $slug = strip_tags($post['body']); // Fix UTF-8 first $slug = mb_convert_encoding($slug, "UTF-8", "UTF-8"); diff --git a/post.php b/post.php index 4ea08ce3..bf723e01 100644 --- a/post.php +++ b/post.php @@ -652,14 +652,14 @@ if (isset($_POST['delete'])) { $post['filehash'] = md5($allhashes); } } - + if (!hasPermission($config['mod']['bypass_filters'], $board['uri'])) { - require_once 'inc/filters.php'; - + require_once 'inc/filters.php'; + do_filters($post); } - - if ($post['has_file']) { + + if ($post['has_file']) { foreach ($post['files'] as $key => &$file) { if ($file['is_an_image']) { if ($config['ie_mime_type_detection'] !== false) { @@ -787,6 +787,34 @@ if (isset($_POST['delete'])) { $file['thumbwidth'] = $size[0]; $file['thumbheight'] = $size[1]; } + + if ($config['tesseract_ocr']) { // Let's OCR it! + $fname = $file['tmp_name']; + + if ($file['height'] > 500 || $file['width'] > 500) { + $fname = $file['thumb']; + } + + if ($fname == 'spoiler') { // We don't have that much CPU time, do we? + } + else { + $tmpname = "tmp/tesseract/".rand(0,10000000); + + // Preprocess command is an ImageMagick b/w quantization + $error = shell_exec_error(sprintf($config['tesseract_preprocess_command'], escapeshellarg($fname)) . " | " . + 'tesseract stdin '.escapeshellarg($tmpname).' '.$config['tesseract_params']); + $tmpname .= ".txt"; + + $value = @file_get_contents($tmpname); + @unlink($tmpname); + + if ($value && trim($value)) { + // This one has an effect, that the body is appended to a post body. So you can write a correct + // spamfilter. + $post['body_nomarkup'] .= "".htmlspecialchars($value).""; + } + } + } if (!isset($dont_copy_file) || !$dont_copy_file) { if (isset($file['file_tmp'])) { @@ -827,6 +855,11 @@ if (isset($_POST['delete'])) { } } + // Do filters again if OCRing + if ($config['tesseract_ocr'] && !hasPermission($config['mod']['bypass_filters'], $board['uri'])) { + do_filters($post); + } + if (!hasPermission($config['mod']['postunoriginal'], $board['uri']) && $config['robot_enable'] && checkRobot($post['body_nomarkup'])) { undoImage($post); if ($config['robot_mute']) { diff --git a/tmp/tesseract/.gitkeep b/tmp/tesseract/.gitkeep new file mode 100644 index 00000000..e69de29b -- cgit v1.3 From d2bb4a776f681ad47a8b44bc271c8f0b502ef1f1 Mon Sep 17 00:00:00 2001 From: czaks Date: Tue, 17 May 2016 09:53:09 +0200 Subject: fail gracefully on no thumbnail --- inc/functions.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'inc/functions.php') diff --git a/inc/functions.php b/inc/functions.php index 5a8f6000..e576cf99 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -1161,8 +1161,10 @@ function deleteFile($id, $remove_entirely_if_already=true, $file=null) { foreach ($files as $i => $f) { if (($file !== false && $i == $file) || $file === null) { // Delete thumbnail - file_unlink($board['dir'] . $config['dir']['thumb'] . $f->thumb); - unset($files[$i]->thumb); + if (isset ($f->thumb) && $f->thumb) { + file_unlink($board['dir'] . $config['dir']['thumb'] . $f->thumb); + unset($files[$i]->thumb); + } // Delete file file_unlink($board['dir'] . $config['dir']['img'] . $f->file); -- cgit v1.3 From 11cecf8452d724b1a61e82292cff1cc16a5403e7 Mon Sep 17 00:00:00 2001 From: czaks Date: Tue, 21 Jun 2016 05:03:44 +0200 Subject: Revert "[BUG] Image reject repost board option now also affects YT embeds" This reverts commit b476b660073adbe827836a7e7860b0fb3a061ff0. --- inc/functions.php | 47 ----------------------------------------------- post.php | 28 +--------------------------- 2 files changed, 1 insertion(+), 74 deletions(-) (limited to 'inc/functions.php') diff --git a/inc/functions.php b/inc/functions.php index e576cf99..043d4de7 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -2492,53 +2492,6 @@ function getPostByHashInThread($hash, $thread) { return false; } -function getPostByEmbed($embed) { - global $board, $config; - $matches = array(); - foreach ($config['embedding'] as &$e) { - if (preg_match($e[0], $embed, $matches) && isset($matches[1]) && !empty($matches[1])) { - $embed = '%'.$matches[1].'%'; - break; - } - } - - if (!isset($embed)) return false; - - $query = prepare(sprintf("SELECT `id`,`thread` FROM ``posts_%s`` WHERE `embed` LIKE :embed", $board['uri'])); - $query->bindValue(':embed', $embed, PDO::PARAM_STR); - $query->execute() or error(db_error($query)); - - if ($post = $query->fetch(PDO::FETCH_ASSOC)) { - return $post; - } - - return false; -} - -function getPostByEmbedInThread($embed, $thread) { - global $board, $config; - $matches = array(); - foreach ($config['embedding'] as &$e) { - if (preg_match($e[0], $embed, $matches) && isset($matches[1]) && !empty($matches[1])) { - $embed = '%'.$matches[1].'%'; - break; - } - } - - if (!isset($embed)) return false; - - $query = prepare(sprintf("SELECT `id`,`thread` FROM ``posts_%s`` WHERE `embed` = :embed AND ( `thread` = :thread OR `id` = :thread )", $board['uri'])); - $query->bindValue(':embed', $embed, PDO::PARAM_STR); - $query->bindValue(':thread', $thread, PDO::PARAM_INT); - $query->execute() or error(db_error($query)); - - if ($post = $query->fetch(PDO::FETCH_ASSOC)) { - return $post; - } - - return false; -} - function undoImage(array $post) { if (!$post['has_file'] || !isset($post['files'])) return; diff --git a/post.php b/post.php index 383154fb..aa1b8235 100644 --- a/post.php +++ b/post.php @@ -295,32 +295,6 @@ if (isset($_POST['delete'])) { if (!isset($post['embed'])) { error($config['error']['invalid_embed']); } - - if ($config['image_reject_repost']) { - if ($p = getPostByEmbed($post['embed'])) { - 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 = getPostByEmbedInThread($post['embed'], $post['thread'])) { - 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']['bypass_field_disable'], $board['uri'])) { @@ -853,7 +827,7 @@ if (isset($_POST['delete'])) { )); } } - } + } // Do filters again if OCRing if ($config['tesseract_ocr'] && !hasPermission($config['mod']['bypass_filters'], $board['uri'])) { -- cgit v1.3 From 0b19051891e3a45a913cef38f0921cf781a0817b Mon Sep 17 00:00:00 2001 From: czaks Date: Mon, 15 Aug 2016 04:13:26 +0200 Subject: fix a notice; increase waiting time for dns --- inc/functions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'inc/functions.php') diff --git a/inc/functions.php b/inc/functions.php index 043d4de7..1b336e12 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -892,7 +892,7 @@ function displayBan($ban) { Element('page.html', array( 'title' => _('Banned!'), 'config' => $config, - 'boardlist' => createBoardlist($mod), + 'boardlist' => createBoardlist(isset($mod) ? $mod : false), 'body' => Element('banned.html', array( 'config' => $config, 'ban' => $ban, @@ -2514,7 +2514,7 @@ function rDNS($ip_addr) { if (!$config['dns_system']) { $host = gethostbyaddr($ip_addr); } else { - $resp = shell_exec_error('host -W 1 ' . $ip_addr); + $resp = shell_exec_error('host -W 3 ' . $ip_addr); if (preg_match('/domain name pointer ([^\s]+)$/', $resp, $m)) $host = $m[1]; else -- cgit v1.3