From 08fd6ae474adfc4411346f222c150cf7f305f7a8 Mon Sep 17 00:00:00 2001 From: czaks Date: Wed, 1 Apr 2015 16:53:28 +0200 Subject: [PATCH 01/46] don`t rebuild a page, when not needed, even if it doesn`t exist --- inc/functions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 8674ecd0..86389e73 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -1507,7 +1507,7 @@ function buildIndex() { $filename = $board['dir'] . ($page == 1 ? $config['file_index'] : sprintf($config['file_page'], $page)); if (!$config['api']['enabled'] && $config['try_smarter'] && isset($build_pages) && !empty($build_pages) - && !in_array($page, $build_pages) && is_file($filename)) + && !in_array($page, $build_pages) ) continue; $content = index($page); if (!$content) @@ -1524,7 +1524,7 @@ function buildIndex() { } if ($config['api']['enabled'] && $config['try_smarter'] && isset($build_pages) && !empty($build_pages) - && !in_array($page, $build_pages) && is_file($filename)) + && !in_array($page, $build_pages) ) continue; if ($config['try_smarter']) { From 453ecfdc6b665c857a25b2bb4063164bd99ee914 Mon Sep 17 00:00:00 2001 From: czaks Date: Wed, 1 Apr 2015 16:56:17 +0200 Subject: [PATCH 02/46] unlink a .gz version of a file if it exists --- inc/functions.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/inc/functions.php b/inc/functions.php index 86389e73..68112c0e 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -630,6 +630,13 @@ function file_unlink($path) { } $ret = @unlink($path); + + if ($config['gzip_static']) { + $gzpath = "$path.gz"; + + @unlink($gzpath); + } + if (isset($config['purge']) && $path[0] != '/' && isset($_SERVER['HTTP_HOST'])) { // Purge cache if (basename($path) == $config['file_index']) { From 3f381ee569425ad7276ca94739abd15897bbaa70 Mon Sep 17 00:00:00 2001 From: czaks Date: Wed, 1 Apr 2015 17:07:24 +0200 Subject: [PATCH 03/46] add a global_api variable for buildIndex --- inc/functions.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 68112c0e..5a74bc09 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -1498,7 +1498,7 @@ function checkMute() { } } -function buildIndex() { +function buildIndex($global_api = "yes") { global $board, $config, $build_pages; $pages = getPages(); @@ -1513,8 +1513,8 @@ function buildIndex() { for ($page = 1; $page <= $config['max_pages']; $page++) { $filename = $board['dir'] . ($page == 1 ? $config['file_index'] : sprintf($config['file_page'], $page)); - if (!$config['api']['enabled'] && $config['try_smarter'] && isset($build_pages) && !empty($build_pages) - && !in_array($page, $build_pages) ) + if (!$config['api']['enabled'] && $global_api != "skip" && $config['try_smarter'] && isset($build_pages) + && !empty($build_pages) && !in_array($page, $build_pages) ) continue; $content = index($page); if (!$content) @@ -1530,8 +1530,8 @@ function buildIndex() { $catalog[$page-1] = $threads; } - if ($config['api']['enabled'] && $config['try_smarter'] && isset($build_pages) && !empty($build_pages) - && !in_array($page, $build_pages) ) + if ($config['api']['enabled'] && $global_api == "skip" && $config['try_smarter'] && isset($build_pages) + && !empty($build_pages) && !in_array($page, $build_pages) ) continue; if ($config['try_smarter']) { @@ -1560,7 +1560,7 @@ function buildIndex() { } // json api catalog - if ($config['api']['enabled']) { + if ($config['api']['enabled'] && $global_api != "skip") { $json = json_encode($api->translateCatalog($catalog)); $jsonFilename = $board['dir'] . 'catalog.json'; file_write($jsonFilename, $json); From b2f6c4c9568cd0571ecbb8d91a1ae2139e00f64c Mon Sep 17 00:00:00 2001 From: czaks Date: Wed, 1 Apr 2015 17:16:30 +0200 Subject: [PATCH 04/46] smart build: define configuration variables --- inc/config.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/inc/config.php b/inc/config.php index b8e7617d..6244479f 100644 --- a/inc/config.php +++ b/inc/config.php @@ -1239,9 +1239,20 @@ // Website favicon. $config['url_favicon'] = 'static/favicon.ico'; - // EXPERIMENTAL: Try not to build pages when we shouldn't have to. + // 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. + $config['smart_build'] = false; + + // Smart build related: 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(); + /* * ==================== * Mod settings From 854b9689b305cbd23ed4abdaf24de143d5607a53 Mon Sep 17 00:00:00 2001 From: czaks Date: Wed, 1 Apr 2015 17:30:06 +0200 Subject: [PATCH 05/46] fix one of the previous commits: fix api_global --- inc/functions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 5a74bc09..2473ed7c 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -1513,7 +1513,7 @@ function buildIndex($global_api = "yes") { for ($page = 1; $page <= $config['max_pages']; $page++) { $filename = $board['dir'] . ($page == 1 ? $config['file_index'] : sprintf($config['file_page'], $page)); - if (!$config['api']['enabled'] && $global_api != "skip" && $config['try_smarter'] && isset($build_pages) + if ((!$config['api']['enabled'] || $global_api == "skip") && $config['try_smarter'] && isset($build_pages) && !empty($build_pages) && !in_array($page, $build_pages) ) continue; $content = index($page); @@ -1530,7 +1530,7 @@ function buildIndex($global_api = "yes") { $catalog[$page-1] = $threads; } - if ($config['api']['enabled'] && $global_api == "skip" && $config['try_smarter'] && isset($build_pages) + if ($config['api']['enabled'] && $global_api != "skip" && $config['try_smarter'] && isset($build_pages) && !empty($build_pages) && !in_array($page, $build_pages) ) continue; From 1e5fb18f37c66fd7d14285312a08155384d404b7 Mon Sep 17 00:00:00 2001 From: czaks Date: Wed, 1 Apr 2015 17:50:59 +0200 Subject: [PATCH 06/46] smart_build.php: initial release --- smart_build.php | 139 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 smart_build.php diff --git a/smart_build.php b/smart_build.php new file mode 100644 index 00000000..6621238e --- /dev/null +++ b/smart_build.php @@ -0,0 +1,139 @@ + $config['max_pages']) return false; + $config['try_smarter'] = true; + $build_pages = array($page); + buildIndex("skip"); + return true; +} + +function sb_api_board($b, $page = 0) { $page = (int)$page; + return sb_board($b, $page + 1); +} + +function sb_thread($b, $thread) { global $config; $thread = (int)$thread; + if (!openBoard($b)) return false; + buildThread($thread); + return true; +} + +function sb_api($b) { global $config; + if (!openBoard($b)) return false; + $config['try_smarter'] = true; + $build_pages = array(-1); + buildIndex(); + return true; +} + +function sb_ukko() { + rebuildTheme("ukko", "post-thread"); + return true; +} + +function sb_catalog($b) { + rebuildTheme("catalog", "post-thread", $b); + return true; +} + +function sb_recent() { + rebuildTheme("recent", "post-thread"); + return true; +} + +$entrypoints = array(); + +$entrypoints['/%b/'] = 'sb_board'; +$entrypoints['/%b/'.$config['file_index']] = 'sb_board'; +$entrypoints['/%b/'.$config['file_page']] = 'sb_board'; +$entrypoints['/%b/%d.json'] = 'sb_api_board'; +if ($config['api']['enabled']) { + $entrypoints['/%b/threads.json'] = 'sb_api'; + $entrypoints['/%b/catalog.json'] = 'sb_api'; +} + +$entrypoints['/%b/'.$config['dir']['res'].$config['file_page']] = 'sb_thread'; +$entrypoints['/%b/'.$config['dir']['res'].$config['file_page50']] = 'sb_thread'; +if ($config['slugify']) { + $entrypoints['/%b/'.$config['dir']['res'].$config['file_page_slug']] = 'sb_thread'; + $entrypoints['/%b/'.$config['dir']['res'].$config['file_page50_slug']] = 'sb_thread'; +} +if ($config['api']['enabled']) { + $entrypoints['/%b/'.$config['dir']['res'].'%d.json'] = 'sb_thread'; +} + +$entrypoints['/*/'] = 'sb_ukko'; +$entrypoints['/*/index.html'] = 'sb_ukko'; +$entrypoints['/recent.html'] = 'sb_recent'; +$entrypoints['/%b/catalog.html'] = 'sb_catalog'; + +$reached = false; + +$request = $_SERVER['REQUEST_URI']; + +foreach ($entrypoints as $id => $fun) { + $id = '@^' . preg_quote($id, '@') . '$@u'; + + $id = str_replace('%b', '('.$config['board_regex'].')', $id); + $id = str_replace('%d', '([0-9]+)', $id); + $id = str_replace('%s', '[a-zA-Z0-9-]+', $id); + + $matches = null; + + if (preg_match ($id, $request, $matches)) { + array_shift($matches); + + $reached = call_user_func_array($fun, $matches); + + break; + } +} + +if ($reached) { + if ($request[strlen($request)-1] == '/') { + $request .= 'index.html'; + } + $request = '.'.$request; + + if (!file_exists($request)) { + header("Location: ".$config['page_404']); + die(); + } + + header("HTTP/1.1 200 OK"); + header("Status: 200 OK"); + if (preg_match('/\.json$/', $request)) { + header("Content-Type", "application/json"); + } + elseif (preg_match('/\.js$/', $request)) { + header("Content-Type", "text/javascript; charset=utf-8"); + } + else { + header("Content-Type", "text/html; charset=utf-8"); + } + header("Cache-Control: public, nocache, no-cache, max-age=0, must-revalidate"); + header("Expires: Fri, 22 Feb 1991 06:00:00 GMT"); + header("Last-Modified: ".date('r', filemtime($request))); + + //if (isset ($_SERVER['HTTP_ACCEPT_ENCODING']) && preg_match('/gzip/', $_SERVER['HTTP_ACCEPT_ENCODING']) && file_exists($request.".gz")) { + // header("Content-Encoding: gzip"); + // $file = fopen($request.".gz", 'r'); + //} + //else { + $file = fopen($request, 'r'); + //} + fpassthru($file); + fclose($file); +} +else { + header("Location: ".$config['page_404']); +} From 97e0380d658e89015cfd5048559bf71b025c50f5 Mon Sep 17 00:00:00 2001 From: czaks Date: Wed, 1 Apr 2015 18:11:08 +0200 Subject: [PATCH 07/46] smart_build for buildIndex --- inc/functions.php | 101 +++++++++++++++++++++++++++------------------- 1 file changed, 59 insertions(+), 42 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 2473ed7c..830b9761 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -1501,53 +1501,62 @@ function checkMute() { function buildIndex($global_api = "yes") { global $board, $config, $build_pages; - $pages = getPages(); - if (!$config['try_smarter']) - $antibot = create_antibot($board['uri']); + if (!$config['smart_build']) { + $pages = getPages(); + if (!$config['try_smarter']) + $antibot = create_antibot($board['uri']); - if ($config['api']['enabled']) { - $api = new Api(); - $catalog = array(); + 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['try_smarter'] && isset($build_pages) - && !empty($build_pages) && !in_array($page, $build_pages) ) - continue; - $content = index($page); - if (!$content) - break; - - // json api - if ($config['api']['enabled']) { - $threads = $content['threads']; - $json = json_encode($api->translatePage($threads)); - $jsonFilename = $board['dir'] . ($page - 1) . '.json'; // pages should start from 0 - 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) ) + if ((!$config['api']['enabled'] || $global_api == "skip" || $config['smart_build']) && $config['try_smarter'] + && isset($build_pages) && !empty($build_pages) && !in_array($page, $build_pages) ) continue; - if ($config['try_smarter']) { - $antibot = create_antibot($board['uri'], 0 - $page); - $content['current_page'] = $page; - } - $antibot->reset(); - $content['pages'] = $pages; - $content['pages'][$page-1]['selected'] = true; - $content['btn'] = getPageButtons($content['pages']); - $content['antibot'] = $antibot; + if (!$config['smart_build']) { + $content = index($page); + if (!$content) + break; - file_write($filename, Element('index.html', $content)); + // json api + if ($config['api']['enabled']) { + $threads = $content['threads']; + $json = json_encode($api->translatePage($threads)); + 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 ($config['try_smarter']) { + $antibot = create_antibot($board['uri'], 0 - $page); + $content['current_page'] = $page; + } + $antibot->reset(); + $content['pages'] = $pages; + $content['pages'][$page-1]['selected'] = true; + $content['btn'] = getPageButtons($content['pages']); + $content['antibot'] = $antibot; + + file_write($filename, Element('index.html', $content)); + } + else { + file_unlink($filename); + file_unlink($jsonFilename); + } } - if ($page < $config['max_pages']) { + if (!$config['smart_build'] && $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); @@ -1561,13 +1570,21 @@ function buildIndex($global_api = "yes") { // json api catalog if ($config['api']['enabled'] && $global_api != "skip") { - $json = json_encode($api->translateCatalog($catalog)); - $jsonFilename = $board['dir'] . 'catalog.json'; - file_write($jsonFilename, $json); + if ($config['smart_build']) { + $jsonFilename = $board['dir'] . 'catalog.json'; + file_unlink($jsonFilename); + $jsonFilename = $board['dir'] . 'threads.json'; + file_unlink($jsonFilename); + } + else { + $json = json_encode($api->translateCatalog($catalog)); + $jsonFilename = $board['dir'] . 'catalog.json'; + file_write($jsonFilename, $json); - $json = json_encode($api->translateCatalog($catalog, true)); - $jsonFilename = $board['dir'] . 'threads.json'; - file_write($jsonFilename, $json); + $json = json_encode($api->translateCatalog($catalog, true)); + $jsonFilename = $board['dir'] . 'threads.json'; + file_write($jsonFilename, $json); + } } if ($config['try_smarter']) From d14594ce6384e7f07b56b941523fdc350e5dcac1 Mon Sep 17 00:00:00 2001 From: czaks Date: Wed, 1 Apr 2015 18:13:32 +0200 Subject: [PATCH 08/46] functions.php fix: after_open_board support; so that we may disable smart_build immediately after open_board --- inc/functions.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/inc/functions.php b/inc/functions.php index 830b9761..9cafa9a7 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -469,6 +469,11 @@ function openBoard($uri) { $board = getBoardInfo($uri); if ($board) { setupBoard($board); + + if (function_exists('after_open_board')) { + after_open_board(); + } + return true; } return false; From 66638348dc446e73b088224ff4f2c99a23292cfd Mon Sep 17 00:00:00 2001 From: czaks Date: Wed, 1 Apr 2015 18:43:48 +0200 Subject: [PATCH 09/46] smart_build: buildThread --- inc/functions.php | 89 ++++++++++++++++++++++++++--------------------- 1 file changed, 50 insertions(+), 39 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 9cafa9a7..0adade57 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -2078,51 +2078,62 @@ function buildThread($id, $return = false, $mod = false) { cache::delete("thread_{$board['uri']}_{$id}"); } - $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)); + if (!$config['smart_build'] || $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)); - while ($post = $query->fetch(PDO::FETCH_ASSOC)) { - if (!isset($thread)) { - $thread = new Thread($post, $mod ? '?/' : $config['root'], $mod); - } else { - $thread->add(new Post($post, $mod ? '?/' : $config['root'], $mod)); + while ($post = $query->fetch(PDO::FETCH_ASSOC)) { + if (!isset($thread)) { + $thread = new Thread($post, $mod ? '?/' : $config['root'], $mod); + } else { + $thread->add(new Post($post, $mod ? '?/' : $config['root'], $mod)); + } + } + + // Check if any posts were found + if (!isset($thread)) + error($config['error']['nonexistant']); + + $hasnoko50 = $thread->postCount() >= $config['noko50_min']; + $antibot = $mod || $return ? false : create_antibot($board['uri'], $id); + + $body = Element('thread.html', array( + 'board' => $board, + 'thread' => $thread, + 'body' => $thread->build(), + 'config' => $config, + 'id' => $id, + 'mod' => $mod, + 'hasnoko50' => $hasnoko50, + 'isnoko50' => false, + 'antibot' => $antibot, + 'boardlist' => createBoardlist($mod), + 'return' => ($mod ? '?' . $board['url'] . $config['file_index'] : $config['root'] . $board['dir'] . $config['file_index']) + )); + + if ($config['try_smarter'] && !$mod) + $build_pages[] = thread_find_page($id); + + // json api + if ($config['api']['enabled']) { + $api = new Api(); + $json = json_encode($api->translateThread($thread)); + $jsonFilename = $board['dir'] . $config['dir']['res'] . $id . '.json'; + file_write($jsonFilename, $json); } } - - // Check if any posts were found - if (!isset($thread)) - error($config['error']['nonexistant']); - - $hasnoko50 = $thread->postCount() >= $config['noko50_min']; - $antibot = $mod || $return ? false : create_antibot($board['uri'], $id); - - $body = Element('thread.html', array( - 'board' => $board, - 'thread' => $thread, - 'body' => $thread->build(), - 'config' => $config, - 'id' => $id, - 'mod' => $mod, - 'hasnoko50' => $hasnoko50, - 'isnoko50' => false, - 'antibot' => $antibot, - 'boardlist' => createBoardlist($mod), - 'return' => ($mod ? '?' . $board['url'] . $config['file_index'] : $config['root'] . $board['dir'] . $config['file_index']) - )); - - if ($config['try_smarter'] && !$mod) - $build_pages[] = thread_find_page($id); - - // json api - if ($config['api']['enabled']) { - $api = new Api(); - $json = json_encode($api->translateThread($thread)); + else { $jsonFilename = $board['dir'] . $config['dir']['res'] . $id . '.json'; - file_write($jsonFilename, $json); + file_unlink($jsonFilename); } - if ($return) { + if ($config['smart_build'] && !$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) { return $body; } else { $noko50fn = $board['dir'] . $config['dir']['res'] . sprintf($config['file_page50'], $id); From 4244f2cad36d1a9760e16b4b1f7088f981156296 Mon Sep 17 00:00:00 2001 From: czaks Date: Wed, 1 Apr 2015 18:46:48 +0200 Subject: [PATCH 10/46] smart_build.php: misc fixes --- smart_build.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/smart_build.php b/smart_build.php index 6621238e..d937241b 100644 --- a/smart_build.php +++ b/smart_build.php @@ -7,8 +7,12 @@ if (!$config['smart_build']) { $config['smart_build'] = false; // Let's disable it, so we can build the page for real +function after_open_board() { global $config; + $config['smart_build'] = false; +}; + function sb_board($b, $page = 1) { global $config, $build_pages; $page = (int)$page; - if ($page < 1 && $page != -1) return false; + if ($page < 1) return false; if (!openBoard($b)) return false; if ($page > $config['max_pages']) return false; $config['try_smarter'] = true; @@ -46,7 +50,7 @@ function sb_catalog($b) { } function sb_recent() { - rebuildTheme("recent", "post-thread"); + rebuildTheme("recent", "post-thread"); return true; } From 7581fc3094b2df85dc3342360b7ce043393be058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20=C5=81abanowski?= Date: Thu, 2 Apr 2015 20:27:10 +0200 Subject: [PATCH 11/46] smart_build.php: optimizations, guards, also a functionality to correct slugs --- smart_build.php | 68 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 8 deletions(-) diff --git a/smart_build.php b/smart_build.php index d937241b..56299aee 100644 --- a/smart_build.php +++ b/smart_build.php @@ -25,12 +25,53 @@ function sb_api_board($b, $page = 0) { $page = (int)$page; return sb_board($b, $page + 1); } -function sb_thread($b, $thread) { global $config; $thread = (int)$thread; +function sb_thread($b, $thread, $slugcheck = false) { global $config; $thread = (int)$thread; + if ($thread < 1) return false; + + if (!preg_match('/^'.$config['board_regex'].'$/u', $b)) return false; + + if (Cache::get("thread_exists_".$b."_".$thread) == "no") return false; + + $query = prepare(sprintf("SELECT MAX(`id`) AS `max` FROM ``posts_%s``", $b)); + if (!$query->execute()) return false; + + $s = $query->fetch(PDO::FETCH_ASSOC); + $max = $s['max']; + + if ($thread > $max) return false; + + $query = prepare(sprintf("SELECT `id` FROM ``posts_%s`` WHERE `id` = :id AND `thread` IS NULL", $b)); + $query->bindValue(':id', $thread); + + if (!$query->execute() || !$query->fetch(PDO::FETCH_ASSOC) ) { + Cache::set("thread_exists_".$b."_".$thread, "no"); + return false; + } + + if ($slugcheck && $config['slugify']) { + global $request; + + $link = link_for(array("id" => $thread), $slugcheck === 50, array("uri" => $b)); + $link = "/".$b."/".$config['dir']['res'].$link; + + if ($link != $request) { + header("Location: $link", true, 301); + die(); + } + } + if (!openBoard($b)) return false; buildThread($thread); return true; } +function sb_thread_slugcheck($b, $thread) { + return sb_thread($b, $thread, true); +} +function sb_thread_slugcheck50($b, $thread) { + return sb_thread($b, $thread, 50); +} + function sb_api($b) { global $config; if (!openBoard($b)) return false; $config['try_smarter'] = true; @@ -65,11 +106,11 @@ if ($config['api']['enabled']) { $entrypoints['/%b/catalog.json'] = 'sb_api'; } -$entrypoints['/%b/'.$config['dir']['res'].$config['file_page']] = 'sb_thread'; -$entrypoints['/%b/'.$config['dir']['res'].$config['file_page50']] = 'sb_thread'; +$entrypoints['/%b/'.$config['dir']['res'].$config['file_page']] = 'sb_thread_slugcheck'; +$entrypoints['/%b/'.$config['dir']['res'].$config['file_page50']] = 'sb_thread_slugcheck50'; if ($config['slugify']) { - $entrypoints['/%b/'.$config['dir']['res'].$config['file_page_slug']] = 'sb_thread'; - $entrypoints['/%b/'.$config['dir']['res'].$config['file_page50_slug']] = 'sb_thread'; + $entrypoints['/%b/'.$config['dir']['res'].$config['file_page_slug']] = 'sb_thread_slugcheck'; + $entrypoints['/%b/'.$config['dir']['res'].$config['file_page50_slug']] = 'sb_thread_slugcheck50'; } if ($config['api']['enabled']) { $entrypoints['/%b/'.$config['dir']['res'].'%d.json'] = 'sb_thread'; @@ -102,6 +143,18 @@ foreach ($entrypoints as $id => $fun) { } } +function die_404() { global $config; + if (!$config['page_404']) { + header("HTTP/1.1 404 Not Exists"); + header("Status: 404 Not Exists"); + echo "

404 Not Exists

Page doesn't exist


vichan
"; + } + else { + header("Location: ".$config['page_404']); + } + die(); +} + if ($reached) { if ($request[strlen($request)-1] == '/') { $request .= 'index.html'; @@ -109,8 +162,7 @@ if ($reached) { $request = '.'.$request; if (!file_exists($request)) { - header("Location: ".$config['page_404']); - die(); + die_404(); } header("HTTP/1.1 200 OK"); @@ -139,5 +191,5 @@ if ($reached) { fclose($file); } else { - header("Location: ".$config['page_404']); + die_404(); } From 076bd22586d61a0000b09296d65ba3c9ecff6e0e Mon Sep 17 00:00:00 2001 From: czaks Date: Thu, 2 Apr 2015 20:35:17 +0200 Subject: [PATCH 12/46] smart_build.php: additional fixes --- smart_build.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/smart_build.php b/smart_build.php index 56299aee..d8352621 100644 --- a/smart_build.php +++ b/smart_build.php @@ -124,6 +124,7 @@ $entrypoints['/%b/catalog.html'] = 'sb_catalog'; $reached = false; $request = $_SERVER['REQUEST_URI']; +list($request) = explode('?', $request); foreach ($entrypoints as $id => $fun) { $id = '@^' . preg_quote($id, '@') . '$@u'; @@ -145,13 +146,14 @@ foreach ($entrypoints as $id => $fun) { function die_404() { global $config; if (!$config['page_404']) { - header("HTTP/1.1 404 Not Exists"); - header("Status: 404 Not Exists"); - echo "

404 Not Exists

Page doesn't exist


vichan
"; + header("HTTP/1.1 404 Not Found"); + header("Status: 404 Not Found"); + echo "

404 Not Found

Page doesn't exist


vichan
"; } else { header("Location: ".$config['page_404']); } + die(); } From 0c3c982d7a7771e93d133405a519331dc59e8ddd Mon Sep 17 00:00:00 2001 From: czaks Date: Thu, 2 Apr 2015 20:43:06 +0200 Subject: [PATCH 13/46] smart_build.php: add a protection for +50; maybe we don`t need to rebuild after all? --- smart_build.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/smart_build.php b/smart_build.php index d8352621..dd9450ae 100644 --- a/smart_build.php +++ b/smart_build.php @@ -59,6 +59,14 @@ function sb_thread($b, $thread, $slugcheck = false) { global $config; $thread = die(); } } + + if ($slugcheck == 50) { // Should we really generate +50 page? Maybe there are not enough posts anyway + global $request; + $r = str_replace("+50", "", $request); + $r = substr($r, 1); // Cut the slash + + if (file_exists($r)) return false; + } if (!openBoard($b)) return false; buildThread($thread); From 402dd58a4bf4ab6f4a67ba41d52631c4da5bbac4 Mon Sep 17 00:00:00 2001 From: czaks Date: Thu, 2 Apr 2015 20:53:50 +0200 Subject: [PATCH 14/46] smart_build.php: a small fix to catalog handling --- smart_build.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/smart_build.php b/smart_build.php index dd9450ae..fb467233 100644 --- a/smart_build.php +++ b/smart_build.php @@ -94,6 +94,8 @@ function sb_ukko() { } function sb_catalog($b) { + if (!openBoard($b)) return false; + rebuildTheme("catalog", "post-thread", $b); return true; } From 36b1714e42c905430daeada1c4174fb118832afd Mon Sep 17 00:00:00 2001 From: czaks Date: Thu, 2 Apr 2015 20:54:28 +0200 Subject: [PATCH 15/46] themes can use smart_build now --- templates/themes/catalog/theme.php | 25 +++++++++++++++++++++---- templates/themes/recent/theme.php | 10 ++++++++-- templates/themes/ukko/theme.php | 10 +++++++++- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/templates/themes/catalog/theme.php b/templates/themes/catalog/theme.php index a7dfabd9..42fad1ea 100644 --- a/templates/themes/catalog/theme.php +++ b/templates/themes/catalog/theme.php @@ -20,12 +20,25 @@ if ($action == 'all') { foreach ($boards as $board) { $b = new Catalog(); - $b->build($settings, $board); + + if ($config['smart_build']) { + file_unlink($config['dir']['home'] . $board . '/catalog.html'); + } + else { + $b->build($settings, $board); + } + if (php_sapi_name() === "cli") echo "Rebuilding $board catalog...\n"; } } elseif ($action == 'post-thread' || ($settings['update_on_posts'] && $action == 'post') || ($settings['update_on_posts'] && $action == 'post-delete') && (in_array($board, $boards) | $settings['all'])) { $b = new Catalog(); - $b->build($settings, $board); + + if ($config['smart_build']) { + file_unlink($config['dir']['home'] . $board . '/catalog.html'); + } + else { + $b->build($settings, $board); + } } } @@ -33,8 +46,12 @@ class Catalog { public function build($settings, $board_name) { global $config, $board; - - openBoard($board_name); + + if ($board['uri'] != $board_name) { + if (!openBoard($board_name)) { + error(sprintf(_("Board %s doesn't exist"), $board_name)); + } + } $recent_images = array(); $recent_posts = array(); diff --git a/templates/themes/recent/theme.php b/templates/themes/recent/theme.php index b1153477..57fe92b5 100644 --- a/templates/themes/recent/theme.php +++ b/templates/themes/recent/theme.php @@ -24,8 +24,14 @@ $this->excluded = explode(' ', $settings['exclude']); - if ($action == 'all' || $action == 'post' || $action == 'post-thread' || $action == 'post-delete') - file_write($config['dir']['home'] . $settings['html'], $this->homepage($settings)); + if ($action == 'all' || $action == 'post' || $action == 'post-thread' || $action == 'post-delete') { + if ($config['smart_build']) { + file_unlink($config['dir']['home'] . $settings['html']); + } + else { + file_write($config['dir']['home'] . $settings['html'], $this->homepage($settings)); + } + } } // Build news page diff --git a/templates/themes/ukko/theme.php b/templates/themes/ukko/theme.php index 0f7c3e1a..fb851bdd 100644 --- a/templates/themes/ukko/theme.php +++ b/templates/themes/ukko/theme.php @@ -2,6 +2,8 @@ require 'info.php'; function ukko_build($action, $settings) { + global $config; + $ukko = new ukko(); $ukko->settings = $settings; @@ -9,7 +11,13 @@ return; } - file_write($settings['uri'] . '/index.html', $ukko->build()); + if ($config['smart_build']) { + file_unlink($settings['uri'] . '/index.html'); + } + else { + file_write($settings['uri'] . '/index.html', $ukko->build()); + } + file_write($settings['uri'] . '/ukko.js', Element('themes/ukko/ukko.js', array())); } From 3b9e174173f56215f8b47e85879dcfbe1752bcc6 Mon Sep 17 00:00:00 2001 From: czaks Date: Fri, 3 Apr 2015 05:57:39 +0200 Subject: [PATCH 16/46] sitemap theme for smart_build --- smart_build.php | 9 ++++++ templates/themes/sitemap/theme.php | 49 +++++++++++++++++------------- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/smart_build.php b/smart_build.php index fb467233..43734e4f 100644 --- a/smart_build.php +++ b/smart_build.php @@ -105,6 +105,11 @@ function sb_recent() { return true; } +function sb_sitemap() { + rebuildTheme("sitemap", "all"); + return true; +} + $entrypoints = array(); $entrypoints['/%b/'] = 'sb_board'; @@ -130,6 +135,7 @@ $entrypoints['/*/'] = 'sb_ukko'; $entrypoints['/*/index.html'] = 'sb_ukko'; $entrypoints['/recent.html'] = 'sb_recent'; $entrypoints['/%b/catalog.html'] = 'sb_catalog'; +$entrypoints['/sitemap.xml'] = 'sb_sitemap'; $reached = false; @@ -185,6 +191,9 @@ if ($reached) { elseif (preg_match('/\.js$/', $request)) { header("Content-Type", "text/javascript; charset=utf-8"); } + elseif (preg_match('/\.xml$/', $request)) { + header("Content-Type", "application/xml"); + } else { header("Content-Type", "text/html; charset=utf-8"); } diff --git a/templates/themes/sitemap/theme.php b/templates/themes/sitemap/theme.php index 3e048dd5..368afe58 100644 --- a/templates/themes/sitemap/theme.php +++ b/templates/themes/sitemap/theme.php @@ -10,30 +10,37 @@ // - boards (board list changed) // - post (a post has been made) // - thread (a thread has been made) + + if ($action != 'all') { + if ($action != 'post-thread' && $action != 'post-delete') + return; - if ($action != 'post-thread' && $action != 'post-delete') - return; - - if (isset($settings['regen_time']) && $settings['regen_time'] > 0) { - if ($last_gen = @filemtime($settings['path'])) { - if (time() - $last_gen < (int)$settings['regen_time']) - return; // Too soon + if (isset($settings['regen_time']) && $settings['regen_time'] > 0) { + if ($last_gen = @filemtime($settings['path'])) { + if (time() - $last_gen < (int)$settings['regen_time']) + return; // Too soon + } } } - - $boards = explode(' ', $settings['boards']); - - $threads = array(); - - foreach ($boards as $board) { - $query = query(sprintf("SELECT `id` AS `thread_id`, (SELECT `time` FROM ``posts_%s`` WHERE `thread` = `thread_id` OR `id` = `thread_id` ORDER BY `time` DESC LIMIT 1) AS `lastmod` FROM ``posts_%s`` WHERE `thread` IS NULL", $board, $board)) or error(db_error()); - $threads[$board] = $query->fetchAll(PDO::FETCH_ASSOC); + + if ($config['smart_build']) { + file_unlink($settings['path']); } + else { + $boards = explode(' ', $settings['boards']); + + $threads = array(); + + foreach ($boards as $board) { + $query = query(sprintf("SELECT `id` AS `thread_id`, (SELECT `time` FROM ``posts_%s`` WHERE `thread` = `thread_id` OR `id` = `thread_id` ORDER BY `time` DESC LIMIT 1) AS `lastmod` FROM ``posts_%s`` WHERE `thread` IS NULL", $board, $board)) or error(db_error()); + $threads[$board] = $query->fetchAll(PDO::FETCH_ASSOC); + } - file_write($settings['path'], Element('themes/sitemap/sitemap.xml', Array( - 'settings' => $settings, - 'config' => $config, - 'threads' => $threads, - 'boards' => $boards, - ))); + file_write($settings['path'], Element('themes/sitemap/sitemap.xml', Array( + 'settings' => $settings, + 'config' => $config, + 'threads' => $threads, + 'boards' => $boards, + ))); + } } From da75891dd8d66070e0f7bbcb9e80fce0f4bc8126 Mon Sep 17 00:00:00 2001 From: Jayme Brereton Date: Sun, 5 Apr 2015 20:14:58 +0930 Subject: [PATCH 17/46] made the define_groups function play nice on hhvm --- inc/functions.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 0adade57..56b43f6f 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -327,8 +327,12 @@ function verbose_error_handler($errno, $errstr, $errfile, $errline) { function define_groups() { global $config; - foreach ($config['mod']['groups'] as $group_value => $group_name) - defined($group_name) or define($group_name, $group_value, true); + foreach ($config['mod']['groups'] as $group_value => $group_name) { + $group_name = strtoupper($group_name); + if(!defined($group_name)) { + define($group_name, $group_value, true); + } + } ksort($config['mod']['groups']); } From c67520d8b16964efad4451e8fd5864b4b64c6d0d Mon Sep 17 00:00:00 2001 From: czaks Date: Sun, 5 Apr 2015 16:20:59 +0200 Subject: [PATCH 18/46] ukko: small optimization: move js creation to install time --- templates/themes/ukko/info.php | 1 + templates/themes/ukko/theme.php | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/templates/themes/ukko/info.php b/templates/themes/ukko/info.php index 45a687fc..9a0a53e6 100644 --- a/templates/themes/ukko/info.php +++ b/templates/themes/ukko/info.php @@ -49,6 +49,7 @@ function ukko_install($settings) { if (!file_exists($settings['uri'])) @mkdir($settings['uri'], 0777) or error("Couldn't create " . $settings['uri'] . ". Check permissions.", true); + file_write($settings['uri'] . '/ukko.js', Element('themes/ukko/ukko.js', array())); } } diff --git a/templates/themes/ukko/theme.php b/templates/themes/ukko/theme.php index fb851bdd..ebf25c15 100644 --- a/templates/themes/ukko/theme.php +++ b/templates/themes/ukko/theme.php @@ -17,8 +17,6 @@ else { file_write($settings['uri'] . '/index.html', $ukko->build()); } - - file_write($settings['uri'] . '/ukko.js', Element('themes/ukko/ukko.js', array())); } class ukko { From 0e84743f7a62e2e18efa6fd49084b5e142a48221 Mon Sep 17 00:00:00 2001 From: czaks Date: Sun, 5 Apr 2015 16:38:16 +0200 Subject: [PATCH 19/46] optimization: get rid of one more sql query related to installed themes --- inc/functions.php | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 56b43f6f..9c3f880b 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -351,9 +351,22 @@ function rebuildThemes($action, $boardname = false) { $_board = $board; // List themes - $query = query("SELECT `theme` FROM ``theme_settings`` WHERE `name` IS NULL AND `value` IS NULL") or error(db_error()); + if ($themes = Cache::get("themes")) { + // OK, we already have themes loaded + } + else { + $query = query("SELECT `theme` FROM ``theme_settings`` WHERE `name` IS NULL AND `value` IS NULL") or error(db_error()); - while ($theme = $query->fetch(PDO::FETCH_ASSOC)) { + $themes = array(); + + while ($theme = $query->fetch(PDO::FETCH_ASSOC)) { + $themes[] = $theme; + } + + Cache::set("themes", $themes); + } + + foreach ($themes as $theme) { // Restore them $config = $_config; $board = $_board; @@ -407,6 +420,10 @@ function rebuildTheme($theme, $action, $board = false) { function themeSettings($theme) { + if ($settings = Cache::get("theme_settings_".$theme)) { + return $settings; + } + $query = prepare("SELECT `name`, `value` FROM ``theme_settings`` WHERE `theme` = :theme AND `name` IS NOT NULL"); $query->bindValue(':theme', $theme); $query->execute() or error(db_error($query)); @@ -416,6 +433,8 @@ function themeSettings($theme) { $settings[$s['name']] = $s['value']; } + Cache::set("theme_settings_".$theme, $settings); + return $settings; } From 099ea5bbbf84c8559271d7441cc003a10f6eb242 Mon Sep 17 00:00:00 2001 From: czaks Date: Sun, 5 Apr 2015 16:45:51 +0200 Subject: [PATCH 20/46] add tmp subdirectory (a filesystem used for further optimizations) --- tmp/cache/.gitkeep | 0 tmp/locks/.gitkeep | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tmp/cache/.gitkeep create mode 100644 tmp/locks/.gitkeep diff --git a/tmp/cache/.gitkeep b/tmp/cache/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tmp/locks/.gitkeep b/tmp/locks/.gitkeep new file mode 100644 index 00000000..e69de29b From 3368ca1141c6c5a57d97978a7f3409a2f6d873a8 Mon Sep 17 00:00:00 2001 From: czaks Date: Sun, 5 Apr 2015 16:52:35 +0200 Subject: [PATCH 21/46] optimization: locale caching, so we don`t have to reparse instance-config every single time --- inc/functions.php | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 9c3f880b..1e6b9123 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -96,18 +96,28 @@ function loadConfig() { // Initialize locale as early as possible - $config['locale'] = 'en'; + // Those calls are expensive. Unfortunately, our cache system is not initialized at this point. + // So, we may store the locale in a tmp/ filesystem. - $configstr = file_get_contents('inc/instance-config.php'); + if (file_exists($fn = 'tmp/cache/locale_' . ( isset($board['uri']) ? $board['uri'] : '' ) ) ) { + $config['locale'] = file_get_contents($fn); + } + else { + $config['locale'] = 'en'; + + $configstr = file_get_contents('inc/instance-config.php'); if (isset($board['dir']) && file_exists($board['dir'] . '/config.php')) { - $configstr .= file_get_contents($board['dir'] . '/config.php'); + $configstr .= file_get_contents($board['dir'] . '/config.php'); } - $matches = array(); - 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]; + $matches = array(); + 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]; + } + + file_put_contents($fn, $config['locale']); } if ($config['locale'] != $current_locale) { From 372e3fc3fce2d40a7eb5b49195d326c2c7274d7a Mon Sep 17 00:00:00 2001 From: czaks Date: Sun, 5 Apr 2015 16:59:04 +0200 Subject: [PATCH 22/46] uncache themes on settings change --- inc/mod/pages.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 7efabae6..97a33487 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -3333,10 +3333,14 @@ function mod_theme_configure($theme_name) { $query->bindValue(':value', $_POST[$conf['name']]); $query->execute() or error(db_error($query)); } - + $query = prepare("INSERT INTO ``theme_settings`` VALUES(:theme, NULL, NULL)"); $query->bindValue(':theme', $theme_name); $query->execute() or error(db_error($query)); + + // Clean cache + Cache::delete("themes"); + Cache::delete("theme_settings_".$theme); $result = true; $message = false; @@ -3384,11 +3388,15 @@ function mod_theme_uninstall($theme_name) { if (!hasPermission($config['mod']['themes'])) error($config['error']['noaccess']); - + $query = prepare("DELETE FROM ``theme_settings`` WHERE `theme` = :theme"); $query->bindValue(':theme', $theme_name); $query->execute() or error(db_error($query)); + // Clean cache + Cache::delete("themes"); + Cache::delete("theme_settings_".$theme); + header('Location: ?/themes', true, $config['redirect_http']); } From 093b1b605ba1ccc3d55d984acb315d8345cbb1a2 Mon Sep 17 00:00:00 2001 From: czaks Date: Sun, 5 Apr 2015 17:13:55 +0200 Subject: [PATCH 23/46] cache.php: fs cache --- inc/cache.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/inc/cache.php b/inc/cache.php index d1200919..d6bc9211 100644 --- a/inc/cache.php +++ b/inc/cache.php @@ -50,6 +50,17 @@ class Cache { case 'php': $data = isset(self::$cache[$key]) ? self::$cache[$key] : false; break; + case 'fs': + $key = str_replace('/', '::', $key); + $key = str_replace("\0", '', $key); + if (!file_exists('tmp/cache/'.$key)) { + $data = false; + } + else { + $data = file_get_contents('tmp/cache/'.$key); + $data = json_decode($data, true); + } + break; case 'redis': if (!self::$cache) self::init(); @@ -87,6 +98,11 @@ class Cache { case 'xcache': xcache_set($key, $value, $expires); break; + case 'fs': + $key = str_replace('/', '::', $key); + $key = str_replace("\0", '', $key); + file_put_contents('tmp/cache/'.$key, json_encode($value)); + break; case 'php': self::$cache[$key] = $value; break; @@ -113,6 +129,11 @@ class Cache { case 'xcache': xcache_unset($key); break; + case 'fs': + $key = str_replace('/', '::', $key); + $key = str_replace("\0", '', $key); + unlink('tmp/cache/'.$key); + break; case 'php': unset(self::$cache[$key]); break; @@ -134,6 +155,12 @@ class Cache { case 'php': self::$cache = array(); break; + case 'fs': + $files = glob('tmp/cache/*'); + foreach ($files as $file) { + unlink($file); + } + break; case 'redis': if (!self::$cache) self::init(); From 5c5751647dd2de6ede57ecf622df0cfa157bd6ed Mon Sep 17 00:00:00 2001 From: czaks Date: Sun, 5 Apr 2015 18:46:45 +0200 Subject: [PATCH 24/46] update gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index 8fecf08c..825799da 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,12 @@ Thumbs.db *.orig *~ +# tmp filesystem +/tmp/cache/* +/tmp/locks/* +!/tmp/cache/.gitkeep +!/tmp/locks/.gitkeep + #vichan custom favicon.ico /static/spoiler.png From 7c78dad6d89a39795d0ec35c2927609960f692d2 Mon Sep 17 00:00:00 2001 From: czaks Date: Mon, 6 Apr 2015 18:42:23 +0200 Subject: [PATCH 25/46] =?UTF-8?q?smart=5Fbuild.php:=20nginx=20=E2=80=93=20?= =?UTF-8?q?cache=20404=20replies=20for=20120s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- smart_build.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smart_build.php b/smart_build.php index 43734e4f..993d7858 100644 --- a/smart_build.php +++ b/smart_build.php @@ -169,7 +169,7 @@ function die_404() { global $config; else { header("Location: ".$config['page_404']); } - + header("X-Accel-Expires: 120"); die(); } From 8da7ec3bdedc480dbd4cf732e003cbb5795126e1 Mon Sep 17 00:00:00 2001 From: czaks Date: Mon, 6 Apr 2015 18:59:33 +0200 Subject: [PATCH 26/46] try_smarter: fix two bugs. 1. uncovered by the second, during a bump only the page the thread was on and first page were rebuild, despite threads rearranging their positions on the remaining pages. happening always. 2. during smart build, the page wasn`t ordered to be rebuilt --- inc/functions.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 1e6b9123..af029d43 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -1049,8 +1049,9 @@ function bumpThread($id) { if (event('bump', $id)) return true; - if ($config['try_smarter']) - $build_pages[] = thread_find_page($id); + if ($config['try_smarter']) { + $build_pages = array_merge(range(1, thread_find_page($id)), $build_pages); + } $query = prepare(sprintf("UPDATE ``posts_%s`` SET `bump` = :time WHERE `id` = :id AND `thread` IS NULL", $board['uri'])); $query->bindValue(':time', time(), PDO::PARAM_INT); @@ -2111,6 +2112,9 @@ function buildThread($id, $return = false, $mod = false) { cache::delete("thread_{$board['uri']}_{$id}"); } + if ($config['try_smarter'] && !$mod) + $build_pages[] = thread_find_page($id); + if (!$config['smart_build'] || $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); @@ -2145,9 +2149,6 @@ function buildThread($id, $return = false, $mod = false) { 'return' => ($mod ? '?' . $board['url'] . $config['file_index'] : $config['root'] . $board['dir'] . $config['file_index']) )); - if ($config['try_smarter'] && !$mod) - $build_pages[] = thread_find_page($id); - // json api if ($config['api']['enabled']) { $api = new Api(); From c1045de87ff7bc8b1d8e1f4e0e7cb7c177db5cf3 Mon Sep 17 00:00:00 2001 From: czaks Date: Mon, 6 Apr 2015 19:28:00 +0200 Subject: [PATCH 27/46] infinity/smart_build: disable slugify for now --- smart_build.php | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/smart_build.php b/smart_build.php index 993d7858..da1966d0 100644 --- a/smart_build.php +++ b/smart_build.php @@ -48,18 +48,6 @@ function sb_thread($b, $thread, $slugcheck = false) { global $config; $thread = return false; } - if ($slugcheck && $config['slugify']) { - global $request; - - $link = link_for(array("id" => $thread), $slugcheck === 50, array("uri" => $b)); - $link = "/".$b."/".$config['dir']['res'].$link; - - if ($link != $request) { - header("Location: $link", true, 301); - die(); - } - } - if ($slugcheck == 50) { // Should we really generate +50 page? Maybe there are not enough posts anyway global $request; $r = str_replace("+50", "", $request); @@ -73,9 +61,6 @@ function sb_thread($b, $thread, $slugcheck = false) { global $config; $thread = return true; } -function sb_thread_slugcheck($b, $thread) { - return sb_thread($b, $thread, true); -} function sb_thread_slugcheck50($b, $thread) { return sb_thread($b, $thread, 50); } @@ -121,12 +106,8 @@ if ($config['api']['enabled']) { $entrypoints['/%b/catalog.json'] = 'sb_api'; } -$entrypoints['/%b/'.$config['dir']['res'].$config['file_page']] = 'sb_thread_slugcheck'; +$entrypoints['/%b/'.$config['dir']['res'].$config['file_page']] = 'sb_thread'; $entrypoints['/%b/'.$config['dir']['res'].$config['file_page50']] = 'sb_thread_slugcheck50'; -if ($config['slugify']) { - $entrypoints['/%b/'.$config['dir']['res'].$config['file_page_slug']] = 'sb_thread_slugcheck'; - $entrypoints['/%b/'.$config['dir']['res'].$config['file_page50_slug']] = 'sb_thread_slugcheck50'; -} if ($config['api']['enabled']) { $entrypoints['/%b/'.$config['dir']['res'].'%d.json'] = 'sb_thread'; } From 9907ae29b818624ec3c9e8a4c6c82d927270fa29 Mon Sep 17 00:00:00 2001 From: czaks Date: Fri, 27 Feb 2015 21:16:03 +0100 Subject: [PATCH 28/46] rearrange config processing a bit --- inc/functions.php | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index af029d43..72487d23 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -138,10 +138,6 @@ function loadConfig() { init_locale($config['locale'], $error); } - if (!isset($__version)) - $__version = file_exists('.installed') ? trim(file_get_contents('.installed')) : false; - $config['version'] = $__version; - date_default_timezone_set($config['timezone']); if (!isset($config['global_message'])) @@ -150,6 +146,7 @@ function loadConfig() { if (!isset($config['post_url'])) $config['post_url'] = $config['root'] . $config['file_post']; + if (!isset($config['referer_match'])) if (isset($_SERVER['HTTP_HOST'])) { $config['referer_match'] = '/^' . @@ -220,19 +217,12 @@ function loadConfig() { if (!isset($config['user_flags'])) $config['user_flags'] = array(); + // Effectful config processing below: + if ($config['root_file']) { chdir($config['root_file']); } - if ($config['verbose_errors']) { - set_error_handler('verbose_error_handler'); - error_reporting(E_ALL); - ini_set('display_errors', true); - ini_set('html_errors', false); - } else { - ini_set('display_errors', false); - } - // Keep the original address to properly comply with other board configurations if (!isset($__ip)) $__ip = $_SERVER['REMOTE_ADDR']; @@ -241,11 +231,25 @@ function loadConfig() { if (preg_match('/^\:\:(ffff\:)?(\d+\.\d+\.\d+\.\d+)$/', $__ip, $m)) $_SERVER['REMOTE_ADDR'] = $m[2]; + if (!isset($__version)) + $__version = file_exists('.installed') ? trim(file_get_contents('.installed')) : false; + $config['version'] = $__version; + + if ($config['verbose_errors']) { + set_error_handler('verbose_error_handler'); + error_reporting(E_ALL); + ini_set('display_errors', true); + ini_set('html_errors', false); + } else { + ini_set('display_errors', false); + } + if ($config['syslog']) openlog('tinyboard', LOG_ODELAY, LOG_SYSLOG); // open a connection to sysem logger if ($config['recaptcha']) require_once 'inc/lib/recaptcha/recaptchalib.php'; + if ($config['cache']['enabled']) require_once 'inc/cache.php'; From 18e5de9a1ffda546313462cb22fcf94bccfa375d Mon Sep 17 00:00:00 2001 From: czaks Date: Sun, 5 Apr 2015 18:48:53 +0200 Subject: [PATCH 29/46] cache_config preliminary release --- inc/config.php | 5 ++++ inc/functions.php | 73 ++++++++++++++++++++++++++++++++++++----------- 2 files changed, 62 insertions(+), 16 deletions(-) diff --git a/inc/config.php b/inc/config.php index 6244479f..e1ad3eec 100644 --- a/inc/config.php +++ b/inc/config.php @@ -132,6 +132,11 @@ // Tinyboard to use. $config['cache']['redis'] = array('localhost', 6379, '', 1); + // EXPERIMENTAL: Should we cache configs? Warning: this changes board behaviour, i'd say, a lot. + // If you have any lambdas/includes present in your config, you should move them to instance-functions.php + // (this file will be explicitly loaded during cache hit, but not during cache miss). + $config['cache_config'] = false; + /* * ==================== * Cookie settings diff --git a/inc/functions.php b/inc/functions.php index 72487d23..8aa0e4d8 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -50,15 +50,40 @@ $current_locale = 'en'; function loadConfig() { - global $board, $config, $__ip, $debug, $__version, $microtime_start, $current_locale; + global $board, $config, $__ip, $debug, $__version, $microtime_start, $current_locale, $events; $error = function_exists('error') ? 'error' : 'basic_error_function_because_the_other_isnt_loaded_yet'; - reset_events(); + $boardsuffix = isset($board['uri']) ? $board['uri'] : ''; if (!isset($_SERVER['REMOTE_ADDR'])) $_SERVER['REMOTE_ADDR'] = '0.0.0.0'; + if (file_exists('tmp/cache/cache_config.php')) { + require_once('tmp/cache/cache_config.php'); + } + + + if (isset($config['cache_config']) && + $config['cache_config'] && + $config = Cache::get('config_' . $boardsuffix ) ) { + $events = Cache::get('events_' . $boardsuffix ); + + if (file_exists('inc/instance-functions.php')) { + require_once('inc/instance-functions.php'); + } + + if ($config['locale'] != $current_locale) { + $current_locale = $config['locale']; + init_locale($config['locale'], $error); + } + } + else { + $config = array(); + // We will indent that later. + + reset_events(); + $arrays = array( 'db', 'api', @@ -86,7 +111,6 @@ function loadConfig() { 'dashboard_links' ); - $config = array(); foreach ($arrays as $key) { $config[$key] = array(); } @@ -99,7 +123,7 @@ function loadConfig() { // Those calls are expensive. Unfortunately, our cache system is not initialized at this point. // So, we may store the locale in a tmp/ filesystem. - if (file_exists($fn = 'tmp/cache/locale_' . ( isset($board['uri']) ? $board['uri'] : '' ) ) ) { + if (file_exists($fn = 'tmp/cache/locale_' . $boardsuffix ) ) { $config['locale'] = file_get_contents($fn); } else { @@ -138,8 +162,6 @@ function loadConfig() { init_locale($config['locale'], $error); } - date_default_timezone_set($config['timezone']); - if (!isset($config['global_message'])) $config['global_message'] = false; @@ -217,8 +239,22 @@ function loadConfig() { if (!isset($config['user_flags'])) $config['user_flags'] = array(); + if (!isset($__version)) + $__version = file_exists('.installed') ? trim(file_get_contents('.installed')) : false; + $config['version'] = $__version; + + if ($config['allow_roll']) + event_handler('post', 'diceRoller'); + + if (is_array($config['anonymous'])) + $config['anonymous'] = $config['anonymous'][array_rand($config['anonymous'])]; + + + } // Effectful config processing below: + date_default_timezone_set($config['timezone']); + if ($config['root_file']) { chdir($config['root_file']); } @@ -231,10 +267,6 @@ function loadConfig() { if (preg_match('/^\:\:(ffff\:)?(\d+\.\d+\.\d+\.\d+)$/', $__ip, $m)) $_SERVER['REMOTE_ADDR'] = $m[2]; - if (!isset($__version)) - $__version = file_exists('.installed') ? trim(file_get_contents('.installed')) : false; - $config['version'] = $__version; - if ($config['verbose_errors']) { set_error_handler('verbose_error_handler'); error_reporting(E_ALL); @@ -258,13 +290,22 @@ function loadConfig() { event_handler('post', 'postHandler'); } - if (is_array($config['anonymous'])) - $config['anonymous'] = $config['anonymous'][array_rand($config['anonymous'])]; - - if ($config['allow_roll']) - event_handler('post', 'diceRoller'); - event('load-config'); + + if ($config['cache_config'] && !isset ($config['cache_config_loaded'])) { + file_put_contents('tmp/cache/cache_config.php', ' Date: Sun, 5 Apr 2015 20:04:27 +0200 Subject: [PATCH 30/46] cache_config: fix debug notice --- inc/functions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 8aa0e4d8..33a6f5d4 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -295,9 +295,9 @@ function loadConfig() { if ($config['cache_config'] && !isset ($config['cache_config_loaded'])) { file_put_contents('tmp/cache/cache_config.php', ' Date: Sun, 5 Apr 2015 20:25:57 +0200 Subject: [PATCH 31/46] groups were not defined --- inc/functions.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/inc/functions.php b/inc/functions.php index 33a6f5d4..30de9d15 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -69,6 +69,8 @@ function loadConfig() { $config = Cache::get('config_' . $boardsuffix ) ) { $events = Cache::get('events_' . $boardsuffix ); + define_groups(); + if (file_exists('inc/instance-functions.php')) { require_once('inc/instance-functions.php'); } From 9ca7dae9c9a553aa85e73e054b00a076a3607105 Mon Sep 17 00:00:00 2001 From: czaks Date: Mon, 6 Apr 2015 19:50:50 +0200 Subject: [PATCH 32/46] infinity/smart-build: clean cache on rebuilding the boards --- inc/8chan-mod-pages.php | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/inc/8chan-mod-pages.php b/inc/8chan-mod-pages.php index 3eb7de07..135a1c46 100644 --- a/inc/8chan-mod-pages.php +++ b/inc/8chan-mod-pages.php @@ -661,6 +661,18 @@ EOT; // Faster than openBoard and bypasses cache...we're trusting the PHP output // to be safe enough to run with every request, we can eval it here. eval(str_replace('flags.php', "$b/flags.php", preg_replace('/^\<\?php$/m', '', $config_file))); + // czaks: maybe reconsider using it, now that config is cached? + + // Clean the cache + if ($config['cache']['enabled']) { + cache::delete('board_' . $board['uri']); + cache::delete('all_boards'); + + cache::delete('config_' . $board['uri']); + cache::delete('events_' . $board['uri']); + + unlink('tmp/cache/locale_' . $board['uri']); + } // be smarter about rebuilds...only some changes really require us to rebuild all threads if ($_config['captcha']['enabled'] != $config['captcha']['enabled'] @@ -686,10 +698,5 @@ EOT; $css = @file_get_contents('stylesheets/board/' . $board['uri'] . '.css'); - if ($config['cache']['enabled']) { - cache::delete('board_' . $board['uri']); - cache::delete('all_boards'); - } - mod_page(_('Board configuration'), 'mod/settings.html', array('board'=>$board, 'css'=>prettify_textarea($css), 'token'=>make_secure_link_token('settings/'.$board['uri']), 'languages'=>$possible_languages,'allowed_urls'=>$config['allowed_offsite_urls'])); }; From be4bf70278ba69a957aa953864484935932ee70b Mon Sep 17 00:00:00 2001 From: czaks Date: Mon, 6 Apr 2015 20:00:18 +0200 Subject: [PATCH 33/46] infinity/smart-build: don`t include gettext php lib if a shared object is loaded: adaptation of vichan-devel/cbbebc --- inc/functions.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/inc/functions.php b/inc/functions.php index 30de9d15..821cc48a 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -19,7 +19,9 @@ require_once 'inc/database.php'; require_once 'inc/events.php'; require_once 'inc/api.php'; require_once 'inc/bans.php'; -require_once 'inc/lib/gettext/gettext.inc'; +if (!extension_loaded('gettext')) { + require_once 'inc/lib/gettext/gettext.inc'; +} require_once 'inc/lib/parsedown/Parsedown.php'; // todo: option for parsedown instead of Tinyboard/STI markup require_once 'inc/mod/auth.php'; From 798121060caf7d31b3e70ea295ad5759c3e0d6a1 Mon Sep 17 00:00:00 2001 From: czaks Date: Mon, 6 Apr 2015 22:18:05 +0200 Subject: [PATCH 34/46] infinity/smart-build: missing slugify patchset fix --- inc/functions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 821cc48a..480df77f 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -2212,10 +2212,10 @@ function buildThread($id, $return = false, $mod = false) { } if ($config['smart_build'] && !$return && !$mod) { - $noko50fn = $board['dir'] . $config['dir']['res'] . link_for(array('id' => $id), true); + $noko50fn = $board['dir'] . $config['dir']['res'] . sprintf($config['file_page50'], $id); file_unlink($noko50fn); - file_unlink($board['dir'] . $config['dir']['res'] . link_for(array('id' => $id))); + file_unlink($board['dir'] . $config['dir']['res'] . sprintf($config['file_page'], $id)); } else if ($return) { return $body; } else { From ca92457e889bd5dc11a34a8f3cddcc4aa48a3a0b Mon Sep 17 00:00:00 2001 From: czaks Date: Mon, 6 Apr 2015 22:37:47 +0200 Subject: [PATCH 35/46] infinity/smart-build: rearrange the config files for cache_config --- inc/8chan-functions.php | 20 + inc/8chan-mod-pages-functions.php | 680 ++++++++++++++++++++++++++++++ inc/8chan-mod-pages.php | 662 +---------------------------- inc/instance-config.php | 17 +- inc/instance-functions.php | 17 + 5 files changed, 727 insertions(+), 669 deletions(-) create mode 100644 inc/8chan-mod-pages-functions.php create mode 100644 inc/instance-functions.php diff --git a/inc/8chan-functions.php b/inc/8chan-functions.php index 5a50ec74..ef289616 100644 --- a/inc/8chan-functions.php +++ b/inc/8chan-functions.php @@ -54,3 +54,23 @@ function human_time_diff( $from, $to = '' ) { return $since; } + +function is_billion_laughs($arr1, $arr2) { + $arr = array(); + foreach ($arr1 as $k => $v) { + $arr[$v] = $arr2[$k]; + } + + for ($i = 0; $i <= sizeof($arr); $i++) { + $cur = array_slice($arr, $i, 1); + $pst = array_slice($arr, 0, $i); + if (!$cur) continue; + $kk = array_keys($cur)[0]; + $vv = array_values($cur)[0]; + foreach ($pst as $k => $v) { + if (str_replace($kk, $vv, $v) != $v) + return true; + } + } + return false; +} diff --git a/inc/8chan-mod-pages-functions.php b/inc/8chan-mod-pages-functions.php new file mode 100644 index 00000000..eef96790 --- /dev/null +++ b/inc/8chan-mod-pages-functions.php @@ -0,0 +1,680 @@ + 5) + error(_('Too many tags.')); + + $delete = prepare('DELETE FROM ``board_tags`` WHERE uri = :uri'); + $delete->bindValue(':uri', $b); + $delete->execute(); + + foreach ($_POST['tags'] as $i => $tag) { + if ($tag) { + if (strlen($tag) > 255) + continue; + + $insert = prepare('INSERT INTO ``board_tags``(uri, tag) VALUES (:uri, :tag)'); + $insert->bindValue(':uri', $b); + $insert->bindValue(':tag', utf8tohtml($tag)); + $insert->execute(); + } + } + + $update = prepare('UPDATE ``boards`` SET sfw = :sfw WHERE uri = :uri'); + $update->bindValue(':uri', $b); + $update->bindValue(':sfw', isset($_POST['sfw'])); + $update->execute(); + } + $query = prepare('SELECT * FROM ``board_tags`` WHERE uri = :uri'); + $query->bindValue(':uri', $b); + $query->execute(); + + $tags = $query->fetchAll(); + + $query = prepare('SELECT `sfw` FROM ``boards`` WHERE uri = :uri'); + $query->bindValue(':uri', $b); + $query->execute(); + + $sfw = $query->fetchColumn(); + + mod_page(_('Edit tags'), 'mod/tags.html', array('board'=>$board,'token'=>make_secure_link_token('tags/'.$board['uri']), 'tags'=>$tags, 'sfw'=>$sfw)); + } + + function mod_8_reassign($b) { + global $board, $config; + + if (!openBoard($b)) + error("Could not open board!"); + + if (!hasPermission($config['mod']['reassign_board'], $b)) + error($config['error']['noaccess']); + + $query = query("SELECT id, username FROM mods WHERE boards = '$b' AND type = 20"); + $mods = $query->fetchAll(); + + if (!$mods) { + error('No mods?'); + } + + $password = base64_encode(openssl_random_pseudo_bytes(9)); + $salt = generate_salt(); + $hashed = hash('sha256', $salt . sha1($password)); + + $query = prepare('UPDATE ``mods`` SET `password` = :hashed, `salt` = :salt WHERE BINARY username = :mod'); + $query->bindValue(':hashed', $hashed); + $query->bindValue(':salt', $salt); + $query->bindValue(':mod', $mods[0]['username']); + $query->execute(); + + $body = "Thanks for your interest in this board. Kindly find the username and password below. You can login at https://8ch.net/mod.php.
Username: {$mods[0]['username']}
Password: {$password}
Thanks for using 8chan!"; + + modLog("Reassigned board /$b/"); + + mod_page(_('Edit reassign'), 'blank.html', array('board'=>$board,'token'=>make_secure_link_token('reassign/'.$board['uri']),'body'=>$body)); + } + + function mod_8_volunteers($b) { + global $board, $config, $pdo; + if (!hasPermission($config['mod']['edit_volunteers'], $b)) + error($config['error']['noaccess']); + + if (!openBoard($b)) + error("Could not open board!"); + + if (isset($_POST['username'], $_POST['password'])) { + $query = prepare('SELECT * FROM ``mods`` WHERE type = 19 AND boards = :board'); + $query->bindValue(':board', $b); + $query->execute() or error(db_error($query)); + $count = $query->rowCount(); + $query = prepare('SELECT `username` FROM ``mods``'); + $query->execute() or error(db_error($query)); + $volunteers = $query->fetchAll(PDO::FETCH_ASSOC); + + if ($_POST['username'] == '') + error(sprintf($config['error']['required'], 'username')); + if ($_POST['password'] == '') + error(sprintf($config['error']['required'], 'password')); + if (!preg_match('/^[a-zA-Z0-9._]{1,30}$/', $_POST['username'])) + error(_('Invalid username')); + + if ($count > 10) { + error(_('Too many board volunteers!')); + } + + foreach ($volunteers as $i => $v) { + if (strtolower($_POST['username']) == strtolower($v['username'])) { + error(_('Refusing to create a volunteer with the same username as an existing one.')); + } + } + + $salt = generate_salt(); + $password = hash('sha256', $salt . sha1($_POST['password'])); + + $query = prepare('INSERT INTO ``mods`` VALUES (NULL, :username, :password, :salt, 19, :board)'); + $query->bindValue(':username', $_POST['username']); + $query->bindValue(':password', $password); + $query->bindValue(':salt', $salt); + $query->bindValue(':board', $b); + $query->execute() or error(db_error($query)); + + $userID = $pdo->lastInsertId(); + + + modLog('Created a new volunteer: ' . utf8tohtml($_POST['username']) . ' (#' . $userID . ')'); + } + + if (isset($_POST['delete'])){ + foreach ($_POST['delete'] as $i => $d){ + $query = prepare('SELECT * FROM ``mods`` WHERE id = :id'); + $query->bindValue(':id', $d); + $query->execute() or error(db_error($query)); + + $result = $query->fetch(PDO::FETCH_ASSOC); + + if (!$result) { + error(_('Volunteer does not exist!')); + } + + if ($result['boards'] != $b || $result['type'] != BOARDVOLUNTEER) { + error($config['error']['noaccess']); + } + + $query = prepare('DELETE FROM ``mods`` WHERE id = :id'); + $query->bindValue(':id', $d); + $query->execute() or error(db_error($query)); + } + } + + $query = prepare('SELECT * FROM ``mods`` WHERE type = 19 AND boards = :board'); + $query->bindValue(':board', $b); + $query->execute() or error(db_error($query)); + $volunteers = $query->fetchAll(PDO::FETCH_ASSOC); + + mod_page(_('Edit volunteers'), 'mod/volunteers.html', array('board'=>$board,'token'=>make_secure_link_token('volunteers/'.$board['uri']),'volunteers'=>$volunteers)); + + } + + function mod_8_flags($b) { + global $config, $mod, $board; + require_once 'inc/image.php'; + if (!hasPermission($config['mod']['edit_flags'], $b)) + error($config['error']['noaccess']); + + if (!openBoard($b)) + error("Could not open board!"); + + if (file_exists("$b/flags.ser")) + $config['user_flags'] = unserialize(file_get_contents("$b/flags.ser")); + + $dir = 'static/custom-flags/'.$b; + + if (!is_dir($dir)){ + mkdir($dir, 0777, true); + } + + function handle_file($id = false, $description, $b, $dir) { + global $config; + + if (!isset($description) and $description) + error(_('You must enter a flag description!')); + + if (strlen($description) > 255) + error(_('Flag description too long!')); + + if ($id) { + $f = 'flag-'.$id; + } else { + $f = 'file'; + $id = time() . substr(microtime(), 2, 3); + } + + $upload = $_FILES[$f]['tmp_name']; + $banners = array_diff(scandir($dir), array('..', '.')); + + if (!is_readable($upload)) + error($config['error']['nomove']); + + $extension = strtolower(mb_substr($_FILES[$f]['name'], mb_strrpos($_FILES[$f]['name'], '.') + 1)); + + if ($extension != 'png') { + error(_('Flags must be in PNG format.')); + } + + if (filesize($upload) > 48000){ + error(_('File too large!')); + } + + if (!$size = @getimagesize($upload)) { + error($config['error']['invalidimg']); + } + + if ($size[0] > 20 or $size[0] < 11 or $size[1] > 16 or $size[1] < 11){ + error(_('Image wrong size!')); + } + if (sizeof($banners) > 256) { + error(_('Too many flags.')); + } + + copy($upload, "$dir/$id.$extension"); + purge("$dir/$id.$extension"); + $config['user_flags'][$id] = utf8tohtml($description); + file_write($b.'/flags.ser', serialize($config['user_flags'])); + } + + // Handle a new flag, if any. + if (isset($_FILES['file'])){ + handle_file(false, $_POST['description'], $b, $dir); + } + + // Handle edits to existing flags. + foreach ($_FILES as $k => $a) { + if (empty($_FILES[$k]['tmp_name'])) continue; + + if (preg_match('/^flag-(\d+)$/', $k, $matches)) { + $id = (int)$matches[1]; + if (!isset($_POST['description-'.$id])) continue; + + if (isset($config['user_flags'][$id])) { + handle_file($id, $_POST['description-'.$id], $b, $dir); + } + } + } + + // Description just changed, flag not edited. + foreach ($_POST as $k => $v) { + if (!preg_match('/^description-(\d+)$/', $k, $matches)) continue; + $id = (int)$matches[1]; + if (!isset($_POST['description-'.$id])) continue; + + $description = $_POST['description-'.$id]; + + if (strlen($description) > 255) + error(_('Flag description too long!')); + $config['user_flags'][$id] = utf8tohtml($description); + file_write($b.'/flags.ser', serialize($config['user_flags'])); + } + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $flags = << $d){ + if (!preg_match('/[0-9+]/', $d)){ + error('Nice try.'); + } + unlink("$dir/$d.png"); + $id = explode('.', $d)[0]; + unset($config['user_flags'][$id]); + file_write($b.'/flags.ser', serialize($config['user_flags'])); + } + } + + if (isset($_POST['alphabetize'])) { + asort($config['user_flags'], SORT_NATURAL | SORT_FLAG_CASE); + file_write($b.'/flags.ser', serialize($config['user_flags'])); + } + + $banners = array_diff(scandir($dir), array('..', '.')); + mod_page(_('Edit flags'), 'mod/flags.html', array('board'=>$board,'banners'=>$banners,'token'=>make_secure_link_token('banners/'.$board['uri']))); + } + + function mod_8_banners($b) { + global $config, $mod, $board; + require_once 'inc/image.php'; + + if (!hasPermission($config['mod']['edit_banners'], $b)) + error($config['error']['noaccess']); + + if (!openBoard($b)) + error("Could not open board!"); + + $dir = 'static/banners/'.$b; + + if (!is_dir($dir)){ + mkdir($dir, 0777, true); + } + + + if (isset($_FILES['file'])){ + $upload = $_FILES['file']['tmp_name']; + $banners = array_diff(scandir($dir), array('..', '.')); + + if (!is_readable($upload)) + error($config['error']['nomove']); + + $id = time() . substr(microtime(), 2, 3); + $extension = strtolower(mb_substr($_FILES['file']['name'], mb_strrpos($_FILES['file']['name'], '.') + 1)); + + if (!in_array($extension, array('jpg','jpeg','png','gif'))){ + error('Not an image extension.'); + } + + if (filesize($upload) > 512000){ + error('File too large!'); + } + + if (!$size = @getimagesize($upload)) { + error($config['error']['invalidimg']); + } + + if ($size[0] != 300 or $size[1] != 100){ + error('Image wrong size!'); + } + if (sizeof($banners) >= 50) { + error('Too many banners.'); + } + + copy($upload, "$dir/$id.$extension"); + } + + if (isset($_POST['delete'])){ + foreach ($_POST['delete'] as $i => $d){ + if (!preg_match('/[0-9+]\.(png|jpeg|jpg|gif)/', $d)){ + error('Nice try.'); + } + unlink("$dir/$d"); + } + } + + $banners = array_diff(scandir($dir), array('..', '.')); + mod_page(_('Edit banners'), 'mod/banners.html', array('board'=>$board,'banners'=>$banners,'token'=>make_secure_link_token('banners/'.$board['uri']))); + + } + + function mod_8_settings($b) { + global $config, $mod; + + //if ($b === 'infinity' && $mod['type'] !== ADMIN) + // error('Settings temporarily disabled for this board.'); + + if (!in_array($b, $mod['boards']) and $mod['boards'][0] != '*') + error($config['error']['noaccess']); + + if (!hasPermission($config['mod']['edit_settings'], $b)) + error($config['error']['noaccess']); + + if (!openBoard($b)) + error("Could not open board!"); + + $possible_languages = array_diff(scandir('inc/locale/'), array('..', '.', '.tx', 'README.md')); + + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + $title = $_POST['title']; + $subtitle = $_POST['subtitle']; + $country_flags = isset($_POST['country_flags']) ? 'true' : 'false'; + $field_disable_name = isset($_POST['field_disable_name']) ? 'true' : 'false'; + $enable_embedding = isset($_POST['enable_embedding']) ? 'true' : 'false'; + $force_image_op = isset($_POST['force_image_op']) ? 'true' : 'false'; + $disable_images = isset($_POST['disable_images']) ? 'true' : 'false'; + $poster_ids = isset($_POST['poster_ids']) ? 'true' : 'false'; + $show_sages = isset($_POST['show_sages']) ? 'true' : 'false'; + $auto_unicode = isset($_POST['auto_unicode']) ? 'true' : 'false'; + $strip_combining_chars = isset($_POST['strip_combining_chars']) ? 'true' : 'false'; + $allow_roll = isset($_POST['allow_roll']) ? 'true' : 'false'; + $image_reject_repost = isset($_POST['image_reject_repost']) ? 'true' : 'false'; + $image_reject_repost_in_thread = isset($_POST['image_reject_repost_in_thread']) ? 'true' : 'false'; + $early_404 = isset($_POST['early_404']) ? 'true' : 'false'; + $allow_delete = isset($_POST['allow_delete']) ? 'true' : 'false'; + $allow_flash = isset($_POST['allow_flash']) ? '$config[\'allowed_ext_files\'][] = \'swf\';' : ''; + $allow_pdf = isset($_POST['allow_pdf']) ? '$config[\'allowed_ext_files\'][] = \'pdf\';' : ''; + $code_tags = isset($_POST['code_tags']) ? '$config[\'additional_javascript\'][] = \'js/code_tags/run_prettify.js\';$config[\'markup\'][] = array("/\[code\](.+?)\[\/code\]/ms", "
\$1
");' : ''; + $katex = isset($_POST['katex']) ? '$config[\'katex\'] = true;$config[\'additional_javascript\'][] = \'js/katex/katex.min.js\'; $config[\'markup\'][] = array("/\[tex\](.+?)\[\/tex\]/ms", "\$1"); $config[\'additional_javascript\'][] = \'js/katex-enable.js\';' : ''; + $user_flags = isset($_POST['user_flags']) ? "if (file_exists('$b/flags.php')) { include 'flags.php'; }\n" : ''; + $captcha = isset($_POST['captcha']) ? 'true' : 'false'; + $force_subject_op = isset($_POST['force_subject_op']) ? 'true' : 'false'; + $force_flag = isset($_POST['force_flag']) ? 'true' : 'false'; + $tor_posting = isset($_POST['tor_posting']) ? 'true' : 'false'; + $new_thread_capt = isset($_POST['new_thread_capt']) ? 'true' : 'false'; + $oekaki = isset($_POST['oekaki']) ? 'true' : 'false'; + + if ($_POST['locale'] !== 'en' && in_array($_POST['locale'], $possible_languages)) { + $locale = "\$config['locale'] = '{$_POST['locale']}.UTF-8';"; + } else { + $locale = ''; + } + + if (isset($_POST['max_images']) && (int)$_POST['max_images'] && (int)$_POST['max_images'] <= 5) { + $_POST['max_images'] = (int)$_POST['max_images']; + $multiimage = "\$config['max_images'] = {$_POST['max_images']}; + \$config['additional_javascript'][] = 'js/multi-image.js';"; + } else { + $multiimage = ''; + } + + $anonymous = base64_encode($_POST['anonymous']); + $blotter = base64_encode(purify_html(html_entity_decode($_POST['blotter']))); + $add_to_config = @file_get_contents($b.'/extra_config.php'); + $replace = ''; + + if (isset($_POST['replace'])) { + if (sizeof($_POST['replace']) > 200 || sizeof($_POST['with']) > 200) { + error(_('Sorry, max 200 wordfilters allowed.')); + } + if (count($_POST['replace']) == count($_POST['with'])) { + foreach ($_POST['replace'] as $i => $r ) { + if ($r !== '') { + $w = $_POST['with'][$i]; + + if (strlen($w) > 255) { + error(sprintf(_('Sorry, %s is too long. Max replacement is 255 characters'), utf8tohtml($w))); + } + + $replace .= '$config[\'wordfilters\'][] = array(base64_decode(\'' . base64_encode($r) . '\'), base64_decode(\'' . base64_encode($w) . '\'));'; + } + } + } + if (is_billion_laughs($_POST['replace'], $_POST['with'])) { + error(_('Wordfilters may not wordfilter previous wordfilters. For example, if a filters to bb and b filters to cc, that is not allowed.')); + } + } + + if (isset($_POST['hour_max_threads']) && in_array($_POST['hour_max_threads'], ['10', '25', '50', '100'])) { + $hour_max_threads = $_POST['hour_max_threads']; + } else { + $hour_max_threads = 'false'; + } + + if (isset($_POST['max_pages'])) { + $mp = (int)$_POST['max_pages']; + if ($mp > 25 || $mp < 2) { + $max_pages = 15; + } else { + $max_pages = $mp; + } + } else { + $max_pages = 15; + } + + if (isset($_POST['reply_limit'])) { + $rl = (int)$_POST['reply_limit']; + if ($rl > 750 || $rl < 250 || $rl % 25) { + $reply_limit = 250; + } else { + $reply_limit = $rl; + } + } else { + $reply_limit = 250; + } + + if (isset($_POST['max_newlines'])) { + $mn = (int)$_POST['max_newlines']; + if ($mn < 20 || $mn > 300) { + $max_newlines = 0; + } else { + $max_newlines = $mn; + } + } else { + $max_newlines = $mn; + } + + if (!(strlen($title) < 40)) + error('Invalid title'); + if (!(strlen($subtitle) < 200)) + error('Invalid subtitle'); + + $query = prepare('UPDATE ``boards`` SET `title` = :title, `subtitle` = :subtitle, `indexed` = :indexed, `public_bans` = :public_bans, `public_logs` = :public_logs, `8archive` = :8archive WHERE `uri` = :uri'); + $query->bindValue(':title', $title); + $query->bindValue(':subtitle', $subtitle); + $query->bindValue(':uri', $b); + $query->bindValue(':indexed', !isset($_POST['meta_noindex'])); + $query->bindValue(':public_bans', isset($_POST['public_bans'])); + $query->bindValue(':public_logs', (int)$_POST['public_logs']); + $query->bindValue(':8archive', isset($_POST['8archive'])); + $query->execute() or error(db_error($query)); + + $config_file = <<fetchAll(PDO::FETCH_ASSOC))); + file_write($b.'/config.php', $config_file); + file_write('stylesheets/board/'.$b.'.css', $clean_css); + + $_config = $config; + unset($config['wordfilters']); + + // Faster than openBoard and bypasses cache...we're trusting the PHP output + // to be safe enough to run with every request, we can eval it here. + eval(str_replace('flags.php', "$b/flags.php", preg_replace('/^\<\?php$/m', '', $config_file))); + // czaks: maybe reconsider using it, now that config is cached? + + // Clean the cache + if ($config['cache']['enabled']) { + cache::delete('board_' . $board['uri']); + cache::delete('all_boards'); + + cache::delete('config_' . $board['uri']); + cache::delete('events_' . $board['uri']); + + unlink('tmp/cache/locale_' . $board['uri']); + } + + // be smarter about rebuilds...only some changes really require us to rebuild all threads + if ($_config['captcha']['enabled'] != $config['captcha']['enabled'] + || $_config['new_thread_capt'] != $config['new_thread_capt'] /*New thread captcha - if toggling "enable captcha" requires this, toggling new thread capt does too, I guess.*/ + || $_config['captcha']['extra'] != $config['captcha']['extra'] + || $_config['blotter'] != $config['blotter'] + || $_config['field_disable_name'] != $config['field_disable_name'] + || $_config['show_sages'] != (isset($config['show_sages']) && $config['show_sages'])) { + buildIndex(); + $query = query(sprintf("SELECT `id` FROM ``posts_%s`` WHERE `thread` IS NULL", $b)) or error(db_error()); + while ($post = $query->fetch(PDO::FETCH_ASSOC)) { + buildThread($post['id']); + } + } + + modLog('Edited board settings', $b); + } + + $query = prepare('SELECT * FROM boards WHERE uri = :board'); + $query->bindValue(':board', $b); + $query->execute() or error(db_error($query)); + $board = $query->fetchAll()[0]; + + $css = @file_get_contents('stylesheets/board/' . $board['uri'] . '.css'); + + mod_page(_('Board configuration'), 'mod/settings.html', array('board'=>$board, 'css'=>prettify_textarea($css), 'token'=>make_secure_link_token('settings/'.$board['uri']), 'languages'=>$possible_languages,'allowed_urls'=>$config['allowed_offsite_urls'])); + } diff --git a/inc/8chan-mod-pages.php b/inc/8chan-mod-pages.php index 135a1c46..7894be58 100644 --- a/inc/8chan-mod-pages.php +++ b/inc/8chan-mod-pages.php @@ -1,26 +1,4 @@ $v) { - $arr[$v] = $arr2[$k]; - } - - for ($i = 0; $i <= sizeof($arr); $i++) { - $cur = array_slice($arr, $i, 1); - $pst = array_slice($arr, 0, $i); - if (!$cur) continue; - $kk = array_keys($cur)[0]; - $vv = array_values($cur)[0]; - foreach ($pst as $k => $v) { - if (str_replace($kk, $vv, $v) != $v) - return true; - } - } - return false; - } - } - $config['mod']['show_ip'] = GLOBALVOLUNTEER; $config['mod']['show_ip_less'] = BOARDVOLUNTEER; $config['mod']['manageusers'] = GLOBALVOLUNTEER; @@ -67,636 +45,10 @@ $config['mod']['shadow_capcode'] = 'Global Volunteer'; - $config['mod']['custom_pages']['/tags/(\%b)'] = function ($b) { - global $board, $config; - - if (!openBoard($b)) - error("Could not open board!"); - - if (!hasPermission($config['mod']['edit_tags'], $b)) - error($config['error']['noaccess']); - - if (isset($_POST['tags'])) { - if (sizeof($_POST['tags']) > 5) - error(_('Too many tags.')); - - $delete = prepare('DELETE FROM ``board_tags`` WHERE uri = :uri'); - $delete->bindValue(':uri', $b); - $delete->execute(); - - foreach ($_POST['tags'] as $i => $tag) { - if ($tag) { - if (strlen($tag) > 255) - continue; - - $insert = prepare('INSERT INTO ``board_tags``(uri, tag) VALUES (:uri, :tag)'); - $insert->bindValue(':uri', $b); - $insert->bindValue(':tag', utf8tohtml($tag)); - $insert->execute(); - } - } - - $update = prepare('UPDATE ``boards`` SET sfw = :sfw WHERE uri = :uri'); - $update->bindValue(':uri', $b); - $update->bindValue(':sfw', isset($_POST['sfw'])); - $update->execute(); - } - $query = prepare('SELECT * FROM ``board_tags`` WHERE uri = :uri'); - $query->bindValue(':uri', $b); - $query->execute(); - - $tags = $query->fetchAll(); - - $query = prepare('SELECT `sfw` FROM ``boards`` WHERE uri = :uri'); - $query->bindValue(':uri', $b); - $query->execute(); - - $sfw = $query->fetchColumn(); - - mod_page(_('Edit tags'), 'mod/tags.html', array('board'=>$board,'token'=>make_secure_link_token('tags/'.$board['uri']), 'tags'=>$tags, 'sfw'=>$sfw)); - }; - - $config['mod']['custom_pages']['/reassign/(\%b)'] = function($b) { - global $board, $config; - - if (!openBoard($b)) - error("Could not open board!"); - - if (!hasPermission($config['mod']['reassign_board'], $b)) - error($config['error']['noaccess']); - - $query = query("SELECT id, username FROM mods WHERE boards = '$b' AND type = 20"); - $mods = $query->fetchAll(); - - if (!$mods) { - error('No mods?'); - } - - $password = base64_encode(openssl_random_pseudo_bytes(9)); - $salt = generate_salt(); - $hashed = hash('sha256', $salt . sha1($password)); - - $query = prepare('UPDATE ``mods`` SET `password` = :hashed, `salt` = :salt WHERE BINARY username = :mod'); - $query->bindValue(':hashed', $hashed); - $query->bindValue(':salt', $salt); - $query->bindValue(':mod', $mods[0]['username']); - $query->execute(); - - $body = "Thanks for your interest in this board. Kindly find the username and password below. You can login at https://8ch.net/mod.php.
Username: {$mods[0]['username']}
Password: {$password}
Thanks for using 8chan!"; - - modLog("Reassigned board /$b/"); - - mod_page(_('Edit reassign'), 'blank.html', array('board'=>$board,'token'=>make_secure_link_token('reassign/'.$board['uri']),'body'=>$body)); - }; - - $config['mod']['custom_pages']['/volunteers/(\%b)'] = function($b) { - global $board, $config, $pdo; - if (!hasPermission($config['mod']['edit_volunteers'], $b)) - error($config['error']['noaccess']); - - if (!openBoard($b)) - error("Could not open board!"); - - if (isset($_POST['username'], $_POST['password'])) { - $query = prepare('SELECT * FROM ``mods`` WHERE type = 19 AND boards = :board'); - $query->bindValue(':board', $b); - $query->execute() or error(db_error($query)); - $count = $query->rowCount(); - $query = prepare('SELECT `username` FROM ``mods``'); - $query->execute() or error(db_error($query)); - $volunteers = $query->fetchAll(PDO::FETCH_ASSOC); - - if ($_POST['username'] == '') - error(sprintf($config['error']['required'], 'username')); - if ($_POST['password'] == '') - error(sprintf($config['error']['required'], 'password')); - if (!preg_match('/^[a-zA-Z0-9._]{1,30}$/', $_POST['username'])) - error(_('Invalid username')); - - if ($count > 10) { - error(_('Too many board volunteers!')); - } - - foreach ($volunteers as $i => $v) { - if (strtolower($_POST['username']) == strtolower($v['username'])) { - error(_('Refusing to create a volunteer with the same username as an existing one.')); - } - } - - $salt = generate_salt(); - $password = hash('sha256', $salt . sha1($_POST['password'])); - - $query = prepare('INSERT INTO ``mods`` VALUES (NULL, :username, :password, :salt, 19, :board)'); - $query->bindValue(':username', $_POST['username']); - $query->bindValue(':password', $password); - $query->bindValue(':salt', $salt); - $query->bindValue(':board', $b); - $query->execute() or error(db_error($query)); - - $userID = $pdo->lastInsertId(); - - - modLog('Created a new volunteer: ' . utf8tohtml($_POST['username']) . ' (#' . $userID . ')'); - } - - if (isset($_POST['delete'])){ - foreach ($_POST['delete'] as $i => $d){ - $query = prepare('SELECT * FROM ``mods`` WHERE id = :id'); - $query->bindValue(':id', $d); - $query->execute() or error(db_error($query)); - - $result = $query->fetch(PDO::FETCH_ASSOC); - - if (!$result) { - error(_('Volunteer does not exist!')); - } - - if ($result['boards'] != $b || $result['type'] != BOARDVOLUNTEER) { - error($config['error']['noaccess']); - } - - $query = prepare('DELETE FROM ``mods`` WHERE id = :id'); - $query->bindValue(':id', $d); - $query->execute() or error(db_error($query)); - } - } - - $query = prepare('SELECT * FROM ``mods`` WHERE type = 19 AND boards = :board'); - $query->bindValue(':board', $b); - $query->execute() or error(db_error($query)); - $volunteers = $query->fetchAll(PDO::FETCH_ASSOC); - - mod_page(_('Edit volunteers'), 'mod/volunteers.html', array('board'=>$board,'token'=>make_secure_link_token('volunteers/'.$board['uri']),'volunteers'=>$volunteers)); - - }; - - $config['mod']['custom_pages']['/flags/(\%b)'] = function($b) { - global $config, $mod, $board; - require_once 'inc/image.php'; - if (!hasPermission($config['mod']['edit_flags'], $b)) - error($config['error']['noaccess']); - - if (!openBoard($b)) - error("Could not open board!"); - - if (file_exists("$b/flags.ser")) - $config['user_flags'] = unserialize(file_get_contents("$b/flags.ser")); - - $dir = 'static/custom-flags/'.$b; - - if (!is_dir($dir)){ - mkdir($dir, 0777, true); - } - - function handle_file($id = false, $description, $b, $dir) { - global $config; - - if (!isset($description) and $description) - error(_('You must enter a flag description!')); - - if (strlen($description) > 255) - error(_('Flag description too long!')); - - if ($id) { - $f = 'flag-'.$id; - } else { - $f = 'file'; - $id = time() . substr(microtime(), 2, 3); - } - - $upload = $_FILES[$f]['tmp_name']; - $banners = array_diff(scandir($dir), array('..', '.')); - - if (!is_readable($upload)) - error($config['error']['nomove']); - - $extension = strtolower(mb_substr($_FILES[$f]['name'], mb_strrpos($_FILES[$f]['name'], '.') + 1)); - - if ($extension != 'png') { - error(_('Flags must be in PNG format.')); - } - - if (filesize($upload) > 48000){ - error(_('File too large!')); - } - - if (!$size = @getimagesize($upload)) { - error($config['error']['invalidimg']); - } - - if ($size[0] > 20 or $size[0] < 11 or $size[1] > 16 or $size[1] < 11){ - error(_('Image wrong size!')); - } - if (sizeof($banners) > 256) { - error(_('Too many flags.')); - } - - copy($upload, "$dir/$id.$extension"); - purge("$dir/$id.$extension"); - $config['user_flags'][$id] = utf8tohtml($description); - file_write($b.'/flags.ser', serialize($config['user_flags'])); - } - - // Handle a new flag, if any. - if (isset($_FILES['file'])){ - handle_file(false, $_POST['description'], $b, $dir); - } - - // Handle edits to existing flags. - foreach ($_FILES as $k => $a) { - if (empty($_FILES[$k]['tmp_name'])) continue; - - if (preg_match('/^flag-(\d+)$/', $k, $matches)) { - $id = (int)$matches[1]; - if (!isset($_POST['description-'.$id])) continue; - - if (isset($config['user_flags'][$id])) { - handle_file($id, $_POST['description-'.$id], $b, $dir); - } - } - } - - // Description just changed, flag not edited. - foreach ($_POST as $k => $v) { - if (!preg_match('/^description-(\d+)$/', $k, $matches)) continue; - $id = (int)$matches[1]; - if (!isset($_POST['description-'.$id])) continue; - - $description = $_POST['description-'.$id]; - - if (strlen($description) > 255) - error(_('Flag description too long!')); - $config['user_flags'][$id] = utf8tohtml($description); - file_write($b.'/flags.ser', serialize($config['user_flags'])); - } - - if ($_SERVER['REQUEST_METHOD'] === 'POST') { - $flags = << $d){ - if (!preg_match('/[0-9+]/', $d)){ - error('Nice try.'); - } - unlink("$dir/$d.png"); - $id = explode('.', $d)[0]; - unset($config['user_flags'][$id]); - file_write($b.'/flags.ser', serialize($config['user_flags'])); - } - } - - if (isset($_POST['alphabetize'])) { - asort($config['user_flags'], SORT_NATURAL | SORT_FLAG_CASE); - file_write($b.'/flags.ser', serialize($config['user_flags'])); - } - - $banners = array_diff(scandir($dir), array('..', '.')); - mod_page(_('Edit flags'), 'mod/flags.html', array('board'=>$board,'banners'=>$banners,'token'=>make_secure_link_token('banners/'.$board['uri']))); - }; - - $config['mod']['custom_pages']['/banners/(\%b)'] = function($b) { - global $config, $mod, $board; - require_once 'inc/image.php'; - - if (!hasPermission($config['mod']['edit_banners'], $b)) - error($config['error']['noaccess']); - - if (!openBoard($b)) - error("Could not open board!"); - - $dir = 'static/banners/'.$b; - - if (!is_dir($dir)){ - mkdir($dir, 0777, true); - } - - - if (isset($_FILES['file'])){ - $upload = $_FILES['file']['tmp_name']; - $banners = array_diff(scandir($dir), array('..', '.')); - - if (!is_readable($upload)) - error($config['error']['nomove']); - - $id = time() . substr(microtime(), 2, 3); - $extension = strtolower(mb_substr($_FILES['file']['name'], mb_strrpos($_FILES['file']['name'], '.') + 1)); - - if (!in_array($extension, array('jpg','jpeg','png','gif'))){ - error('Not an image extension.'); - } - - if (filesize($upload) > 512000){ - error('File too large!'); - } - - if (!$size = @getimagesize($upload)) { - error($config['error']['invalidimg']); - } - - if ($size[0] != 300 or $size[1] != 100){ - error('Image wrong size!'); - } - if (sizeof($banners) >= 50) { - error('Too many banners.'); - } - - copy($upload, "$dir/$id.$extension"); - } - - if (isset($_POST['delete'])){ - foreach ($_POST['delete'] as $i => $d){ - if (!preg_match('/[0-9+]\.(png|jpeg|jpg|gif)/', $d)){ - error('Nice try.'); - } - unlink("$dir/$d"); - } - } - - $banners = array_diff(scandir($dir), array('..', '.')); - mod_page(_('Edit banners'), 'mod/banners.html', array('board'=>$board,'banners'=>$banners,'token'=>make_secure_link_token('banners/'.$board['uri']))); - - }; - - $config['mod']['custom_pages']['/settings/(\%b)'] = function($b) { - global $config, $mod; - - //if ($b === 'infinity' && $mod['type'] !== ADMIN) - // error('Settings temporarily disabled for this board.'); - - if (!in_array($b, $mod['boards']) and $mod['boards'][0] != '*') - error($config['error']['noaccess']); - - if (!hasPermission($config['mod']['edit_settings'], $b)) - error($config['error']['noaccess']); - - if (!openBoard($b)) - error("Could not open board!"); - - $possible_languages = array_diff(scandir('inc/locale/'), array('..', '.', '.tx', 'README.md')); - - if ($_SERVER['REQUEST_METHOD'] == 'POST') { - $title = $_POST['title']; - $subtitle = $_POST['subtitle']; - $country_flags = isset($_POST['country_flags']) ? 'true' : 'false'; - $field_disable_name = isset($_POST['field_disable_name']) ? 'true' : 'false'; - $enable_embedding = isset($_POST['enable_embedding']) ? 'true' : 'false'; - $force_image_op = isset($_POST['force_image_op']) ? 'true' : 'false'; - $disable_images = isset($_POST['disable_images']) ? 'true' : 'false'; - $poster_ids = isset($_POST['poster_ids']) ? 'true' : 'false'; - $show_sages = isset($_POST['show_sages']) ? 'true' : 'false'; - $auto_unicode = isset($_POST['auto_unicode']) ? 'true' : 'false'; - $strip_combining_chars = isset($_POST['strip_combining_chars']) ? 'true' : 'false'; - $allow_roll = isset($_POST['allow_roll']) ? 'true' : 'false'; - $image_reject_repost = isset($_POST['image_reject_repost']) ? 'true' : 'false'; - $image_reject_repost_in_thread = isset($_POST['image_reject_repost_in_thread']) ? 'true' : 'false'; - $early_404 = isset($_POST['early_404']) ? 'true' : 'false'; - $allow_delete = isset($_POST['allow_delete']) ? 'true' : 'false'; - $allow_flash = isset($_POST['allow_flash']) ? '$config[\'allowed_ext_files\'][] = \'swf\';' : ''; - $allow_pdf = isset($_POST['allow_pdf']) ? '$config[\'allowed_ext_files\'][] = \'pdf\';' : ''; - $code_tags = isset($_POST['code_tags']) ? '$config[\'additional_javascript\'][] = \'js/code_tags/run_prettify.js\';$config[\'markup\'][] = array("/\[code\](.+?)\[\/code\]/ms", "
\$1
");' : ''; - $katex = isset($_POST['katex']) ? '$config[\'katex\'] = true;$config[\'additional_javascript\'][] = \'js/katex/katex.min.js\'; $config[\'markup\'][] = array("/\[tex\](.+?)\[\/tex\]/ms", "\$1"); $config[\'additional_javascript\'][] = \'js/katex-enable.js\';' : ''; - $user_flags = isset($_POST['user_flags']) ? "if (file_exists('$b/flags.php')) { include 'flags.php'; }\n" : ''; - $captcha = isset($_POST['captcha']) ? 'true' : 'false'; - $force_subject_op = isset($_POST['force_subject_op']) ? 'true' : 'false'; - $force_flag = isset($_POST['force_flag']) ? 'true' : 'false'; - $tor_posting = isset($_POST['tor_posting']) ? 'true' : 'false'; - $new_thread_capt = isset($_POST['new_thread_capt']) ? 'true' : 'false'; - $oekaki = isset($_POST['oekaki']) ? 'true' : 'false'; - - if ($_POST['locale'] !== 'en' && in_array($_POST['locale'], $possible_languages)) { - $locale = "\$config['locale'] = '{$_POST['locale']}.UTF-8';"; - } else { - $locale = ''; - } - - if (isset($_POST['max_images']) && (int)$_POST['max_images'] && (int)$_POST['max_images'] <= 5) { - $_POST['max_images'] = (int)$_POST['max_images']; - $multiimage = "\$config['max_images'] = {$_POST['max_images']}; - \$config['additional_javascript'][] = 'js/multi-image.js';"; - } else { - $multiimage = ''; - } - - $anonymous = base64_encode($_POST['anonymous']); - $blotter = base64_encode(purify_html(html_entity_decode($_POST['blotter']))); - $add_to_config = @file_get_contents($b.'/extra_config.php'); - $replace = ''; - - if (isset($_POST['replace'])) { - if (sizeof($_POST['replace']) > 200 || sizeof($_POST['with']) > 200) { - error(_('Sorry, max 200 wordfilters allowed.')); - } - if (count($_POST['replace']) == count($_POST['with'])) { - foreach ($_POST['replace'] as $i => $r ) { - if ($r !== '') { - $w = $_POST['with'][$i]; - - if (strlen($w) > 255) { - error(sprintf(_('Sorry, %s is too long. Max replacement is 255 characters'), utf8tohtml($w))); - } - - $replace .= '$config[\'wordfilters\'][] = array(base64_decode(\'' . base64_encode($r) . '\'), base64_decode(\'' . base64_encode($w) . '\'));'; - } - } - } - if (is_billion_laughs($_POST['replace'], $_POST['with'])) { - error(_('Wordfilters may not wordfilter previous wordfilters. For example, if a filters to bb and b filters to cc, that is not allowed.')); - } - } - - if (isset($_POST['hour_max_threads']) && in_array($_POST['hour_max_threads'], ['10', '25', '50', '100'])) { - $hour_max_threads = $_POST['hour_max_threads']; - } else { - $hour_max_threads = 'false'; - } - - if (isset($_POST['max_pages'])) { - $mp = (int)$_POST['max_pages']; - if ($mp > 25 || $mp < 2) { - $max_pages = 15; - } else { - $max_pages = $mp; - } - } else { - $max_pages = 15; - } - - if (isset($_POST['reply_limit'])) { - $rl = (int)$_POST['reply_limit']; - if ($rl > 750 || $rl < 250 || $rl % 25) { - $reply_limit = 250; - } else { - $reply_limit = $rl; - } - } else { - $reply_limit = 250; - } - - if (isset($_POST['max_newlines'])) { - $mn = (int)$_POST['max_newlines']; - if ($mn < 20 || $mn > 300) { - $max_newlines = 0; - } else { - $max_newlines = $mn; - } - } else { - $max_newlines = $mn; - } - - if (!(strlen($title) < 40)) - error('Invalid title'); - if (!(strlen($subtitle) < 200)) - error('Invalid subtitle'); - - $query = prepare('UPDATE ``boards`` SET `title` = :title, `subtitle` = :subtitle, `indexed` = :indexed, `public_bans` = :public_bans, `public_logs` = :public_logs, `8archive` = :8archive WHERE `uri` = :uri'); - $query->bindValue(':title', $title); - $query->bindValue(':subtitle', $subtitle); - $query->bindValue(':uri', $b); - $query->bindValue(':indexed', !isset($_POST['meta_noindex'])); - $query->bindValue(':public_bans', isset($_POST['public_bans'])); - $query->bindValue(':public_logs', (int)$_POST['public_logs']); - $query->bindValue(':8archive', isset($_POST['8archive'])); - $query->execute() or error(db_error($query)); - - $config_file = <<fetchAll(PDO::FETCH_ASSOC))); - file_write($b.'/config.php', $config_file); - file_write('stylesheets/board/'.$b.'.css', $clean_css); - - $_config = $config; - unset($config['wordfilters']); - - // Faster than openBoard and bypasses cache...we're trusting the PHP output - // to be safe enough to run with every request, we can eval it here. - eval(str_replace('flags.php', "$b/flags.php", preg_replace('/^\<\?php$/m', '', $config_file))); - // czaks: maybe reconsider using it, now that config is cached? - - // Clean the cache - if ($config['cache']['enabled']) { - cache::delete('board_' . $board['uri']); - cache::delete('all_boards'); - - cache::delete('config_' . $board['uri']); - cache::delete('events_' . $board['uri']); - - unlink('tmp/cache/locale_' . $board['uri']); - } - - // be smarter about rebuilds...only some changes really require us to rebuild all threads - if ($_config['captcha']['enabled'] != $config['captcha']['enabled'] - || $_config['new_thread_capt'] != $config['new_thread_capt'] /*New thread captcha - if toggling "enable captcha" requires this, toggling new thread capt does too, I guess.*/ - || $_config['captcha']['extra'] != $config['captcha']['extra'] - || $_config['blotter'] != $config['blotter'] - || $_config['field_disable_name'] != $config['field_disable_name'] - || $_config['show_sages'] != (isset($config['show_sages']) && $config['show_sages'])) { - buildIndex(); - $query = query(sprintf("SELECT `id` FROM ``posts_%s`` WHERE `thread` IS NULL", $b)) or error(db_error()); - while ($post = $query->fetch(PDO::FETCH_ASSOC)) { - buildThread($post['id']); - } - } - - modLog('Edited board settings', $b); - } - - $query = prepare('SELECT * FROM boards WHERE uri = :board'); - $query->bindValue(':board', $b); - $query->execute() or error(db_error($query)); - $board = $query->fetchAll()[0]; - - $css = @file_get_contents('stylesheets/board/' . $board['uri'] . '.css'); - - mod_page(_('Board configuration'), 'mod/settings.html', array('board'=>$board, 'css'=>prettify_textarea($css), 'token'=>make_secure_link_token('settings/'.$board['uri']), 'languages'=>$possible_languages,'allowed_urls'=>$config['allowed_offsite_urls'])); - }; + // Mod pages code now resides in 8chan-mod-pages-functions.php file + $config['mod']['custom_pages']['/tags/(\%b)'] = '8_tags'; + $config['mod']['custom_pages']['/reassign/(\%b)'] = '8_reassign'; + $config['mod']['custom_pages']['/volunteers/(\%b)'] = '8_volunteers'; + $config['mod']['custom_pages']['/flags/(\%b)'] = '8_flags'; + $config['mod']['custom_pages']['/banners/(\%b)'] = '8_banners'; + $config['mod']['custom_pages']['/settings/(\%b)'] = '8_settings'; diff --git a/inc/instance-config.php b/inc/instance-config.php index bc8d63da..152bb9d4 100644 --- a/inc/instance-config.php +++ b/inc/instance-config.php @@ -8,7 +8,7 @@ * You can copy values from config.php (defaults) and paste them here. */ require_once "lib/htmlpurifier-4.6.0/library/HTMLPurifier.auto.php"; - require_once "8chan-functions.php"; + require_once "instance-functions.php"; // Note - you may want to change some of these in secrets.php instead of here // See the secrets.example.php file @@ -177,19 +177,7 @@ $config['hour_max_threads'] = 10; $config['filters'][] = array( 'condition' => array( - 'custom' => function($post) { - global $config, $board; - if (!$config['hour_max_threads']) return false; - - if ($post['op']) { - $query = prepare(sprintf('SELECT COUNT(*) AS `count` FROM ``posts_%s`` WHERE `thread` IS NULL AND FROM_UNIXTIME(`time`) > DATE_SUB(NOW(), INTERVAL 1 HOUR);', $board['uri'])); - $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']); - $query->execute() or error(db_error($query)); - $r = $query->fetch(PDO::FETCH_ASSOC); - - return ($r['count'] > $config['hour_max_threads']); - } - } + 'custom' => 'test_posts_per_hour' ), 'action' => 'reject', 'message' => 'On this board, to prevent raids the number of threads that can be created per hour is limited. Please try again later, or post in an existing thread.' @@ -212,6 +200,7 @@ $config['enable_antibot'] = false; $config['spam']['unicode'] = false; $config['twig_cache'] = false; $config['report_captcha'] = true; + // 8chan specific mod pages require '8chan-mod-pages.php'; diff --git a/inc/instance-functions.php b/inc/instance-functions.php new file mode 100644 index 00000000..f291714f --- /dev/null +++ b/inc/instance-functions.php @@ -0,0 +1,17 @@ + DATE_SUB(NOW(), INTERVAL 1 HOUR);', $board[$ + $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']); + $query->execute() or error(db_error($query)); + $r = $query->fetch(PDO::FETCH_ASSOC); + + return ($r['count'] > $config['hour_max_threads']); + } +} From a123d8b2eefd71110d9bba4b66d221989db7a2bb Mon Sep 17 00:00:00 2001 From: czaks Date: Mon, 6 Apr 2015 22:42:15 +0200 Subject: [PATCH 36/46] infinity/smart-build: do it other way around (esthetics) --- inc/8chan-mod-config.php | 54 +++ inc/8chan-mod-pages-functions.php | 680 ------------------------------ inc/8chan-mod-pages.php | 640 +++++++++++++++++++++++++++- inc/instance-config.php | 2 +- inc/instance-functions.php | 2 +- 5 files changed, 689 insertions(+), 689 deletions(-) create mode 100644 inc/8chan-mod-config.php delete mode 100644 inc/8chan-mod-pages-functions.php diff --git a/inc/8chan-mod-config.php b/inc/8chan-mod-config.php new file mode 100644 index 00000000..7894be58 --- /dev/null +++ b/inc/8chan-mod-config.php @@ -0,0 +1,54 @@ + 5) - error(_('Too many tags.')); - - $delete = prepare('DELETE FROM ``board_tags`` WHERE uri = :uri'); - $delete->bindValue(':uri', $b); - $delete->execute(); - - foreach ($_POST['tags'] as $i => $tag) { - if ($tag) { - if (strlen($tag) > 255) - continue; - - $insert = prepare('INSERT INTO ``board_tags``(uri, tag) VALUES (:uri, :tag)'); - $insert->bindValue(':uri', $b); - $insert->bindValue(':tag', utf8tohtml($tag)); - $insert->execute(); - } - } - - $update = prepare('UPDATE ``boards`` SET sfw = :sfw WHERE uri = :uri'); - $update->bindValue(':uri', $b); - $update->bindValue(':sfw', isset($_POST['sfw'])); - $update->execute(); - } - $query = prepare('SELECT * FROM ``board_tags`` WHERE uri = :uri'); - $query->bindValue(':uri', $b); - $query->execute(); - - $tags = $query->fetchAll(); - - $query = prepare('SELECT `sfw` FROM ``boards`` WHERE uri = :uri'); - $query->bindValue(':uri', $b); - $query->execute(); - - $sfw = $query->fetchColumn(); - - mod_page(_('Edit tags'), 'mod/tags.html', array('board'=>$board,'token'=>make_secure_link_token('tags/'.$board['uri']), 'tags'=>$tags, 'sfw'=>$sfw)); - } - - function mod_8_reassign($b) { - global $board, $config; - - if (!openBoard($b)) - error("Could not open board!"); - - if (!hasPermission($config['mod']['reassign_board'], $b)) - error($config['error']['noaccess']); - - $query = query("SELECT id, username FROM mods WHERE boards = '$b' AND type = 20"); - $mods = $query->fetchAll(); - - if (!$mods) { - error('No mods?'); - } - - $password = base64_encode(openssl_random_pseudo_bytes(9)); - $salt = generate_salt(); - $hashed = hash('sha256', $salt . sha1($password)); - - $query = prepare('UPDATE ``mods`` SET `password` = :hashed, `salt` = :salt WHERE BINARY username = :mod'); - $query->bindValue(':hashed', $hashed); - $query->bindValue(':salt', $salt); - $query->bindValue(':mod', $mods[0]['username']); - $query->execute(); - - $body = "Thanks for your interest in this board. Kindly find the username and password below. You can login at https://8ch.net/mod.php.
Username: {$mods[0]['username']}
Password: {$password}
Thanks for using 8chan!"; - - modLog("Reassigned board /$b/"); - - mod_page(_('Edit reassign'), 'blank.html', array('board'=>$board,'token'=>make_secure_link_token('reassign/'.$board['uri']),'body'=>$body)); - } - - function mod_8_volunteers($b) { - global $board, $config, $pdo; - if (!hasPermission($config['mod']['edit_volunteers'], $b)) - error($config['error']['noaccess']); - - if (!openBoard($b)) - error("Could not open board!"); - - if (isset($_POST['username'], $_POST['password'])) { - $query = prepare('SELECT * FROM ``mods`` WHERE type = 19 AND boards = :board'); - $query->bindValue(':board', $b); - $query->execute() or error(db_error($query)); - $count = $query->rowCount(); - $query = prepare('SELECT `username` FROM ``mods``'); - $query->execute() or error(db_error($query)); - $volunteers = $query->fetchAll(PDO::FETCH_ASSOC); - - if ($_POST['username'] == '') - error(sprintf($config['error']['required'], 'username')); - if ($_POST['password'] == '') - error(sprintf($config['error']['required'], 'password')); - if (!preg_match('/^[a-zA-Z0-9._]{1,30}$/', $_POST['username'])) - error(_('Invalid username')); - - if ($count > 10) { - error(_('Too many board volunteers!')); - } - - foreach ($volunteers as $i => $v) { - if (strtolower($_POST['username']) == strtolower($v['username'])) { - error(_('Refusing to create a volunteer with the same username as an existing one.')); - } - } - - $salt = generate_salt(); - $password = hash('sha256', $salt . sha1($_POST['password'])); - - $query = prepare('INSERT INTO ``mods`` VALUES (NULL, :username, :password, :salt, 19, :board)'); - $query->bindValue(':username', $_POST['username']); - $query->bindValue(':password', $password); - $query->bindValue(':salt', $salt); - $query->bindValue(':board', $b); - $query->execute() or error(db_error($query)); - - $userID = $pdo->lastInsertId(); - - - modLog('Created a new volunteer: ' . utf8tohtml($_POST['username']) . ' (#' . $userID . ')'); - } - - if (isset($_POST['delete'])){ - foreach ($_POST['delete'] as $i => $d){ - $query = prepare('SELECT * FROM ``mods`` WHERE id = :id'); - $query->bindValue(':id', $d); - $query->execute() or error(db_error($query)); - - $result = $query->fetch(PDO::FETCH_ASSOC); - - if (!$result) { - error(_('Volunteer does not exist!')); - } - - if ($result['boards'] != $b || $result['type'] != BOARDVOLUNTEER) { - error($config['error']['noaccess']); - } - - $query = prepare('DELETE FROM ``mods`` WHERE id = :id'); - $query->bindValue(':id', $d); - $query->execute() or error(db_error($query)); - } - } - - $query = prepare('SELECT * FROM ``mods`` WHERE type = 19 AND boards = :board'); - $query->bindValue(':board', $b); - $query->execute() or error(db_error($query)); - $volunteers = $query->fetchAll(PDO::FETCH_ASSOC); - - mod_page(_('Edit volunteers'), 'mod/volunteers.html', array('board'=>$board,'token'=>make_secure_link_token('volunteers/'.$board['uri']),'volunteers'=>$volunteers)); - - } - - function mod_8_flags($b) { - global $config, $mod, $board; - require_once 'inc/image.php'; - if (!hasPermission($config['mod']['edit_flags'], $b)) - error($config['error']['noaccess']); - - if (!openBoard($b)) - error("Could not open board!"); - - if (file_exists("$b/flags.ser")) - $config['user_flags'] = unserialize(file_get_contents("$b/flags.ser")); - - $dir = 'static/custom-flags/'.$b; - - if (!is_dir($dir)){ - mkdir($dir, 0777, true); - } - - function handle_file($id = false, $description, $b, $dir) { - global $config; - - if (!isset($description) and $description) - error(_('You must enter a flag description!')); - - if (strlen($description) > 255) - error(_('Flag description too long!')); - - if ($id) { - $f = 'flag-'.$id; - } else { - $f = 'file'; - $id = time() . substr(microtime(), 2, 3); - } - - $upload = $_FILES[$f]['tmp_name']; - $banners = array_diff(scandir($dir), array('..', '.')); - - if (!is_readable($upload)) - error($config['error']['nomove']); - - $extension = strtolower(mb_substr($_FILES[$f]['name'], mb_strrpos($_FILES[$f]['name'], '.') + 1)); - - if ($extension != 'png') { - error(_('Flags must be in PNG format.')); - } - - if (filesize($upload) > 48000){ - error(_('File too large!')); - } - - if (!$size = @getimagesize($upload)) { - error($config['error']['invalidimg']); - } - - if ($size[0] > 20 or $size[0] < 11 or $size[1] > 16 or $size[1] < 11){ - error(_('Image wrong size!')); - } - if (sizeof($banners) > 256) { - error(_('Too many flags.')); - } - - copy($upload, "$dir/$id.$extension"); - purge("$dir/$id.$extension"); - $config['user_flags'][$id] = utf8tohtml($description); - file_write($b.'/flags.ser', serialize($config['user_flags'])); - } - - // Handle a new flag, if any. - if (isset($_FILES['file'])){ - handle_file(false, $_POST['description'], $b, $dir); - } - - // Handle edits to existing flags. - foreach ($_FILES as $k => $a) { - if (empty($_FILES[$k]['tmp_name'])) continue; - - if (preg_match('/^flag-(\d+)$/', $k, $matches)) { - $id = (int)$matches[1]; - if (!isset($_POST['description-'.$id])) continue; - - if (isset($config['user_flags'][$id])) { - handle_file($id, $_POST['description-'.$id], $b, $dir); - } - } - } - - // Description just changed, flag not edited. - foreach ($_POST as $k => $v) { - if (!preg_match('/^description-(\d+)$/', $k, $matches)) continue; - $id = (int)$matches[1]; - if (!isset($_POST['description-'.$id])) continue; - - $description = $_POST['description-'.$id]; - - if (strlen($description) > 255) - error(_('Flag description too long!')); - $config['user_flags'][$id] = utf8tohtml($description); - file_write($b.'/flags.ser', serialize($config['user_flags'])); - } - - if ($_SERVER['REQUEST_METHOD'] === 'POST') { - $flags = << $d){ - if (!preg_match('/[0-9+]/', $d)){ - error('Nice try.'); - } - unlink("$dir/$d.png"); - $id = explode('.', $d)[0]; - unset($config['user_flags'][$id]); - file_write($b.'/flags.ser', serialize($config['user_flags'])); - } - } - - if (isset($_POST['alphabetize'])) { - asort($config['user_flags'], SORT_NATURAL | SORT_FLAG_CASE); - file_write($b.'/flags.ser', serialize($config['user_flags'])); - } - - $banners = array_diff(scandir($dir), array('..', '.')); - mod_page(_('Edit flags'), 'mod/flags.html', array('board'=>$board,'banners'=>$banners,'token'=>make_secure_link_token('banners/'.$board['uri']))); - } - - function mod_8_banners($b) { - global $config, $mod, $board; - require_once 'inc/image.php'; - - if (!hasPermission($config['mod']['edit_banners'], $b)) - error($config['error']['noaccess']); - - if (!openBoard($b)) - error("Could not open board!"); - - $dir = 'static/banners/'.$b; - - if (!is_dir($dir)){ - mkdir($dir, 0777, true); - } - - - if (isset($_FILES['file'])){ - $upload = $_FILES['file']['tmp_name']; - $banners = array_diff(scandir($dir), array('..', '.')); - - if (!is_readable($upload)) - error($config['error']['nomove']); - - $id = time() . substr(microtime(), 2, 3); - $extension = strtolower(mb_substr($_FILES['file']['name'], mb_strrpos($_FILES['file']['name'], '.') + 1)); - - if (!in_array($extension, array('jpg','jpeg','png','gif'))){ - error('Not an image extension.'); - } - - if (filesize($upload) > 512000){ - error('File too large!'); - } - - if (!$size = @getimagesize($upload)) { - error($config['error']['invalidimg']); - } - - if ($size[0] != 300 or $size[1] != 100){ - error('Image wrong size!'); - } - if (sizeof($banners) >= 50) { - error('Too many banners.'); - } - - copy($upload, "$dir/$id.$extension"); - } - - if (isset($_POST['delete'])){ - foreach ($_POST['delete'] as $i => $d){ - if (!preg_match('/[0-9+]\.(png|jpeg|jpg|gif)/', $d)){ - error('Nice try.'); - } - unlink("$dir/$d"); - } - } - - $banners = array_diff(scandir($dir), array('..', '.')); - mod_page(_('Edit banners'), 'mod/banners.html', array('board'=>$board,'banners'=>$banners,'token'=>make_secure_link_token('banners/'.$board['uri']))); - - } - - function mod_8_settings($b) { - global $config, $mod; - - //if ($b === 'infinity' && $mod['type'] !== ADMIN) - // error('Settings temporarily disabled for this board.'); - - if (!in_array($b, $mod['boards']) and $mod['boards'][0] != '*') - error($config['error']['noaccess']); - - if (!hasPermission($config['mod']['edit_settings'], $b)) - error($config['error']['noaccess']); - - if (!openBoard($b)) - error("Could not open board!"); - - $possible_languages = array_diff(scandir('inc/locale/'), array('..', '.', '.tx', 'README.md')); - - if ($_SERVER['REQUEST_METHOD'] == 'POST') { - $title = $_POST['title']; - $subtitle = $_POST['subtitle']; - $country_flags = isset($_POST['country_flags']) ? 'true' : 'false'; - $field_disable_name = isset($_POST['field_disable_name']) ? 'true' : 'false'; - $enable_embedding = isset($_POST['enable_embedding']) ? 'true' : 'false'; - $force_image_op = isset($_POST['force_image_op']) ? 'true' : 'false'; - $disable_images = isset($_POST['disable_images']) ? 'true' : 'false'; - $poster_ids = isset($_POST['poster_ids']) ? 'true' : 'false'; - $show_sages = isset($_POST['show_sages']) ? 'true' : 'false'; - $auto_unicode = isset($_POST['auto_unicode']) ? 'true' : 'false'; - $strip_combining_chars = isset($_POST['strip_combining_chars']) ? 'true' : 'false'; - $allow_roll = isset($_POST['allow_roll']) ? 'true' : 'false'; - $image_reject_repost = isset($_POST['image_reject_repost']) ? 'true' : 'false'; - $image_reject_repost_in_thread = isset($_POST['image_reject_repost_in_thread']) ? 'true' : 'false'; - $early_404 = isset($_POST['early_404']) ? 'true' : 'false'; - $allow_delete = isset($_POST['allow_delete']) ? 'true' : 'false'; - $allow_flash = isset($_POST['allow_flash']) ? '$config[\'allowed_ext_files\'][] = \'swf\';' : ''; - $allow_pdf = isset($_POST['allow_pdf']) ? '$config[\'allowed_ext_files\'][] = \'pdf\';' : ''; - $code_tags = isset($_POST['code_tags']) ? '$config[\'additional_javascript\'][] = \'js/code_tags/run_prettify.js\';$config[\'markup\'][] = array("/\[code\](.+?)\[\/code\]/ms", "
\$1
");' : ''; - $katex = isset($_POST['katex']) ? '$config[\'katex\'] = true;$config[\'additional_javascript\'][] = \'js/katex/katex.min.js\'; $config[\'markup\'][] = array("/\[tex\](.+?)\[\/tex\]/ms", "\$1"); $config[\'additional_javascript\'][] = \'js/katex-enable.js\';' : ''; - $user_flags = isset($_POST['user_flags']) ? "if (file_exists('$b/flags.php')) { include 'flags.php'; }\n" : ''; - $captcha = isset($_POST['captcha']) ? 'true' : 'false'; - $force_subject_op = isset($_POST['force_subject_op']) ? 'true' : 'false'; - $force_flag = isset($_POST['force_flag']) ? 'true' : 'false'; - $tor_posting = isset($_POST['tor_posting']) ? 'true' : 'false'; - $new_thread_capt = isset($_POST['new_thread_capt']) ? 'true' : 'false'; - $oekaki = isset($_POST['oekaki']) ? 'true' : 'false'; - - if ($_POST['locale'] !== 'en' && in_array($_POST['locale'], $possible_languages)) { - $locale = "\$config['locale'] = '{$_POST['locale']}.UTF-8';"; - } else { - $locale = ''; - } - - if (isset($_POST['max_images']) && (int)$_POST['max_images'] && (int)$_POST['max_images'] <= 5) { - $_POST['max_images'] = (int)$_POST['max_images']; - $multiimage = "\$config['max_images'] = {$_POST['max_images']}; - \$config['additional_javascript'][] = 'js/multi-image.js';"; - } else { - $multiimage = ''; - } - - $anonymous = base64_encode($_POST['anonymous']); - $blotter = base64_encode(purify_html(html_entity_decode($_POST['blotter']))); - $add_to_config = @file_get_contents($b.'/extra_config.php'); - $replace = ''; - - if (isset($_POST['replace'])) { - if (sizeof($_POST['replace']) > 200 || sizeof($_POST['with']) > 200) { - error(_('Sorry, max 200 wordfilters allowed.')); - } - if (count($_POST['replace']) == count($_POST['with'])) { - foreach ($_POST['replace'] as $i => $r ) { - if ($r !== '') { - $w = $_POST['with'][$i]; - - if (strlen($w) > 255) { - error(sprintf(_('Sorry, %s is too long. Max replacement is 255 characters'), utf8tohtml($w))); - } - - $replace .= '$config[\'wordfilters\'][] = array(base64_decode(\'' . base64_encode($r) . '\'), base64_decode(\'' . base64_encode($w) . '\'));'; - } - } - } - if (is_billion_laughs($_POST['replace'], $_POST['with'])) { - error(_('Wordfilters may not wordfilter previous wordfilters. For example, if a filters to bb and b filters to cc, that is not allowed.')); - } - } - - if (isset($_POST['hour_max_threads']) && in_array($_POST['hour_max_threads'], ['10', '25', '50', '100'])) { - $hour_max_threads = $_POST['hour_max_threads']; - } else { - $hour_max_threads = 'false'; - } - - if (isset($_POST['max_pages'])) { - $mp = (int)$_POST['max_pages']; - if ($mp > 25 || $mp < 2) { - $max_pages = 15; - } else { - $max_pages = $mp; - } - } else { - $max_pages = 15; - } - - if (isset($_POST['reply_limit'])) { - $rl = (int)$_POST['reply_limit']; - if ($rl > 750 || $rl < 250 || $rl % 25) { - $reply_limit = 250; - } else { - $reply_limit = $rl; - } - } else { - $reply_limit = 250; - } - - if (isset($_POST['max_newlines'])) { - $mn = (int)$_POST['max_newlines']; - if ($mn < 20 || $mn > 300) { - $max_newlines = 0; - } else { - $max_newlines = $mn; - } - } else { - $max_newlines = $mn; - } - - if (!(strlen($title) < 40)) - error('Invalid title'); - if (!(strlen($subtitle) < 200)) - error('Invalid subtitle'); - - $query = prepare('UPDATE ``boards`` SET `title` = :title, `subtitle` = :subtitle, `indexed` = :indexed, `public_bans` = :public_bans, `public_logs` = :public_logs, `8archive` = :8archive WHERE `uri` = :uri'); - $query->bindValue(':title', $title); - $query->bindValue(':subtitle', $subtitle); - $query->bindValue(':uri', $b); - $query->bindValue(':indexed', !isset($_POST['meta_noindex'])); - $query->bindValue(':public_bans', isset($_POST['public_bans'])); - $query->bindValue(':public_logs', (int)$_POST['public_logs']); - $query->bindValue(':8archive', isset($_POST['8archive'])); - $query->execute() or error(db_error($query)); - - $config_file = <<fetchAll(PDO::FETCH_ASSOC))); - file_write($b.'/config.php', $config_file); - file_write('stylesheets/board/'.$b.'.css', $clean_css); - - $_config = $config; - unset($config['wordfilters']); - - // Faster than openBoard and bypasses cache...we're trusting the PHP output - // to be safe enough to run with every request, we can eval it here. - eval(str_replace('flags.php', "$b/flags.php", preg_replace('/^\<\?php$/m', '', $config_file))); - // czaks: maybe reconsider using it, now that config is cached? - - // Clean the cache - if ($config['cache']['enabled']) { - cache::delete('board_' . $board['uri']); - cache::delete('all_boards'); - - cache::delete('config_' . $board['uri']); - cache::delete('events_' . $board['uri']); - - unlink('tmp/cache/locale_' . $board['uri']); - } - - // be smarter about rebuilds...only some changes really require us to rebuild all threads - if ($_config['captcha']['enabled'] != $config['captcha']['enabled'] - || $_config['new_thread_capt'] != $config['new_thread_capt'] /*New thread captcha - if toggling "enable captcha" requires this, toggling new thread capt does too, I guess.*/ - || $_config['captcha']['extra'] != $config['captcha']['extra'] - || $_config['blotter'] != $config['blotter'] - || $_config['field_disable_name'] != $config['field_disable_name'] - || $_config['show_sages'] != (isset($config['show_sages']) && $config['show_sages'])) { - buildIndex(); - $query = query(sprintf("SELECT `id` FROM ``posts_%s`` WHERE `thread` IS NULL", $b)) or error(db_error()); - while ($post = $query->fetch(PDO::FETCH_ASSOC)) { - buildThread($post['id']); - } - } - - modLog('Edited board settings', $b); - } - - $query = prepare('SELECT * FROM boards WHERE uri = :board'); - $query->bindValue(':board', $b); - $query->execute() or error(db_error($query)); - $board = $query->fetchAll()[0]; - - $css = @file_get_contents('stylesheets/board/' . $board['uri'] . '.css'); - - mod_page(_('Board configuration'), 'mod/settings.html', array('board'=>$board, 'css'=>prettify_textarea($css), 'token'=>make_secure_link_token('settings/'.$board['uri']), 'languages'=>$possible_languages,'allowed_urls'=>$config['allowed_offsite_urls'])); - } diff --git a/inc/8chan-mod-pages.php b/inc/8chan-mod-pages.php index 7894be58..eef96790 100644 --- a/inc/8chan-mod-pages.php +++ b/inc/8chan-mod-pages.php @@ -45,10 +45,636 @@ $config['mod']['shadow_capcode'] = 'Global Volunteer'; - // Mod pages code now resides in 8chan-mod-pages-functions.php file - $config['mod']['custom_pages']['/tags/(\%b)'] = '8_tags'; - $config['mod']['custom_pages']['/reassign/(\%b)'] = '8_reassign'; - $config['mod']['custom_pages']['/volunteers/(\%b)'] = '8_volunteers'; - $config['mod']['custom_pages']['/flags/(\%b)'] = '8_flags'; - $config['mod']['custom_pages']['/banners/(\%b)'] = '8_banners'; - $config['mod']['custom_pages']['/settings/(\%b)'] = '8_settings'; + function mod_8_tags ($b) { + global $board, $config; + + if (!openBoard($b)) + error("Could not open board!"); + + if (!hasPermission($config['mod']['edit_tags'], $b)) + error($config['error']['noaccess']); + + if (isset($_POST['tags'])) { + if (sizeof($_POST['tags']) > 5) + error(_('Too many tags.')); + + $delete = prepare('DELETE FROM ``board_tags`` WHERE uri = :uri'); + $delete->bindValue(':uri', $b); + $delete->execute(); + + foreach ($_POST['tags'] as $i => $tag) { + if ($tag) { + if (strlen($tag) > 255) + continue; + + $insert = prepare('INSERT INTO ``board_tags``(uri, tag) VALUES (:uri, :tag)'); + $insert->bindValue(':uri', $b); + $insert->bindValue(':tag', utf8tohtml($tag)); + $insert->execute(); + } + } + + $update = prepare('UPDATE ``boards`` SET sfw = :sfw WHERE uri = :uri'); + $update->bindValue(':uri', $b); + $update->bindValue(':sfw', isset($_POST['sfw'])); + $update->execute(); + } + $query = prepare('SELECT * FROM ``board_tags`` WHERE uri = :uri'); + $query->bindValue(':uri', $b); + $query->execute(); + + $tags = $query->fetchAll(); + + $query = prepare('SELECT `sfw` FROM ``boards`` WHERE uri = :uri'); + $query->bindValue(':uri', $b); + $query->execute(); + + $sfw = $query->fetchColumn(); + + mod_page(_('Edit tags'), 'mod/tags.html', array('board'=>$board,'token'=>make_secure_link_token('tags/'.$board['uri']), 'tags'=>$tags, 'sfw'=>$sfw)); + } + + function mod_8_reassign($b) { + global $board, $config; + + if (!openBoard($b)) + error("Could not open board!"); + + if (!hasPermission($config['mod']['reassign_board'], $b)) + error($config['error']['noaccess']); + + $query = query("SELECT id, username FROM mods WHERE boards = '$b' AND type = 20"); + $mods = $query->fetchAll(); + + if (!$mods) { + error('No mods?'); + } + + $password = base64_encode(openssl_random_pseudo_bytes(9)); + $salt = generate_salt(); + $hashed = hash('sha256', $salt . sha1($password)); + + $query = prepare('UPDATE ``mods`` SET `password` = :hashed, `salt` = :salt WHERE BINARY username = :mod'); + $query->bindValue(':hashed', $hashed); + $query->bindValue(':salt', $salt); + $query->bindValue(':mod', $mods[0]['username']); + $query->execute(); + + $body = "Thanks for your interest in this board. Kindly find the username and password below. You can login at https://8ch.net/mod.php.
Username: {$mods[0]['username']}
Password: {$password}
Thanks for using 8chan!"; + + modLog("Reassigned board /$b/"); + + mod_page(_('Edit reassign'), 'blank.html', array('board'=>$board,'token'=>make_secure_link_token('reassign/'.$board['uri']),'body'=>$body)); + } + + function mod_8_volunteers($b) { + global $board, $config, $pdo; + if (!hasPermission($config['mod']['edit_volunteers'], $b)) + error($config['error']['noaccess']); + + if (!openBoard($b)) + error("Could not open board!"); + + if (isset($_POST['username'], $_POST['password'])) { + $query = prepare('SELECT * FROM ``mods`` WHERE type = 19 AND boards = :board'); + $query->bindValue(':board', $b); + $query->execute() or error(db_error($query)); + $count = $query->rowCount(); + $query = prepare('SELECT `username` FROM ``mods``'); + $query->execute() or error(db_error($query)); + $volunteers = $query->fetchAll(PDO::FETCH_ASSOC); + + if ($_POST['username'] == '') + error(sprintf($config['error']['required'], 'username')); + if ($_POST['password'] == '') + error(sprintf($config['error']['required'], 'password')); + if (!preg_match('/^[a-zA-Z0-9._]{1,30}$/', $_POST['username'])) + error(_('Invalid username')); + + if ($count > 10) { + error(_('Too many board volunteers!')); + } + + foreach ($volunteers as $i => $v) { + if (strtolower($_POST['username']) == strtolower($v['username'])) { + error(_('Refusing to create a volunteer with the same username as an existing one.')); + } + } + + $salt = generate_salt(); + $password = hash('sha256', $salt . sha1($_POST['password'])); + + $query = prepare('INSERT INTO ``mods`` VALUES (NULL, :username, :password, :salt, 19, :board)'); + $query->bindValue(':username', $_POST['username']); + $query->bindValue(':password', $password); + $query->bindValue(':salt', $salt); + $query->bindValue(':board', $b); + $query->execute() or error(db_error($query)); + + $userID = $pdo->lastInsertId(); + + + modLog('Created a new volunteer: ' . utf8tohtml($_POST['username']) . ' (#' . $userID . ')'); + } + + if (isset($_POST['delete'])){ + foreach ($_POST['delete'] as $i => $d){ + $query = prepare('SELECT * FROM ``mods`` WHERE id = :id'); + $query->bindValue(':id', $d); + $query->execute() or error(db_error($query)); + + $result = $query->fetch(PDO::FETCH_ASSOC); + + if (!$result) { + error(_('Volunteer does not exist!')); + } + + if ($result['boards'] != $b || $result['type'] != BOARDVOLUNTEER) { + error($config['error']['noaccess']); + } + + $query = prepare('DELETE FROM ``mods`` WHERE id = :id'); + $query->bindValue(':id', $d); + $query->execute() or error(db_error($query)); + } + } + + $query = prepare('SELECT * FROM ``mods`` WHERE type = 19 AND boards = :board'); + $query->bindValue(':board', $b); + $query->execute() or error(db_error($query)); + $volunteers = $query->fetchAll(PDO::FETCH_ASSOC); + + mod_page(_('Edit volunteers'), 'mod/volunteers.html', array('board'=>$board,'token'=>make_secure_link_token('volunteers/'.$board['uri']),'volunteers'=>$volunteers)); + + } + + function mod_8_flags($b) { + global $config, $mod, $board; + require_once 'inc/image.php'; + if (!hasPermission($config['mod']['edit_flags'], $b)) + error($config['error']['noaccess']); + + if (!openBoard($b)) + error("Could not open board!"); + + if (file_exists("$b/flags.ser")) + $config['user_flags'] = unserialize(file_get_contents("$b/flags.ser")); + + $dir = 'static/custom-flags/'.$b; + + if (!is_dir($dir)){ + mkdir($dir, 0777, true); + } + + function handle_file($id = false, $description, $b, $dir) { + global $config; + + if (!isset($description) and $description) + error(_('You must enter a flag description!')); + + if (strlen($description) > 255) + error(_('Flag description too long!')); + + if ($id) { + $f = 'flag-'.$id; + } else { + $f = 'file'; + $id = time() . substr(microtime(), 2, 3); + } + + $upload = $_FILES[$f]['tmp_name']; + $banners = array_diff(scandir($dir), array('..', '.')); + + if (!is_readable($upload)) + error($config['error']['nomove']); + + $extension = strtolower(mb_substr($_FILES[$f]['name'], mb_strrpos($_FILES[$f]['name'], '.') + 1)); + + if ($extension != 'png') { + error(_('Flags must be in PNG format.')); + } + + if (filesize($upload) > 48000){ + error(_('File too large!')); + } + + if (!$size = @getimagesize($upload)) { + error($config['error']['invalidimg']); + } + + if ($size[0] > 20 or $size[0] < 11 or $size[1] > 16 or $size[1] < 11){ + error(_('Image wrong size!')); + } + if (sizeof($banners) > 256) { + error(_('Too many flags.')); + } + + copy($upload, "$dir/$id.$extension"); + purge("$dir/$id.$extension"); + $config['user_flags'][$id] = utf8tohtml($description); + file_write($b.'/flags.ser', serialize($config['user_flags'])); + } + + // Handle a new flag, if any. + if (isset($_FILES['file'])){ + handle_file(false, $_POST['description'], $b, $dir); + } + + // Handle edits to existing flags. + foreach ($_FILES as $k => $a) { + if (empty($_FILES[$k]['tmp_name'])) continue; + + if (preg_match('/^flag-(\d+)$/', $k, $matches)) { + $id = (int)$matches[1]; + if (!isset($_POST['description-'.$id])) continue; + + if (isset($config['user_flags'][$id])) { + handle_file($id, $_POST['description-'.$id], $b, $dir); + } + } + } + + // Description just changed, flag not edited. + foreach ($_POST as $k => $v) { + if (!preg_match('/^description-(\d+)$/', $k, $matches)) continue; + $id = (int)$matches[1]; + if (!isset($_POST['description-'.$id])) continue; + + $description = $_POST['description-'.$id]; + + if (strlen($description) > 255) + error(_('Flag description too long!')); + $config['user_flags'][$id] = utf8tohtml($description); + file_write($b.'/flags.ser', serialize($config['user_flags'])); + } + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $flags = << $d){ + if (!preg_match('/[0-9+]/', $d)){ + error('Nice try.'); + } + unlink("$dir/$d.png"); + $id = explode('.', $d)[0]; + unset($config['user_flags'][$id]); + file_write($b.'/flags.ser', serialize($config['user_flags'])); + } + } + + if (isset($_POST['alphabetize'])) { + asort($config['user_flags'], SORT_NATURAL | SORT_FLAG_CASE); + file_write($b.'/flags.ser', serialize($config['user_flags'])); + } + + $banners = array_diff(scandir($dir), array('..', '.')); + mod_page(_('Edit flags'), 'mod/flags.html', array('board'=>$board,'banners'=>$banners,'token'=>make_secure_link_token('banners/'.$board['uri']))); + } + + function mod_8_banners($b) { + global $config, $mod, $board; + require_once 'inc/image.php'; + + if (!hasPermission($config['mod']['edit_banners'], $b)) + error($config['error']['noaccess']); + + if (!openBoard($b)) + error("Could not open board!"); + + $dir = 'static/banners/'.$b; + + if (!is_dir($dir)){ + mkdir($dir, 0777, true); + } + + + if (isset($_FILES['file'])){ + $upload = $_FILES['file']['tmp_name']; + $banners = array_diff(scandir($dir), array('..', '.')); + + if (!is_readable($upload)) + error($config['error']['nomove']); + + $id = time() . substr(microtime(), 2, 3); + $extension = strtolower(mb_substr($_FILES['file']['name'], mb_strrpos($_FILES['file']['name'], '.') + 1)); + + if (!in_array($extension, array('jpg','jpeg','png','gif'))){ + error('Not an image extension.'); + } + + if (filesize($upload) > 512000){ + error('File too large!'); + } + + if (!$size = @getimagesize($upload)) { + error($config['error']['invalidimg']); + } + + if ($size[0] != 300 or $size[1] != 100){ + error('Image wrong size!'); + } + if (sizeof($banners) >= 50) { + error('Too many banners.'); + } + + copy($upload, "$dir/$id.$extension"); + } + + if (isset($_POST['delete'])){ + foreach ($_POST['delete'] as $i => $d){ + if (!preg_match('/[0-9+]\.(png|jpeg|jpg|gif)/', $d)){ + error('Nice try.'); + } + unlink("$dir/$d"); + } + } + + $banners = array_diff(scandir($dir), array('..', '.')); + mod_page(_('Edit banners'), 'mod/banners.html', array('board'=>$board,'banners'=>$banners,'token'=>make_secure_link_token('banners/'.$board['uri']))); + + } + + function mod_8_settings($b) { + global $config, $mod; + + //if ($b === 'infinity' && $mod['type'] !== ADMIN) + // error('Settings temporarily disabled for this board.'); + + if (!in_array($b, $mod['boards']) and $mod['boards'][0] != '*') + error($config['error']['noaccess']); + + if (!hasPermission($config['mod']['edit_settings'], $b)) + error($config['error']['noaccess']); + + if (!openBoard($b)) + error("Could not open board!"); + + $possible_languages = array_diff(scandir('inc/locale/'), array('..', '.', '.tx', 'README.md')); + + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + $title = $_POST['title']; + $subtitle = $_POST['subtitle']; + $country_flags = isset($_POST['country_flags']) ? 'true' : 'false'; + $field_disable_name = isset($_POST['field_disable_name']) ? 'true' : 'false'; + $enable_embedding = isset($_POST['enable_embedding']) ? 'true' : 'false'; + $force_image_op = isset($_POST['force_image_op']) ? 'true' : 'false'; + $disable_images = isset($_POST['disable_images']) ? 'true' : 'false'; + $poster_ids = isset($_POST['poster_ids']) ? 'true' : 'false'; + $show_sages = isset($_POST['show_sages']) ? 'true' : 'false'; + $auto_unicode = isset($_POST['auto_unicode']) ? 'true' : 'false'; + $strip_combining_chars = isset($_POST['strip_combining_chars']) ? 'true' : 'false'; + $allow_roll = isset($_POST['allow_roll']) ? 'true' : 'false'; + $image_reject_repost = isset($_POST['image_reject_repost']) ? 'true' : 'false'; + $image_reject_repost_in_thread = isset($_POST['image_reject_repost_in_thread']) ? 'true' : 'false'; + $early_404 = isset($_POST['early_404']) ? 'true' : 'false'; + $allow_delete = isset($_POST['allow_delete']) ? 'true' : 'false'; + $allow_flash = isset($_POST['allow_flash']) ? '$config[\'allowed_ext_files\'][] = \'swf\';' : ''; + $allow_pdf = isset($_POST['allow_pdf']) ? '$config[\'allowed_ext_files\'][] = \'pdf\';' : ''; + $code_tags = isset($_POST['code_tags']) ? '$config[\'additional_javascript\'][] = \'js/code_tags/run_prettify.js\';$config[\'markup\'][] = array("/\[code\](.+?)\[\/code\]/ms", "
\$1
");' : ''; + $katex = isset($_POST['katex']) ? '$config[\'katex\'] = true;$config[\'additional_javascript\'][] = \'js/katex/katex.min.js\'; $config[\'markup\'][] = array("/\[tex\](.+?)\[\/tex\]/ms", "\$1"); $config[\'additional_javascript\'][] = \'js/katex-enable.js\';' : ''; + $user_flags = isset($_POST['user_flags']) ? "if (file_exists('$b/flags.php')) { include 'flags.php'; }\n" : ''; + $captcha = isset($_POST['captcha']) ? 'true' : 'false'; + $force_subject_op = isset($_POST['force_subject_op']) ? 'true' : 'false'; + $force_flag = isset($_POST['force_flag']) ? 'true' : 'false'; + $tor_posting = isset($_POST['tor_posting']) ? 'true' : 'false'; + $new_thread_capt = isset($_POST['new_thread_capt']) ? 'true' : 'false'; + $oekaki = isset($_POST['oekaki']) ? 'true' : 'false'; + + if ($_POST['locale'] !== 'en' && in_array($_POST['locale'], $possible_languages)) { + $locale = "\$config['locale'] = '{$_POST['locale']}.UTF-8';"; + } else { + $locale = ''; + } + + if (isset($_POST['max_images']) && (int)$_POST['max_images'] && (int)$_POST['max_images'] <= 5) { + $_POST['max_images'] = (int)$_POST['max_images']; + $multiimage = "\$config['max_images'] = {$_POST['max_images']}; + \$config['additional_javascript'][] = 'js/multi-image.js';"; + } else { + $multiimage = ''; + } + + $anonymous = base64_encode($_POST['anonymous']); + $blotter = base64_encode(purify_html(html_entity_decode($_POST['blotter']))); + $add_to_config = @file_get_contents($b.'/extra_config.php'); + $replace = ''; + + if (isset($_POST['replace'])) { + if (sizeof($_POST['replace']) > 200 || sizeof($_POST['with']) > 200) { + error(_('Sorry, max 200 wordfilters allowed.')); + } + if (count($_POST['replace']) == count($_POST['with'])) { + foreach ($_POST['replace'] as $i => $r ) { + if ($r !== '') { + $w = $_POST['with'][$i]; + + if (strlen($w) > 255) { + error(sprintf(_('Sorry, %s is too long. Max replacement is 255 characters'), utf8tohtml($w))); + } + + $replace .= '$config[\'wordfilters\'][] = array(base64_decode(\'' . base64_encode($r) . '\'), base64_decode(\'' . base64_encode($w) . '\'));'; + } + } + } + if (is_billion_laughs($_POST['replace'], $_POST['with'])) { + error(_('Wordfilters may not wordfilter previous wordfilters. For example, if a filters to bb and b filters to cc, that is not allowed.')); + } + } + + if (isset($_POST['hour_max_threads']) && in_array($_POST['hour_max_threads'], ['10', '25', '50', '100'])) { + $hour_max_threads = $_POST['hour_max_threads']; + } else { + $hour_max_threads = 'false'; + } + + if (isset($_POST['max_pages'])) { + $mp = (int)$_POST['max_pages']; + if ($mp > 25 || $mp < 2) { + $max_pages = 15; + } else { + $max_pages = $mp; + } + } else { + $max_pages = 15; + } + + if (isset($_POST['reply_limit'])) { + $rl = (int)$_POST['reply_limit']; + if ($rl > 750 || $rl < 250 || $rl % 25) { + $reply_limit = 250; + } else { + $reply_limit = $rl; + } + } else { + $reply_limit = 250; + } + + if (isset($_POST['max_newlines'])) { + $mn = (int)$_POST['max_newlines']; + if ($mn < 20 || $mn > 300) { + $max_newlines = 0; + } else { + $max_newlines = $mn; + } + } else { + $max_newlines = $mn; + } + + if (!(strlen($title) < 40)) + error('Invalid title'); + if (!(strlen($subtitle) < 200)) + error('Invalid subtitle'); + + $query = prepare('UPDATE ``boards`` SET `title` = :title, `subtitle` = :subtitle, `indexed` = :indexed, `public_bans` = :public_bans, `public_logs` = :public_logs, `8archive` = :8archive WHERE `uri` = :uri'); + $query->bindValue(':title', $title); + $query->bindValue(':subtitle', $subtitle); + $query->bindValue(':uri', $b); + $query->bindValue(':indexed', !isset($_POST['meta_noindex'])); + $query->bindValue(':public_bans', isset($_POST['public_bans'])); + $query->bindValue(':public_logs', (int)$_POST['public_logs']); + $query->bindValue(':8archive', isset($_POST['8archive'])); + $query->execute() or error(db_error($query)); + + $config_file = <<fetchAll(PDO::FETCH_ASSOC))); + file_write($b.'/config.php', $config_file); + file_write('stylesheets/board/'.$b.'.css', $clean_css); + + $_config = $config; + unset($config['wordfilters']); + + // Faster than openBoard and bypasses cache...we're trusting the PHP output + // to be safe enough to run with every request, we can eval it here. + eval(str_replace('flags.php', "$b/flags.php", preg_replace('/^\<\?php$/m', '', $config_file))); + // czaks: maybe reconsider using it, now that config is cached? + + // Clean the cache + if ($config['cache']['enabled']) { + cache::delete('board_' . $board['uri']); + cache::delete('all_boards'); + + cache::delete('config_' . $board['uri']); + cache::delete('events_' . $board['uri']); + + unlink('tmp/cache/locale_' . $board['uri']); + } + + // be smarter about rebuilds...only some changes really require us to rebuild all threads + if ($_config['captcha']['enabled'] != $config['captcha']['enabled'] + || $_config['new_thread_capt'] != $config['new_thread_capt'] /*New thread captcha - if toggling "enable captcha" requires this, toggling new thread capt does too, I guess.*/ + || $_config['captcha']['extra'] != $config['captcha']['extra'] + || $_config['blotter'] != $config['blotter'] + || $_config['field_disable_name'] != $config['field_disable_name'] + || $_config['show_sages'] != (isset($config['show_sages']) && $config['show_sages'])) { + buildIndex(); + $query = query(sprintf("SELECT `id` FROM ``posts_%s`` WHERE `thread` IS NULL", $b)) or error(db_error()); + while ($post = $query->fetch(PDO::FETCH_ASSOC)) { + buildThread($post['id']); + } + } + + modLog('Edited board settings', $b); + } + + $query = prepare('SELECT * FROM boards WHERE uri = :board'); + $query->bindValue(':board', $b); + $query->execute() or error(db_error($query)); + $board = $query->fetchAll()[0]; + + $css = @file_get_contents('stylesheets/board/' . $board['uri'] . '.css'); + + mod_page(_('Board configuration'), 'mod/settings.html', array('board'=>$board, 'css'=>prettify_textarea($css), 'token'=>make_secure_link_token('settings/'.$board['uri']), 'languages'=>$possible_languages,'allowed_urls'=>$config['allowed_offsite_urls'])); + } diff --git a/inc/instance-config.php b/inc/instance-config.php index 152bb9d4..4a1572a7 100644 --- a/inc/instance-config.php +++ b/inc/instance-config.php @@ -202,7 +202,7 @@ $config['twig_cache'] = false; $config['report_captcha'] = true; // 8chan specific mod pages -require '8chan-mod-pages.php'; +require '8chan-mod-config.php'; // Load database credentials require "secrets.php"; diff --git a/inc/instance-functions.php b/inc/instance-functions.php index f291714f..d3fdf24b 100644 --- a/inc/instance-functions.php +++ b/inc/instance-functions.php @@ -1,6 +1,6 @@ Date: Mon, 6 Apr 2015 22:44:48 +0200 Subject: [PATCH 37/46] infinity/smart-build: lol nano --- inc/instance-functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/instance-functions.php b/inc/instance-functions.php index d3fdf24b..a2102f94 100644 --- a/inc/instance-functions.php +++ b/inc/instance-functions.php @@ -7,7 +7,7 @@ function max_posts_per_hour($post) { if (!$config['hour_max_threads']) return false; if ($post['op']) { - $query = prepare(sprintf('SELECT COUNT(*) AS `count` FROM ``posts_%s`` WHERE `thread` IS NULL AND FROM_UNIXTIME(`time`) > DATE_SUB(NOW(), INTERVAL 1 HOUR);', $board[$ + $query = prepare(sprintf('SELECT COUNT(*) AS `count` FROM ``posts_%s`` WHERE `thread` IS NULL AND FROM_UNIXTIME(`time`) > DATE_SUB(NOW(), INTERVAL 1 HOUR);', $board['uri'])); $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']); $query->execute() or error(db_error($query)); $r = $query->fetch(PDO::FETCH_ASSOC); From be88d026bd2aaa66d265a29651b22f792cc957de Mon Sep 17 00:00:00 2001 From: czaks Date: Mon, 6 Apr 2015 22:47:46 +0200 Subject: [PATCH 38/46] infinity/smart-build: typo --- inc/instance-config.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/instance-config.php b/inc/instance-config.php index 4a1572a7..97b20491 100644 --- a/inc/instance-config.php +++ b/inc/instance-config.php @@ -177,7 +177,7 @@ $config['hour_max_threads'] = 10; $config['filters'][] = array( 'condition' => array( - 'custom' => 'test_posts_per_hour' + 'custom' => 'max_posts_per_hour' ), 'action' => 'reject', 'message' => 'On this board, to prevent raids the number of threads that can be created per hour is limited. Please try again later, or post in an existing thread.' From ab74235a4b6a1d0d3ffa4ac1f72cf4e61497a543 Mon Sep 17 00:00:00 2001 From: czaks Date: Mon, 6 Apr 2015 22:51:02 +0200 Subject: [PATCH 39/46] fs cache backend: silence the error --- inc/cache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/cache.php b/inc/cache.php index d6bc9211..852aefa2 100644 --- a/inc/cache.php +++ b/inc/cache.php @@ -132,7 +132,7 @@ class Cache { case 'fs': $key = str_replace('/', '::', $key); $key = str_replace("\0", '', $key); - unlink('tmp/cache/'.$key); + @unlink('tmp/cache/'.$key); break; case 'php': unset(self::$cache[$key]); From 1670da07cb3e9f76ba224c0c716b3c2e5f003059 Mon Sep 17 00:00:00 2001 From: czaks Date: Mon, 6 Apr 2015 22:55:52 +0200 Subject: [PATCH 40/46] infinity/smart-build: move a require to instance-functions --- inc/instance-config.php | 1 - inc/instance-functions.php | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/inc/instance-config.php b/inc/instance-config.php index 97b20491..529b266c 100644 --- a/inc/instance-config.php +++ b/inc/instance-config.php @@ -7,7 +7,6 @@ * * You can copy values from config.php (defaults) and paste them here. */ - require_once "lib/htmlpurifier-4.6.0/library/HTMLPurifier.auto.php"; require_once "instance-functions.php"; // Note - you may want to change some of these in secrets.php instead of here diff --git a/inc/instance-functions.php b/inc/instance-functions.php index a2102f94..9bcc7c3f 100644 --- a/inc/instance-functions.php +++ b/inc/instance-functions.php @@ -2,6 +2,9 @@ require_once("inc/8chan-functions.php"); require_once("inc/8chan-mod-pages.php"); +require_once "lib/htmlpurifier-4.6.0/library/HTMLPurifier.auto.php"; + + function max_posts_per_hour($post) { global $config, $board; if (!$config['hour_max_threads']) return false; From 8a6a20bd9c71e2c3cb48a27655643731bb63f321 Mon Sep 17 00:00:00 2001 From: czaks Date: Mon, 6 Apr 2015 23:54:00 +0200 Subject: [PATCH 41/46] infinity/smart-build: fix context --- inc/8chan-mod-pages.php | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/inc/8chan-mod-pages.php b/inc/8chan-mod-pages.php index eef96790..d3a207bd 100644 --- a/inc/8chan-mod-pages.php +++ b/inc/8chan-mod-pages.php @@ -641,17 +641,6 @@ EOT; eval(str_replace('flags.php', "$b/flags.php", preg_replace('/^\<\?php$/m', '', $config_file))); // czaks: maybe reconsider using it, now that config is cached? - // Clean the cache - if ($config['cache']['enabled']) { - cache::delete('board_' . $board['uri']); - cache::delete('all_boards'); - - cache::delete('config_' . $board['uri']); - cache::delete('events_' . $board['uri']); - - unlink('tmp/cache/locale_' . $board['uri']); - } - // be smarter about rebuilds...only some changes really require us to rebuild all threads if ($_config['captcha']['enabled'] != $config['captcha']['enabled'] || $_config['new_thread_capt'] != $config['new_thread_capt'] /*New thread captcha - if toggling "enable captcha" requires this, toggling new thread capt does too, I guess.*/ @@ -673,6 +662,16 @@ EOT; $query->bindValue(':board', $b); $query->execute() or error(db_error($query)); $board = $query->fetchAll()[0]; + + // Clean the cache + if ($config['cache']['enabled']) { + cache::delete('board_' . $board['uri']); + cache::delete('all_boards'); + + cache::delete('config_' . $board['uri']); + cache::delete('events_' . $board['uri']); + unlink('tmp/cache/locale_' . $board['uri']); + } $css = @file_get_contents('stylesheets/board/' . $board['uri'] . '.css'); From ef8ef2c0b43f73c09a1601739ae02c74628aa680 Mon Sep 17 00:00:00 2001 From: czaks Date: Mon, 6 Apr 2015 23:58:23 +0200 Subject: [PATCH 42/46] infinity/smart-build: rebuild config cache when flags are modified --- inc/8chan-mod-pages.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/inc/8chan-mod-pages.php b/inc/8chan-mod-pages.php index d3a207bd..9d74b8bf 100644 --- a/inc/8chan-mod-pages.php +++ b/inc/8chan-mod-pages.php @@ -319,6 +319,11 @@ \$config['user_flags'] = unserialize(file_get_contents('$b/flags.ser')); FLAGS; + if ($config['cache']['enabled']) { + cache::delete('config_' . $b); + cache::delete('events_' . $b); + } + file_write($b.'/flags.php', $flags); } From e3a8308e0033e6bbde6fd3bbc9536d789b473454 Mon Sep 17 00:00:00 2001 From: czaks Date: Tue, 7 Apr 2015 00:04:55 +0200 Subject: [PATCH 43/46] infinity/smart-build: fix order of loading --- inc/instance-config.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/inc/instance-config.php b/inc/instance-config.php index 529b266c..0826e894 100644 --- a/inc/instance-config.php +++ b/inc/instance-config.php @@ -7,8 +7,6 @@ * * You can copy values from config.php (defaults) and paste them here. */ - require_once "instance-functions.php"; - // Note - you may want to change some of these in secrets.php instead of here // See the secrets.example.php file $config['db']['server'] = 'localhost'; @@ -202,6 +200,9 @@ $config['report_captcha'] = true; // 8chan specific mod pages require '8chan-mod-config.php'; + +// Load instance functions later on +require_once 'instance-functions.php'; // Load database credentials require "secrets.php"; From ac693427f86731f0342f92f2b8191d5fd10374d1 Mon Sep 17 00:00:00 2001 From: czaks Date: Tue, 7 Apr 2015 00:20:07 +0200 Subject: [PATCH 44/46] infinity/smart-build: needless code duplication, fixed now --- inc/8chan-mod-config.php | 3 +-- inc/8chan-mod-pages.php | 46 ---------------------------------------- 2 files changed, 1 insertion(+), 48 deletions(-) diff --git a/inc/8chan-mod-config.php b/inc/8chan-mod-config.php index 7894be58..1c2ed2ac 100644 --- a/inc/8chan-mod-config.php +++ b/inc/8chan-mod-config.php @@ -44,8 +44,7 @@ $config['mod']['move'] = GLOBALVOLUNTEER; $config['mod']['shadow_capcode'] = 'Global Volunteer'; - - // Mod pages code now resides in 8chan-mod-pages-functions.php file + // Mod pages assignment $config['mod']['custom_pages']['/tags/(\%b)'] = '8_tags'; $config['mod']['custom_pages']['/reassign/(\%b)'] = '8_reassign'; $config['mod']['custom_pages']['/volunteers/(\%b)'] = '8_volunteers'; diff --git a/inc/8chan-mod-pages.php b/inc/8chan-mod-pages.php index 9d74b8bf..a8e44311 100644 --- a/inc/8chan-mod-pages.php +++ b/inc/8chan-mod-pages.php @@ -1,50 +1,4 @@ Date: Tue, 7 Apr 2015 01:44:55 +0200 Subject: [PATCH 45/46] 8chan/smart-build: includable 404.php --- inc/instance-config.php | 3 ++- inc/instance-functions.php | 4 ++++ smart_build.php | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/inc/instance-config.php b/inc/instance-config.php index 0826e894..1701fc64 100644 --- a/inc/instance-config.php +++ b/inc/instance-config.php @@ -198,6 +198,8 @@ $config['spam']['unicode'] = false; $config['twig_cache'] = false; $config['report_captcha'] = true; +$config['page_404'] = 'page_404'; + // 8chan specific mod pages require '8chan-mod-config.php'; @@ -206,4 +208,3 @@ require_once 'instance-functions.php'; // Load database credentials require "secrets.php"; - diff --git a/inc/instance-functions.php b/inc/instance-functions.php index 9bcc7c3f..71e933d1 100644 --- a/inc/instance-functions.php +++ b/inc/instance-functions.php @@ -18,3 +18,7 @@ function max_posts_per_hour($post) { return ($r['count'] > $config['hour_max_threads']); } } + +function page_404() { + include('404.php'); +} diff --git a/smart_build.php b/smart_build.php index da1966d0..ac016480 100644 --- a/smart_build.php +++ b/smart_build.php @@ -147,6 +147,9 @@ function die_404() { global $config; header("Status: 404 Not Found"); echo "

404 Not Found

Page doesn't exist


vichan
"; } + elseif (is_callable($config['page_404'])) { + $config['page_404'](); + } else { header("Location: ".$config['page_404']); } From be80df21f0bf72210ac4b87ac6e0c773f2a6fd3a Mon Sep 17 00:00:00 2001 From: czaks Date: Tue, 7 Apr 2015 01:48:03 +0200 Subject: [PATCH 46/46] 8chan/smart-build: 404.php should global $config? php is weird --- 404.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/404.php b/404.php index e74eb65b..7422be23 100644 --- a/404.php +++ b/404.php @@ -3,6 +3,8 @@ require_once "inc/functions.php"; header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found"); +global $config; + $dir = "static/404/"; if (!is_dir($dir))