From 72beacc1da12d3851330e229ff43c8ec6f2ce352 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Wed, 21 Aug 2013 20:54:46 +1000 Subject: [PATCH 01/35] allow moving threads with non-image uploads --- inc/mod/pages.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 63e7266e..7df8f316 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -1048,7 +1048,7 @@ function mod_move($originBoard, $postID) { if ($post['has_file']) { // copy image $clone($file_src, sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $post['file']); - if (!in_array($post['thumb'], array('spoiler', 'deleted'))) + if (!in_array($post['thumb'], array('spoiler', 'deleted', 'file'))) $clone($file_thumb, sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $post['thumb']); } From 54a8c721212e279f47d786168b1c38bd9c28e8ed Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Wed, 21 Aug 2013 21:34:18 +1000 Subject: [PATCH 02/35] Huge bug with deleting boards: $tmp_board unused; sometimes it would delete the wrong board's directory --- inc/mod/pages.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 7df8f316..706513a3 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -379,7 +379,7 @@ function mod_edit_board($boardName) { $query->bindValue(':uri', $board['uri'], PDO::PARAM_INT); $query->execute() or error(db_error($query)); - $query = prepare("SELECT `board`, `post` FROM ``cites`` WHERE `target_board` = :board"); + $query = prepare("SELECT `board`, `post` FROM ``cites`` WHERE `target_board` = :board ORDER BY `board`"); $query->bindValue(':board', $board['uri']); $query->execute() or error(db_error($query)); while ($cite = $query->fetch(PDO::FETCH_ASSOC)) { @@ -391,6 +391,9 @@ function mod_edit_board($boardName) { } } + if (isset($tmp_board)) + $board = $tmp_board; + $query = prepare('DELETE FROM ``cites`` WHERE `board` = :board OR `target_board` = :board'); $query->bindValue(':board', $board['uri']); $query->execute() or error(db_error($query)); From 0cb54b15dc097cc27d6ba6dd2645e856bfda6df5 Mon Sep 17 00:00:00 2001 From: K Date: Tue, 2 Jul 2013 19:52:29 +0200 Subject: [PATCH 03/35] Add support for 4chan-compatible json API. Conflicts: inc/functions.php --- inc/api.php | 123 ++++++++++++++++++++++++++++++++++++++++++++++ inc/functions.php | 39 +++++++++++++-- 2 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 inc/api.php diff --git a/inc/api.php b/inc/api.php new file mode 100644 index 00000000..5592d8af --- /dev/null +++ b/inc/api.php @@ -0,0 +1,123 @@ + 'no', + 'thread' => 'resto', + 'subject' => 'sub', + 'email' => 'email', + 'name' => 'name', + 'trip' => 'trip', + 'capcode' => 'capcode', + 'body' => 'com', + 'time' => 'time', + 'thumb' => 'thumb', // non-compatible field + 'thumbx' => 'tn_w', + 'thumby' => 'tn_h', + 'file' => 'file', // non-compatible field + 'filex' => 'w', + 'filey' => 'h', + 'filesize' => 'fsize', + //'filename' => 'filename', + 'omitted' => 'omitted_posts', + 'omitted_images' => 'omitted_images', + //'posts' => 'replies', + //'ip' => '', + 'sticky' => 'sticky', + 'locked' => 'locked', + //'bumplocked' => '', + //'embed' => '', + //'root' => '', + //'mod' => '', + //'hr' => '', + ); + + static $ints = array( + 'no' => 1, + 'resto' => 1, + 'time' => 1, + 'tn_w' => 1, + 'tn_h' => 1, + 'w' => 1, + 'h' => 1, + 'fsize' => 1, + 'omitted_posts' => 1, + 'omitted_images' => 1, + 'sticky' => 1, + 'locked' => 1, + ); + + private function translatePost($post) { + $apiPost = array(); + foreach (self::$postFields as $local => $translated) { + if (!isset($post->$local)) + continue; + + $toInt = isset(self::$ints[$translated]); + $val = $post->$local; + if ($val !== null && $val !== '') { + $apiPost[$translated] = $toInt ? (int) $val : $val; + } + } + + if (isset($post->filename)) { + $dotPos = strrpos($post->filename, '.'); + $apiPost['filename'] = substr($post->filename, 0, $dotPos); + $apiPost['ext'] = substr($post->filename, $dotPos); + } + + return $apiPost; + } + + function translateThread(Thread $thread) { + $apiPosts = array(); + $op = $this->translatePost($thread); + $op['resto'] = 0; + $apiPosts['posts'][] = $op; + + foreach ($thread->posts as $p) { + $apiPosts['posts'][] = $this->translatePost($p); + } + + return $apiPosts; + } + + function translatePage(array $threads) { + $apiPage = array(); + foreach ($threads as $thread) { + $apiPage['threads'][] = $this->translateThread($thread); + } + return $apiPage; + } + + function translateCatalogPage(array $threads) { + $apiPage = array(); + foreach ($threads as $thread) { + $ts = $this->translateThread($thread); + $apiPage['threads'][] = current($ts['posts']); + } + return $apiPage; + } + + function translateCatalog($catalog) { + $apiCatalog = array(); + foreach ($catalog as $page => $threads) { + $apiPage = $this->translateCatalogPage($threads); + $apiPage['page'] = $page; + $apiCatalog[] = $apiPage; + } + + return $apiCatalog; + } +} diff --git a/inc/functions.php b/inc/functions.php index d5348f0d..cedf5534 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -13,6 +13,7 @@ require_once 'inc/display.php'; require_once 'inc/template.php'; require_once 'inc/database.php'; require_once 'inc/events.php'; +require_once 'inc/api.php'; require_once 'inc/lib/gettext/gettext.inc'; // the user is not currently logged in as a moderator @@ -1047,6 +1048,9 @@ function index($page, $mod=false) { if ($query->rowCount() < 1 && $page > 1) return false; + + $threads = array(); + while ($th = $query->fetch(PDO::FETCH_ASSOC)) { $thread = new Thread($th, $mod ? '?/' : $config['root'], $mod); @@ -1087,7 +1091,8 @@ function index($page, $mod=false) { $thread->omitted = $omitted['post_count'] - ($th['sticky'] ? $config['threads_preview_sticky'] : $config['threads_preview']); $thread->omitted_images = $omitted['image_count'] - $num_images; } - + + $threads[] = $thread; $body .= $thread->build(true); } @@ -1096,7 +1101,8 @@ function index($page, $mod=false) { 'body' => $body, 'post_url' => $config['post_url'], 'config' => $config, - 'boardlist' => createBoardlist($mod) + 'boardlist' => createBoardlist($mod), + 'threads' => $threads ); } @@ -1289,10 +1295,14 @@ function buildIndex() { if (!$config['try_smarter']) $antibot = create_antibot($board['uri']); + $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)); - if ($config['try_smarter'] && isset($build_pages) && count($build_pages) && !in_array($page, $build_pages) && is_file($filename)) + if ($config['try_smarter'] && isset($build_pages) && count($build_pages) + && !in_array($page, $build_pages) && is_file($filename)) continue; $content = index($page); if (!$content) @@ -1309,13 +1319,30 @@ function buildIndex() { $content['antibot'] = $antibot; file_write($filename, Element('index.html', $content)); + + // json api + $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 ($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); + + $jsonFilename = $board['dir'] . ($page-1) . ".json"; + file_unlink($jsonFilename); } } + + // json api catalog + $json = json_encode($api->translateCatalog($catalog)); + $jsonFilename = $board['dir'] . "catalog.json"; + file_write($jsonFilename, $json); } function buildJavascript() { @@ -1735,6 +1762,12 @@ function buildThread($id, $return = false, $mod = false) { return $body; file_write($board['dir'] . $config['dir']['res'] . sprintf($config['file_page'], $id), $body); + + // json api + $api = new Api(); + $json = json_encode($api->translateThread($thread)); + $jsonFilename = $board['dir'] . $config['dir']['res'] . $id . ".json"; + file_write($jsonFilename, $json); } function rrmdir($dir) { From 01e906b1d37858168d4e8b2b7faa114fd4b9f63e Mon Sep 17 00:00:00 2001 From: ctrlcctrlv Date: Tue, 20 Aug 2013 16:53:05 +0000 Subject: [PATCH 04/35] Make it possible to disable API, disable it by default Conflicts: inc/functions.php --- inc/config.php | 9 ++++ inc/functions.php | 129 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 123 insertions(+), 15 deletions(-) diff --git a/inc/config.php b/inc/config.php index ae45a746..886d7183 100644 --- a/inc/config.php +++ b/inc/config.php @@ -1285,6 +1285,15 @@ // return 'Sorry, you cannot post that!'; // }); +/* + * ============= + * API settings + * ============= + */ + + // Whether or not to use the API, disabled by default. + $config['api']['enabled'] = false; + /* * ==================== * Other/uncategorized diff --git a/inc/functions.php b/inc/functions.php index cedf5534..98cf9d77 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -1295,9 +1295,11 @@ function buildIndex() { if (!$config['try_smarter']) $antibot = create_antibot($board['uri']); - $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)); @@ -1321,12 +1323,14 @@ function buildIndex() { file_write($filename, Element('index.html', $content)); // json api - $threads = $content['threads']; - $json = json_encode($api->translatePage($threads)); - $jsonFilename = $board['dir'] . ($page-1) . ".json"; // pages should start from 0 - file_write($jsonFilename, $json); + 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; + $catalog[$page-1] = $threads; + } } if ($page < $config['max_pages']) { @@ -1340,9 +1344,11 @@ function buildIndex() { } // json api catalog - $json = json_encode($api->translateCatalog($catalog)); - $jsonFilename = $board['dir'] . "catalog.json"; - file_write($jsonFilename, $json); + if ($config['api']['enabled']) { + $json = json_encode($api->translateCatalog($catalog)); + $jsonFilename = $board['dir'] . "catalog.json"; + file_write($jsonFilename, $json); + } } function buildJavascript() { @@ -1764,10 +1770,103 @@ function buildThread($id, $return = false, $mod = false) { file_write($board['dir'] . $config['dir']['res'] . sprintf($config['file_page'], $id), $body); // json api - $api = new Api(); - $json = json_encode($api->translateThread($thread)); - $jsonFilename = $board['dir'] . $config['dir']['res'] . $id . ".json"; - file_write($jsonFilename, $json); + 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); + } + + if ($return) { + return $body; + } else { + $noko50fn = $board['dir'] . $config['dir']['res'] . sprintf($config['file_page50'], $id); + if ($hasnoko50 || file_exists($noko50fn)) { + buildThread50($id, $return, $mod, $thread); + } + + file_write($board['dir'] . $config['dir']['res'] . sprintf($config['file_page'], $id), $body); + } +} + +function buildThread50($id, $return = false, $mod = false, $thread = null) { + global $board, $config, $build_pages; + $id = round($id); + + if (event('build-thread', $id)) + return; + + if (!$thread) { + $query = prepare(sprintf("SELECT * FROM ``posts_%s`` WHERE (`thread` IS NULL AND `id` = :id) OR `thread` = :id ORDER BY `thread`,`id` DESC LIMIT :limit", $board['uri'])); + $query->bindValue(':id', $id, PDO::PARAM_INT); + $query->bindValue(':limit', $config['noko50_count']+1, PDO::PARAM_INT); + $query->execute() or error(db_error($query)); + + $num_images = 0; + while ($post = $query->fetch(PDO::FETCH_ASSOC)) { + if (!isset($thread)) { + $thread = new Thread($post, $mod ? '?/' : $config['root'], $mod); + } else { + if ($post['file']) + $num_images++; + + $thread->add(new Post($post, $mod ? '?/' : $config['root'], $mod)); + } + } + + // Check if any posts were found + if (!isset($thread)) + error($config['error']['nonexistant']); + + + if ($query->rowCount() == $config['noko50_count']+1) { + $count = prepare(sprintf("SELECT COUNT(`id`) as `num` FROM ``posts_%s`` WHERE `thread` = :thread UNION ALL SELECT COUNT(`id`) FROM ``posts_%s`` WHERE `file` IS NOT NULL AND `thread` = :thread", $board['uri'], $board['uri'])); + $count->bindValue(':thread', $id, PDO::PARAM_INT); + $count->execute() or error(db_error($count)); + + $c = $count->fetch(); + $thread->omitted = $c['num'] - $config['noko50_count']; + + $c = $count->fetch(); + $thread->omitted_images = $c['num'] - $num_images; + } + + $thread->posts = array_reverse($thread->posts); + } else { + $allPosts = $thread->posts; + + $thread->posts = array_slice($allPosts, -$config['noko50_count']); + $thread->omitted += count($allPosts) - count($thread->posts); + foreach ($allPosts as $index => $post) { + if ($index == count($allPosts)-count($thread->posts)) + break; + if ($post->file) + $thread->omitted_images++; + } + } + + $hasnoko50 = $thread->postCount() >= $config['noko50_min']; + + $body = Element('thread.html', array( + 'board' => $board, + 'thread' => $thread, + 'body' => $thread->build(false, true), + 'config' => $config, + 'id' => $id, + 'mod' => $mod, + 'hasnoko50' => $hasnoko50, + 'isnoko50' => true, + 'antibot' => $mod ? false : create_antibot($board['uri'], $id), + 'boardlist' => createBoardlist($mod), + 'return' => ($mod ? '?' . $board['url'] . $config['file_index'] : $config['root'] . $board['dir'] . $config['file_index']) + )); + + if ($return) { + return $body; + } else { + file_write($board['dir'] . $config['dir']['res'] . sprintf($config['file_page50'], $id), $body); + } +>>>>>>> a29a932... Make it possible to disable API, disable it by default } function rrmdir($dir) { From ecda099dfb73f9e967a33ce96e45dc7ba1217bf6 Mon Sep 17 00:00:00 2001 From: ctrlcctrlv Date: Tue, 20 Aug 2013 18:17:05 +0000 Subject: [PATCH 05/35] Custom fields in API, read config.php for info. Non-4chan compatible fields removed. --- inc/api.php | 67 ++++++++++++++++++++++------------------------- inc/config.php | 6 +++++ inc/functions.php | 4 +-- 3 files changed, 39 insertions(+), 38 deletions(-) diff --git a/inc/api.php b/inc/api.php index 5592d8af..2bf6b7b8 100644 --- a/inc/api.php +++ b/inc/api.php @@ -1,5 +1,4 @@ postFields = array( + 'id' => 'no', + 'thread' => 'resto', + 'subject' => 'sub', + 'body' => 'com', + 'email' => 'email', + 'name' => 'name', + 'trip' => 'trip', + 'capcode' => 'capcode', + 'time' => 'time', + 'thumbx' => 'tn_w', + 'thumby' => 'tn_h', + 'filex' => 'w', + 'filey' => 'h', + 'filesize' => 'fsize', + 'filename' => 'filename', + 'omitted' => 'omitted_posts', + 'omitted_images' => 'omitted_images', + 'sticky' => 'sticky', + 'locked' => 'locked', + ); - /** - * Translation from local fields to fields in 4chan-style API - */ - public static $postFields = array( - 'id' => 'no', - 'thread' => 'resto', - 'subject' => 'sub', - 'email' => 'email', - 'name' => 'name', - 'trip' => 'trip', - 'capcode' => 'capcode', - 'body' => 'com', - 'time' => 'time', - 'thumb' => 'thumb', // non-compatible field - 'thumbx' => 'tn_w', - 'thumby' => 'tn_h', - 'file' => 'file', // non-compatible field - 'filex' => 'w', - 'filey' => 'h', - 'filesize' => 'fsize', - //'filename' => 'filename', - 'omitted' => 'omitted_posts', - 'omitted_images' => 'omitted_images', - //'posts' => 'replies', - //'ip' => '', - 'sticky' => 'sticky', - 'locked' => 'locked', - //'bumplocked' => '', - //'embed' => '', - //'root' => '', - //'mod' => '', - //'hr' => '', - ); + if (isset($config['api']['extra_fields']) && gettype($config['api']['extra_fields']) == 'array'){ + $this->postFields = array_merge($this->postFields, $config['api']['extra_fields']); + } + } - static $ints = array( + private static $ints = array( 'no' => 1, 'resto' => 1, 'time' => 1, @@ -60,7 +55,7 @@ class Api { private function translatePost($post) { $apiPost = array(); - foreach (self::$postFields as $local => $translated) { + foreach ($this->postFields as $local => $translated) { if (!isset($post->$local)) continue; diff --git a/inc/config.php b/inc/config.php index 886d7183..750782bc 100644 --- a/inc/config.php +++ b/inc/config.php @@ -1294,6 +1294,12 @@ // Whether or not to use the API, disabled by default. $config['api']['enabled'] = false; + // Extra fields in to be shown in the array that are not 4chan API compatible. + // You canget these by taking a look at the schema for posts_ tables. The array should be formatted as $db_name => $translated_name. + // For example: + + // $config['api']['extra_fields'] = array('body_nomarkup'=>'com_nomarkup'); + /* * ==================== * Other/uncategorized diff --git a/inc/functions.php b/inc/functions.php index 98cf9d77..aa92d0ef 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -1296,7 +1296,7 @@ function buildIndex() { $antibot = create_antibot($board['uri']); if ($config['api']['enabled']) { - $api = new Api(); + $api = new Api($config); $catalog = array(); } @@ -1771,7 +1771,7 @@ function buildThread($id, $return = false, $mod = false) { // json api if ($config['api']['enabled']) { - $api = new Api(); + $api = new Api($config); $json = json_encode($api->translateThread($thread)); $jsonFilename = $board['dir'] . $config['dir']['res'] . $id . ".json"; file_write($jsonFilename, $json); From 618d979ec419bddb5606837a9e1496369331f9bd Mon Sep 17 00:00:00 2001 From: ctrlcctrlv Date: Tue, 20 Aug 2013 18:52:12 +0000 Subject: [PATCH 06/35] Country flags in API if they are enabled --- inc/api.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/inc/api.php b/inc/api.php index 2bf6b7b8..69a58a0c 100644 --- a/inc/api.php +++ b/inc/api.php @@ -3,6 +3,7 @@ * Copyright (c) 2010-2013 Tinyboard Development Group */ + /** * Class for generating json API compatible with 4chan API */ @@ -11,6 +12,8 @@ class Api { /** * Translation from local fields to fields in 4chan-style API */ + $this->config = $config; + $this->postFields = array( 'id' => 'no', 'thread' => 'resto', @@ -64,6 +67,7 @@ class Api { if ($val !== null && $val !== '') { $apiPost[$translated] = $toInt ? (int) $val : $val; } + } if (isset($post->filename)) { @@ -72,6 +76,18 @@ class Api { $apiPost['ext'] = substr($post->filename, $dotPos); } + // Handle country field + if (isset($post->body_nomarkup) && $this->config['country_flags']) { + $modifiers = extract_modifiers($post->body_nomarkup); + if (isset($modifiers['flag']) && isset($modifiers['flag alt'])) { + $country = mb_convert_case($modifiers['flag'], MB_CASE_UPPER); + if ($country) { + $apiPost['country'] = $country; + $apiPost['country_name'] = $modifiers['flag alt']; + } + } + } + return $apiPost; } From ddd34347f8f6bf1b47e06c621419e03dca3eb00c Mon Sep 17 00:00:00 2001 From: ctrlcctrlv Date: Tue, 20 Aug 2013 21:31:46 +0000 Subject: [PATCH 07/35] Make file/thumb dimensons actually work (thanks sti) --- inc/api.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/inc/api.php b/inc/api.php index 69a58a0c..5e4cc88a 100644 --- a/inc/api.php +++ b/inc/api.php @@ -24,10 +24,10 @@ class Api { 'trip' => 'trip', 'capcode' => 'capcode', 'time' => 'time', - 'thumbx' => 'tn_w', - 'thumby' => 'tn_h', - 'filex' => 'w', - 'filey' => 'h', + 'thumbheight' => 'tn_w', + 'thumbwidth' => 'tn_h', + 'fileheight' => 'w', + 'filewidth' => 'h', 'filesize' => 'fsize', 'filename' => 'filename', 'omitted' => 'omitted_posts', From ed142a5e5d47b4e41ebb35ec51d50eba98a3d44b Mon Sep 17 00:00:00 2001 From: ctrlcctrlv Date: Tue, 20 Aug 2013 21:35:16 +0000 Subject: [PATCH 08/35] ;_; --- inc/api.php | 3 ++- inc/functions.php | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/inc/api.php b/inc/api.php index 5e4cc88a..9cad8ba0 100644 --- a/inc/api.php +++ b/inc/api.php @@ -8,7 +8,8 @@ * Class for generating json API compatible with 4chan API */ class Api { - function __construct($config){ + function __construct(){ + global $config; /** * Translation from local fields to fields in 4chan-style API */ diff --git a/inc/functions.php b/inc/functions.php index aa92d0ef..98cf9d77 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -1296,7 +1296,7 @@ function buildIndex() { $antibot = create_antibot($board['uri']); if ($config['api']['enabled']) { - $api = new Api($config); + $api = new Api(); $catalog = array(); } @@ -1771,7 +1771,7 @@ function buildThread($id, $return = false, $mod = false) { // json api if ($config['api']['enabled']) { - $api = new Api($config); + $api = new Api(); $json = json_encode($api->translateThread($thread)); $jsonFilename = $board['dir'] . $config['dir']['res'] . $id . ".json"; file_write($jsonFilename, $json); From 9d77a4cc4c3a633f163d1e11a4e2ef775dbe95cc Mon Sep 17 00:00:00 2001 From: ctrlcctrlv Date: Tue, 20 Aug 2013 22:11:32 +0000 Subject: [PATCH 09/35] Only add country to JSON if flag fits country code regex --- inc/api.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/api.php b/inc/api.php index 9cad8ba0..a8331590 100644 --- a/inc/api.php +++ b/inc/api.php @@ -80,8 +80,8 @@ class Api { // Handle country field if (isset($post->body_nomarkup) && $this->config['country_flags']) { $modifiers = extract_modifiers($post->body_nomarkup); - if (isset($modifiers['flag']) && isset($modifiers['flag alt'])) { - $country = mb_convert_case($modifiers['flag'], MB_CASE_UPPER); + if (isset($modifiers['flag']) && isset($modifiers['flag alt']) && preg_match('/^[a-z]{2}$/', $modifiers['flag'])) { + $country = strtoupper($modifiers['flag']); if ($country) { $apiPost['country'] = $country; $apiPost['country_name'] = $modifiers['flag alt']; From cbfafeea3636e222feb73cf42c24fec16bff71bd Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Wed, 21 Aug 2013 22:36:48 +1000 Subject: [PATCH 10/35] merge fail --- inc/functions.php | 1 - 1 file changed, 1 deletion(-) diff --git a/inc/functions.php b/inc/functions.php index 98cf9d77..76865882 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -1866,7 +1866,6 @@ function buildThread50($id, $return = false, $mod = false, $thread = null) { } else { file_write($board['dir'] . $config['dir']['res'] . sprintf($config['file_page50'], $id), $body); } ->>>>>>> a29a932... Make it possible to disable API, disable it by default } function rrmdir($dir) { From b01a402d23a1e7dd94ef2b9980a055c07d332f9d Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Wed, 21 Aug 2013 22:41:42 +1000 Subject: [PATCH 11/35] 4chan-compatible api: better config.php comments --- inc/config.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/inc/config.php b/inc/config.php index 750782bc..b6c510a7 100644 --- a/inc/config.php +++ b/inc/config.php @@ -1291,14 +1291,14 @@ * ============= */ - // Whether or not to use the API, disabled by default. + // Whether or not to enable the 4chan-compatible API, disabled by default. See + // https://github.com/4chan/4chan-API for API specification. $config['api']['enabled'] = false; - // Extra fields in to be shown in the array that are not 4chan API compatible. - // You canget these by taking a look at the schema for posts_ tables. The array should be formatted as $db_name => $translated_name. - // For example: - - // $config['api']['extra_fields'] = array('body_nomarkup'=>'com_nomarkup'); + // Extra fields in to be shown in the array that are not in the 4chan-API. You can get these by taking a + // look at the schema for posts_ tables. The array should be formatted as $db_column => $translated_name. + // Example: Adding the pre-markup post body to the API as "com_nomarkup". + // $config['api']['extra_fields'] = array('body_nomarkup' => 'com_nomarkup'); /* * ==================== From a83715dcb0c6f9bdb82f037b385f09870638963c Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Wed, 21 Aug 2013 22:43:35 +1000 Subject: [PATCH 12/35] single quotation marks please --- inc/functions.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 76865882..c34adbcc 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -1326,7 +1326,7 @@ function buildIndex() { if ($config['api']['enabled']) { $threads = $content['threads']; $json = json_encode($api->translatePage($threads)); - $jsonFilename = $board['dir'] . ($page-1) . ".json"; // pages should start from 0 + $jsonFilename = $board['dir'] . ($page - 1) . '.json'; // pages should start from 0 file_write($jsonFilename, $json); $catalog[$page-1] = $threads; @@ -1338,7 +1338,7 @@ function buildIndex() { $filename = $board['dir'] . ($page==1 ? $config['file_index'] : sprintf($config['file_page'], $page)); file_unlink($filename); - $jsonFilename = $board['dir'] . ($page-1) . ".json"; + $jsonFilename = $board['dir'] . ($page - 1) . '.json'; file_unlink($jsonFilename); } } @@ -1346,7 +1346,7 @@ function buildIndex() { // json api catalog if ($config['api']['enabled']) { $json = json_encode($api->translateCatalog($catalog)); - $jsonFilename = $board['dir'] . "catalog.json"; + $jsonFilename = $board['dir'] . 'catalog.json'; file_write($jsonFilename, $json); } } @@ -1773,7 +1773,7 @@ function buildThread($id, $return = false, $mod = false) { if ($config['api']['enabled']) { $api = new Api(); $json = json_encode($api->translateThread($thread)); - $jsonFilename = $board['dir'] . $config['dir']['res'] . $id . ".json"; + $jsonFilename = $board['dir'] . $config['dir']['res'] . $id . '.json'; file_write($jsonFilename, $json); } From 11fa73e4d11777ce6cc808c278d5c8ca2175b392 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Wed, 21 Aug 2013 22:44:23 +1000 Subject: [PATCH 13/35] automatically initialize $config['api'] --- inc/functions.php | 1 + 1 file changed, 1 insertion(+) diff --git a/inc/functions.php b/inc/functions.php index c34adbcc..9ea6aa9a 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -35,6 +35,7 @@ function loadConfig() { $arrays = array( 'db', + 'api', 'cache', 'cookies', 'error', From c04e8056a09a5c10d65d737dd978e32964ee230b Mon Sep 17 00:00:00 2001 From: Czterooki Date: Mon, 19 Aug 2013 10:22:41 -0400 Subject: [PATCH 14/35] fix background on dark_roach after a bad merge Conflicts: stylesheets/dark_roach.css --- stylesheets/dark_roach.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stylesheets/dark_roach.css b/stylesheets/dark_roach.css index cff616b6..a0e2be44 100644 --- a/stylesheets/dark_roach.css +++ b/stylesheets/dark_roach.css @@ -5,7 +5,7 @@ src: local('Ubuntu Light'), local('Ubuntu-Light'), url(http://themes.googleusercontent.com/static/fonts/ubuntu/v4/_aijTyevf54tkVDLy-dlnD8E0i7KZn-EPnyo3HZu7kw.woff) format('woff'); } body { - background: #000000; + background: #000112; background-image: url(data:image/png;base64,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), url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAmsAAAJrCAMAAACIkiTWAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAKmUExURQABEgABEwACEwACFAECEwECFAECFQEDFAEDFQEDFgEEFQEEFgEEFwEFFgEFFwIEFgIEFwIFFgIFFwIFGAIGFwIGGAIGGQIGGgIHGQMGGAMGGQMGGgMHGQMHGgMHGwMIGgMIGwMIHAMJHAQHGwQIGwQIHAQJGwQJHAQJHQQJHgQKHAQKHQQKHgQKHwQLHgQLHwUJHQUKHQUKHgUKHwULHgULHwULIAUMHwUMIAUMIQUNIAUNIQUNIgYLIAYMIAYMIQYNIAYNIQYNIgYNIwYOIgYOIwYOJAYPJAYPJQcNIgcOIgcOIwcOJAcPIwcPJAcPJQcQJAcQJQcQJgcRJggPJQgQJAgQJQgQJggRJggRJwgRKAgSJwgSKAgSKQgTKAgTKQkRJwkRKAkSJwkSKAkSKQkTKAkTKQkTKgkUKQkUKgkUKwkVKgkVKwoTKQoTKgoUKgoUKwoULAoVKwoVLAoVLQoWKwoWLAoWLQoXLQoXLgsVLAsVLQsWLAsWLQsWLgsXLQsXLgsXLwsYLgsYLwsYMAsZMAwXLgwXLwwXMAwYLwwYMAwZLwwZMAwZMQwZMgwaMQwaMgwaMwwbMw0ZMQ0ZMg0aMQ0aMg0aMw0bMg0bMw0bNA0cMw0cNA0cNQ0dNQ0dNg4bMw4bNA4bNQ4cMw4cNA4cNQ4cNg4dNA4dNQ4dNg4dNw4eNg4eNw4fOA8dNQ8dNg8dNw8eNg8eNw8eOA8fNw8fOA8fOQ8gOA8gOQ8gOg8hOg8hOxAfOBAfORAgOBAgORAgOhAgOxAhOhAhOxAiOxAiPBAiPRAjPREhOxEhPBEiOxEiPBEiPREjPBEjPREjPhEkPREkPhIjPRIjPhIkPRIkPhIkPxIlPxIlQBMmQBMmQRMmQhMnQhMnQxQoQxQoRBQpRBQpRSmsPHMAADDASURBVHja7Z39f1vXfd8BmBhuCK9wLga2QnFNyAN3sYFNZoyXhuYULFByWQzr0pQ9EijZxDUo0NQSEyU6snENGSyTumKISalTymC0rNrEjZyVeqFNTd7iiBXtNptUuc7qh9lxHv6T3QeAvMQzLu4TyM/7ZSf+QQSpe98853y/53u+x2QCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI40ZjOeAdCG++6z4CEATbDcd999eAoAsgHIBoC88IBbs2HRBrSSzQbZgDayEQRkAxqt2SAbgGwAsgEA2QBkAwCyAcgGIBsAbWOFbACygaOGGbIBTWVDXTjQRLZ/8JnPEJANaBOMcrJhGgUayWaHbEAb2ex2O6ZRoJ1seAxAC+6DbACygSMHAdmAlrIhGAWaxAecbMh8AMgGjlh80A3ZgJbBKGQDWmC7//77sYEANOEzvGx4DECL+OB+yAa0WrLxsqE9G9BMNsQHQAPMBD+LQjagxZKNuJ8kuyEb0EI2kgPxAdACGy+bDc8BaADBufbZLjwHoAHdnGwO7B8ALZZsdtLpxJINaIHV6XQiPgDaLNk42R6w4jkA9TE/wMuGLBvQYsnGy2bHcwBaLNm48ADxAcCSDRytWdSOJRvQahblXHN24zkATWZRl8uFjVGgAeYHONdcWLIBDbCRnGv/CEs2oAHdLsyiQKNY9B9yrpGYRYFWsyiJWRRoM4v29GD7AGgxi5I9PT1OLNmABhCcaz2/glkUqI/ZzsuGig+gxSzq5GXDLAq0mkXRdwFowf2YRYGWs6gTGV2gAUJ4QOI5AK3CA2R0gVbhgRPhAVAf868gPAAaYeVmUYpCkg1oAElxONFPBqiPzc3LhvAAaICDd82NJBtQH4swsCE8ABpg9/ADG8IDoMHAJoQH2D0AGkAIKzaEB0CDgc0p5D2wewA0GtgodPgAGgxsLgxsQKuBzYO8B9AwFPVSGNiABgOblwcDG1Afcw/v2q9hYAPqY8fABjQLRTGwAY3oxsAGtBrYKN41CrVFACs2cHSwUlixAY0gOdVOnsTABrQZ2E6e/FUMbEB9nLxrGNiABhC8aid78CCA+vTwrj2IkwdAfe4XBjYnHgRQPzr4VWFgQz4XaBAd8K7ROFIFNIgOHqQ5PEh7ANUx9/Cu+ZD2AOpD8q7RJ/AggPrRgU8Y2JD2AOpzQhjYEB0ADaIDwTWcqALqYxEmUT+aewD1cdJ+DhceBFB/EuVV8z/YhScB1MZMCbIhxQY0mEQF11BZBDSYRPt413CgCmgQiQqTaJ8DTwJoNIkiEgVaTaKoYgMaTKJeYWD7LJ4EUJ0eXrWABw8CqI5dcK0Xe6JAdax9AQ4afcGB+gs2inetH+epgPqQvGr9KCwC6tP9z3jX/KjOBepPoif7eR7AkwCqQwmuYf8dqI8zwLvmxYINqA7h511DJTjQYMH2kDCJ4jgVwIINHB1cgmvIsAH1sQuuPYQMG1AdmxAc9KM4F6gfHFD9DAeKc4H69AiuUXgQQHVIwTUawQFQPzgI8q4FERwA9YODgOAaggOgfnBAMwgOgDZ4GQQHQBvcgms+PAigfiA6wLvmR28soFEgGkBZEVA/EBWTHujDBtQPRP2caoODKGED6rvmHeTBIVGgPpTg2q/jQQDV6RFce9CMJwHUhuRMC4Ww+w7Ux8GZFgr50fIPqA7Rz7tGo9IDqI7tc7xrSOYC9bEK4xqDZC5QHYufdy2EZC5Q3zWaV+0RVLAB9XlIGNdw9h2oj1dwDdWSQH0owTUvHgRQHc8jQoINDwKojosREmzYpAKqQzKhU6dO/QaSuUB17Ayn2qlBuAZUxya4dgobB0B1LL8huIbKXKC+a/5TQxxuPAmgNmY/r9oQkrlAfddOCq71oQocqA4luBZEZS5QHfdQhCOMpAdQHafgWgR31gLVIUXXUC0JVMce5lWLIukBVIcIRnm8eBJAbawBwTU/Kj2A2lhowTUGSQ+gOn2Ca6j0AOrjFVwLofE8UB234NoQKj2A6jiHBNew+w5UxxESBrY+PAmgNoToWgBPAqiNhRGTHrjkAKiOXwxEkfQAquPjTBsZGcHuO1AdakQA/WOA6rhE17x4EkBtHMOCa/04cgDUhnhUcC2ESg+gNlZGcO0LCESB2lgCgmtRNDIFqnMSwQHQCCoquObHkwBqQ54SXGPwJIDqgajo2incFARUDw5CvGqxL2GXCqhO/0iMB+WSQHVoQbUYuoED9QNR0bUgStgUWpTgEdQORHnTWDaKnQNlsFsRZtXCFmEFcJZKocDeRaLvU60xnxFd68WjUOZ5ki4XOqvXICC6hvMtSk0UpKsHslWnV3QthKYeSsnm7IFs1XGOCK6NoNGCcks2yFYjcAqLA5sHjwKyqUxXMThQOJvbBdlAreCAUTILae/vt0E2UBEcnBYXbApmc4no1JkRyAa3yiFjgmujCtaBE2wymXwM0yjkqhiERNcU7FZkCXOuJY93+yPIVk2MEDvK06/ggo3iXRs93mkUogeyVeAXVBuLKLhgczzDuTZ9zAuVCBdFPQC9Dg9Comusggs2S3Sa4/Fj/mtNeCgKKfLDwcHjgmtjSg5D/t+bnk6ljvsmq90N2cp+/QbHBB5RsKuHK8G7xhKQjcJRDgnmgOjasJIZtlHetaTPBNkgm5Re0TUlF2ymR1I8Q12QDbIdmvCeEGVTcsHmFVyL43yWIBtOIZToHhZdU7JHM5kQZIvi6do9kE2SoQjxpp0580UFB3vbacG1BLKZ4siGEy9FaNG1MwrOeJaI4NoMutJw84aLopyQrbhgYwXVzijZQsbPicYRRXtUcQfBhVOR4rMYOSMQU/DQgScpuJZALlOQzev9NSwneMwMJ9rExISSWQ/H+IyAF4+Xl63H66VIjPF8hkJ0bULBrIclyps2OxtBCFYa2XpdkM1kIkcF1SbCCmY9BnnTZmcTOAYuYHV6vV4XTkaarFFBtTir4OqqT3TtK0jnFp8xyclGERjmg7xpHApuYLpmBVLoj1paVQiy2Y+9bFTRNQVv1bBPibKx+E0+JNuxT+vaR+MCp5WbRG0jomtxZD0Oy9ZzzGWzhHnTEomEcpOoJVScRLFgk/xOnzx50nvcjyF4BdMSiUHlPrJfdG0WLZAOyfYgp9v9x3pdQY6Lril4JJlOzc6e48DNalIIJy/bsd4e7RoWVEsoWHHmmjwn8EXsBEq5T5Ct5zg/lIDoWkK5vh7dcdG130Vd6mHZ7n/wJE1Tx3jR5poQXWMVM8MyKrqWfhB+HX4wDi9N073OY7u2sEWLA5tyrRGinGc8QehVJhvRy8lGU7bjaluw6NqQYg8gNCOolv4S7Kr4zab4oY3qPqayeRJKT6KBomtPIRCtwEz2CvPo8YxHiVhRNsXyYdScoNr8Myj1qIKD8vv9fQ8ezxChv+haRKnCIjKZnudJ4Yrvath/3c/zwHGsMyKFSHRqakwpNYinBNXmn8XNatUXbS6al+04nkSwjEyJKFUFZIuJrs2jrKjGE3/Ay8v24DG8oIkpujam0BqiKyKYlslEoFWtoZ8S5tGeY5f96EkUZVMoOrCEOM94EghEa8omzqPH7mS8tTSJnlHoLx4oupbBQdzaD93ex8t28rgNbbSoWjKpUIrNW3QtjaRHvWUt1RcIBGjv8SrYdSSKrim0q+SeEV2bx+573XWty8vJ1u+njlNAaokUXXtKmeiAnCrOoSjNrf/c7VSgv78/4HUdo6GNSkwlBZRp+UIkiq6hNLeBbFZPvwBlPzbHlYkYL9r09LQynT0sY0XXkGBr+Kh6KEE27/Ep/ni46NrvKXPF4+NF14bQXKDx73mPOLSdPC4xAjkhujb9m4r8dkWKrg0jwdZE9oM86edle+iYnFe2DE2LxBXZFGWKruE8cpND20O8bP5+57E4U0tN883iOUJKTHv+c6JrY10QqcmhjZeNYWiP9eg/Muto0bW4Ejkxik+wLSwsTOEoVZPzCkFxpvH43Ee/1shfdE2Rni/u1ILArBMaNYvLK8rW7yWP+m+oY0xUTZEW3vaSax441PyqzeUf4GUbCHodR3udax4oupZSIJ9LfFl07Rxca+n33RsUxzY/dbRtcyWLrilQxmaf5ERbXFz8ai8Eaul39IFAybYHiSOcbuuKlAa2/rY/q3tyUSDjgz+tzS42qo9XbXBw8J8e5bHNNZkS7yY403YpEHlWdO0cNt9bj0i9omuD/4KhnKYjqpvYwZtv4j3Q7kf1nhNdS8M1Ge/B8+CgQCjU7z2qB0mp3ym6Ntlu+jqU4UTLZrNzJ6COnGWbs58JifTT5JE8SWotDmyzs4H2hu4uVjAtm30Od5bInEgpf1E25pGHPEexibh3eqbY67a9zQNnouhaCn395f7e27yBUAm6lzhy6V3rcLH9aJsrNmoqKxLHfmgbAZbrc58vDW6f8zqP2tkNquTambbKPfyZomunUefRDvae0rrt1OApv7f7SMUJVrYk23Abfy8zU1Qt+xhca+99EBQtqCYQ9DkdRygJ4v2yqNq5cyfbeEKxkmu4AK39KMHjZ4quDf3mkJ8ij0ytuHlYNK2tzsoEW3JtCK4pMZP6Py+oJhAcOOGyHY3Semqy6NqM/LwHkRBEy+Vyj+C8gRJjm62HZkTTIhzhMO21H4W41MqIqp07NyU77+GYE03L5XDruzJ0mZ3/eIgRTOOIRocG/W6nteMTSs4ZoYc3x5Dcj3CnS64NQBOlsD3gDjIl1zjboky/10FaOnqRYhksdvFOy75Qqm9eNC2Xi8IRBacc6wnfQFgwTeTUENNH2R0WU8euVLpHi66lx2XmPQILRdXSKClSWDcHFWKiUkIM4/WQBNGhuRBvUbX5+bC8DxhYLLo2iYa5ittmc/b1R09JbBseeXSQob0uwm7qPOEssXS62ITULesDQqUpNIUScBVej8nh8gUH910b4RgeGRkM9Xu9JNlt6qyIwZmcLzYhfVzWLBouuXbWDTXUCRQIt3cwtK+ayPDIF0KP+rkRzumwEVZTV0cMcxam5No5v5wfN1ZyLYk5VL3JlCT9/uHh4ZFyHo0OBZl/7qM9TjdJkFYTYTa0c92TomqZjKxYdCxXcg29/tQcEiyEh+pnHh2R+hYT+RLLRkcioaGQnwlSNOXiwgdHt4mwGFA7erbYkCOTssM1I/tmc1BeJnTgW9E1tshpluX+OxKLMZFQsL+f8j5IOklLd5eBnLNG0yXZZGxVwTVNQwWL3e719j8aquaahNOjo4+zo1zcyoRCXi6M6LHbjDHOkfGSawmvWaZrS0vTcE0bzCa73en1/iYzFPtSVdE4RouMjY2xYyynJRPp99KU02mz6ry56svsy0bCtc4Y4KwOguqlmWCYjQhTZ6VoY4f512fO/PboFz4/SFOU3UGY9CqitsT2ZRttNfFxGq7pB2HrdpJu2hsMBSPRcDQ2HGNjVU07IzLBwT4Viz4WHOglXYRJj9pfMrEv279s1bUs5xlPCq7pQ5fJ3N1FuBykx+2lfcFTzFA0OjrKVnFtQiTOw1nJhHw9bpvm5Zh9yZJr897WvvLx84Jpy8twTfd5lR+nuCDARVJ+up9hRr44zC/XDg9rJdfi8UQiMToWHWIoitR0EWcOFlVbWIi3ttc0XHJtAXtUxkktcPFql72HpAa8A6FT/+qLXzwQTWKayJkzo5FQP0Xxgao2NSTdCaE3JM9YSwNUpOTaMlwz4vRqt5E9Pf4gE43GJp4aL/Nsn7FEbGjIyw1wmqzg3FNF1RYXoq2MqKHzvGccf4x2HobNklhNDgfp8wWZcOwJtppr/PXFiamxaIj2OtXf0LfQxfaQi4sZhjTxNaBNKTeYWy6C+jVjC8dNXlaH2x0IRUbYsUS8wjWexJNT0VNeF6lySsTGPCv27ONki9IP9w5Eg70U0dDx4L5ruCOoI4SzmVyk298/NDpSqVrxGuMzp4IU6VBz9UZExN5W2Wz2+bnUXDb73FyC9TfqYkTvuxbCq+yYkNViJh0U0x+LjiYmDnkm3IrHwU6E/W6Let2AnF8tqiYhNd9gGebdd+0xnNnrsBwJQZI0PTTGVpgm3CA1HYuF3Ko1IByodC2Xy9SPLz1fF01bWcG9LR0JafcGmAPf9l3jmUxEGY9dlVjBNVtmmlAHOV83RiAXS64l4VqnDnB2kmKYqbEni6btuyZ0747RXkL544LBdLlnAnS9b2RPFVVbyeAS7s6ly0R4PEx4fOLpkmfTpe7dZ5PxSNCt7K6QnX6mmmm5XN3ij+4nec94zuPelk6PGGyUZyg6wS3V9kUTmUkkogFKscQb4YolX6juWq7ezfDEeFG1lRySuUcAgujzR59KpM4ecm1mZnqGHfE6FEm7nXj4qXQN03K5eqv+rtGSay8xyv2C4Z3rOb494AyG2Mmzh1zjWys/yXKDW9u6UexcDc+EY8b1JtFYybWVcWWSHm4/Q3s9pk48bXt0ogWr2xuNJQ88mxGaK6dmn3yUbuuOGYvzsWdqjmk8s/W2n5iSaquzCmzcWqjxfDZ7PpeaivmDTsJpMqELr06+EU5/aCqx71mJJ+MBr+wPtfWOZ7N1XVuot2DrK6m2utD+BQcEM7u+fln45xv5pVRyIjrs9XgtdhvGOD1GN4fTHz0zM33ItdnZr8z0U/KWOZZAfL6eaDz1tp9OZEuuXWi7K5YlfP7b6wdcKVy6sppbnE0mI0zY4/NYHQ6TVTzi3fR0bRan4m4CssoaiKxe/+OJw66dO3dudNQtY//qBJOs7xlfnVav3ZUjLZrGMdbuqDaUXZeqJnJ1Y+Py5ctLSyvT6WQsFg0yQY/LRzo8li5uGdltMlktfHEgP9eabcJ/WQihRoVwdFldFOkIPkw9zLKxCYgjEyc18sTssweiCT37EkN9LabcLL2pdP0xTaiEHKkzKFhnVkXy+Ux71w3ZQkvrVVzbOOByYWMlf/V8dn7h7NzZ8dR4dCIWHI4FmJEAzQTpQCjIiUgzDBMai4TYp9nxubnn0tmXc6uvbVzeeh3SyJ5ubJ7+4dSXS56JbdRS6eFAVysZNyqezTbhGltvgX62pFo+31bWwxI6X9+0Awqbm9e+u7m5trWVX9+6lN+6tHJ5ZWktv5LbWs2t/tnSpevfu/gft69d3d7e2N7efo3793UOONPO0o10h9jflajGt7c6e5pueia10c/Vnz+LR6SWWGvlKsjKfRuHxdTlkLiWaCOz3BU435RnGxubIlvlbAj/e11k+xBwrX0Isn/k3Fekrs3PT56mmpzL+pINRzThJMHy4TnU4nA5fMHoUJhlg48FIwsl0/L5JfnRsJn+g8tlrrUkWm3PBNHgmhKDm9MbffpANJ70bLSviZjUSteNCiSqLQ9LYwFfMvZU8vmXsn+0spr71up8tuiZwIjsvwiZqpg/W3Wt5ojGsbOzA1kUwOF+fKIkmnjyLj0TcTeUjf5aw/iz5Fop52G105HpzEp2P6W2ejCmCczL3RM1R//94fnzSt35E67pNrhZfEx87kA1nrEgWX/x5HmmsWjFI1LLQZPYAWwomV05YLXStfyUvBWbhTrfeKG22cz8WWMChWuKYTZ5gs+mpK5lpqd89RIgVKqZhVrp2IrFYnUGRzMvVYiWL0feCRfX1OWGrtURraFnO3BN0QwvGRnLHGJ6pPaMZhvPtuBanKbCySZE40jLamXKfLtaALreomvVTNvZB4ooGpQOjh6SbXZmoMabtwW/1uz8KZCaXVlZacq1FTnXDT2QrhzTXs48k05OX179zsZVefEnXFM3KHWHMqkD1xYWFthA1a1DOvlirskxrVjh3aRq+fw3x1qWrYutCD2v5ljKTZrdfno8nlp+8drmK/ISHXBNxbHNG5qSurbw5SfIylIfdzzbtGjVPash2iWOb7R8zzK9VOFadpAUA2mL1eLxeE//VnxuJr+2srm1XsrZSkVr6BlcU4Vu72hy3zSehJ+oGEeel+taHdMuFVlbafFaDfNUeaLjasZzKJ7tMplImyvYF2WH05nZlW8sb/2HS1uCdvXTHEU2uX/WkctVAaslwGb+Xak5B0cqcnigsdBZ2fNnY9fW1tZWM+FW5tHe5fLgM1sjqLGZrC6CoikmFhpNjiXnZ3OFr1/6Xv7ia1fXfvBa4bXtq9e2N69e/e//aev1jcvbr64VXi0sF/KZufn5MXZ0DGaogitw9t/uq7a4mE71Ss/TuSebCwhqiVZn/hRdW3t5bTzY/CpzqlCW5siHG2TprKYuu6nbRVK0mxqiH45Ghk6zp+NxNh4fH0+wE4kkG2PHmPAw4wuEXS6PmzDZcaBQNXojzx64ls2+IBlorLGswrPnYdFEMgzRZFq3t1A2qq1PNNuUyyxMrza+cayJcJispNXscJgcpMXcLdawtVRWCWRCemb2TeOJ7l8X5ZuXs1Krp1qFaDznZ3xNKWNPlCdvMzho2mk4RielLROeKl7a6Ii3M3827drly5czTDMTF50vL04bwrvrOGzew4dWTvMtn7uYRYUTatWHNc61y+ejjbfiuyavlbn2HC5R68TcriN26IAU67I53Mlqg5rshdqlqqIJpnEsLwcbLdp83yzf9wxigdWRmIdYaY1afDgykVXQtZojGkdB4FK4/qLNyparhrYgnZv+iLwg2fnMLMidP2W6Vrg0XDfV5jlfptrlYRyu69wQ4eEX0ursfNafP4uuFdYm6i3aIoUy11ZP4JV18KqNis3nlE5z5Ot6ti+aIFvKW3sKzZRHBmdteGOdjD0y1+RGQbtpjnLPONbXL8/21frJBgrl9ZAMXldn0x1KqjN/NnaNr4C8nBmtkZVJltdCrqKJW6dDUJOthJ7N73w2mD9F19Yvr1ff+fZdKC+7ncQU2vmLNnJC0TRHg4BAKppItTVbV7yixDuMV3UEZLNMKlw51MzsecBsldQH+VL5Eal1XKB2JDgx30xAIGv+bOzalcITlXmz0Fr58eKMFe/pSAQI8SVVN6QK9Ya1K1cKFU3crKnyo+wbLF7T0YgPEipUDl1uTjSeP/snfYRdOpP68uVHjNdxf9oRybLNKF85tNZ4oXZwkqCQSc6NRyMnSpV00YqjeJexaXA0cGSUrxxqyTX+oOfaRj6bHXzMTHA/z1zFmc+sA6/pSEDOLLW5UFuTMX9WaZtwdaMwnSCpwXyFawk0/T4ic+j0ktppjsaelVjLzZ3/rxVH2aN4S0cD65h6lUNV0xz1ew5dqeyZsO7HWzoihNo5itdUoqNp16p351hGaHBU6F2oo5qMhVpT82crrmUQGhwVbMOtutZ0mqP5hVq9nn2z2Hg/OpHorAKVQ62lOVrpd3slhVd0dPAnFUqoyXatXhu1V2fxho4Q1PhLOZU3pJqaP6s3UlvEsZajhOvhRFrq2oW6rrVcOXRFtmh8K7VvEnhBRwonxbK55+cufGv+hdW5Z84u5TVKqTXRhPSScifeLUQXfy8aBkp9sZiIgCsQDvgZP+WmfCuKVg61dQnGa0pV5ZLBSIR9hg0OWBDZ6o/YSor/vR9TeEOqnUswVhWZRGk2vba2eet/bO5cTEWQsjMO1oRM15RIqFVcTuBve9Kz+Ufz29tv3Ra4tVdY8mFoMwq+nMyNAkXSHOVNvLN0e7J1keMrGzd2d3dF127v7e1tRND3yBjYhtWqHGouzVHeWznra+vEAZ28dmNfNN40jq0Uqn2NkQOZbT3N0VJCrdVLMF6IOWUPbWQkf2v3kGcCrxdY1MUZAGa5zZRaWwm1apcTrK0F5HXFsrjjW7tVVNvb292bRAWJ/vmPxKrmlUMNbyu7nByQ83fxpDZ2q7vGkQnhZesMlWsvoSancqjxXZ+bWxFv66rNbdcSjedSHG9bV8zDbaQ52kioNbxXdicz3GLO35fZqS2asGrD69YVe0qllFp7t8ryrL3os7eiWnr7dn3X9vC6dcWdUadySOZdn4dvK1t9oumLhixU5sZtuGboKZTJq1M51Pa9sjw31mf8TQ5tJ+bfqO8ZXNM9CmVb6dinSEKt7m2fZa7d2Ppvo021/nM/+0Yj0d555x28bx1NM9nPtVOlpkTmtvZtn5xqHDuzA43vkyJTbzQa1N6Ba3pnPJbbSXQomuao4pnISsrRYNeKnGyoGlzTHWZFl8qhhq7dkHJzga5faTS8Wd8zQTS4pjPsij6VQ625dnOZrRciMIXbTYh2584dvG89F2ypP1c8pda2Z4dF41W7efO7s701E7vklbrT54FqcK1WNkKLb2Kbl9WzT9GUWl3Rbha5tX2NqVFf2312Z28PrrVBt4Mk1feNeF63yqGaaY4qqt3i2Rz3Vn1Q8ZvNiQbXak1u4adSMT9lUfkImyOnTEpNfkKteddu/SAbrPagNpsUDa7VGnC+ls1lM3GW8XlMKvpGXlD9KHvLCbUapt26tbtzLVb5N1iuP6j9rzffubMH1+phTQiXML44/+LE6ZDbY7Fa1XNN5aPsstIcFZ4JvLWbKNsgtc3eqKfa9y9xDzG7tvEWXKuDb3b/kpV0On46HOgl+QlV4ZO1jqzelUM10xxS1XYPSB86OWCJ1Js+v58ZYXwnCIqmk7mdXbhWE3r84PqoF3Pz87/PskN9fWR366cdzXXjUL0rh5oZ1iSu7V6ISH7f6JU6C7U35yhCPGVgsbgj6Y1bcK0WbiY7f/i2st9fmolPRhnGRXFjnKXRGGezmWwkaSMpl8PnJT3e7m4PYSUt/AHkg698Tv2j7PISajVM4yiMHfz46Tdru/bm7KHbhjzUCwVIVXPNRg0nK+9g/MPc/GyGZSMBmiZdDj5wqDghxClGupkwE0lMjE5kUpOZxZlzi6knE1Ps0+NhdsgXoEmPQ+h1YZ1WqWdfK641O6KVjhJsxoubCJaR7dqq/c9cee2bA7eq1cHuicVz2aq3ymb+MJNKJdjxKB30khRh5qZWKy8PYaH6WXYslcktLuXzfFuY1VLdbf6ba2sra2u5lWx6bm58PEYHfcHzulQOyXStVAr5et4jDG2+G3XyHNcCFeM+jr/XTbOZPEwifb7WZStLKyvZ83+USj/HsqeDkYCbZuiRqcVsbql+z6E/X1vLr60t5ZfSOVXSHDIqh1pybW9vjea8IVK7dVJq87BHxrrNy/7O0vm61/pcWP3Wi6sX0kuZzKKSt5UpmVKTlVDbraHa3l6B8bgiG7WHtR+v4TiorMHNSg2xi1/V5xKMK7I3pGSm1Bp7JnAlN1PYrS4aP6pdC8IbuVOpy8MMxxcXDHIJRuuJjtfbc63KoZWdmp5x7IzjFtI2sJkob2gynjufVee2T7WPsreV6Gh0QKp87zNHQpg26e5yB5loPHn++fMrLyl5W5kWR9llJ9RuNzatzLXXaLiiyPhG2Lx9g1H26eeeW+Eignqtlfl/V9fWVl9ee/kC93+qXoKhVkqtGc/KCzp2x6GJchAmwuP0hwfY8fG5+cw3ls6v5Ffy+WLf26Vcfml+cTmTynw9Hk8mT088zf2xiXjy+UTmT9NL3/nTlcKa5pVDMjekSq61INqdO3s5DwxRmC6TxdHlolwDIWY4OjKbSmTSyVRmih1jI/0DDO2iaIfNTdoIJ9HldNhIN+Gg3T6GYWOJhdwFVSqHritROdTqsFbhGmJQNaNUG2GyuywuL+nyeqyEy2oy8ylyc1U/TQ6zw0fFxpIXlxWvHLquSOVQW/PnnTu3EINqpF3zUS0VTWfWv63UJRiKp9RaCAgkonH8CWJQAy76bP6RmbyelUM3FExzlPgLBm/WmOOgJ7KUN2TlkJyFmpDFZbHDbtjBzc7+gSZH2VutHGp9ocbz4xwutDIw1uBUu0fxFK0c2m01cytlCzOosSdSMq565VDrG1KyVHtrHMOasTF3jSmUUlO4cqhl13K4e8r4JJU5yt5qSq2NhFoV0+7sxvAmjY89rcDOZ+tH2WWmOap5xnF3C5tTnYAvr2Xl0C1FU2pF0+7e3cBqrSMCBPZyp1UOVbq2Dtc6I9GW6rDKoQrTuHENoUFn0Lem11H2dmdPUbS7d++9ivVah+R0xwvqVQ61PX/Wca1k2r17N3G9WYdAzqx3TuVQVdfensNb7BCojJqVQzcVrRyqYhrHNoW32CGENTjKLrOco55nRdF4/tiCt9gZOFJqVQ61vSHVeEwTSeEtdgieeWUvwVAqodbM/FnkLEa2DoE+r+wlGG2l1Op6VlW0e/fefTfVjdfYGTDKHcVT9ih7NdEqPBOYR5atQ7JsKVUqh1TYkKpw7d0SObcZL7ITsOeUT6kpvSFVff5890C2IGTrjJRuTunKoVuKLtTuNhCN51IIsnVGMLrUam8rdY6yyxnRBN57770rDGTrBCzUUvubBDdV35Cq5dp7AoUIch8dI5sKKTWFKodqBqBS1967Ng7ZOkI277KBK4cazZ8877///k4csnWEbO7Fi2odZb+tRppD6hkvGs9uArJ1hGy2aKqNuz5rDWntz59NuFZU7YMPIFun4J5rxzU1K4caz5+ia5CtYwheU/4SjDbqbuuL9l6FaB9Ats7Bt2aEo+zNpzlKnpVEg2ydA7WmXOXQntLzZ/3JUyIbotFOgMzrXzl0V95CTSpbErIZH1vOGJVDd1vwrEw0cRrFdpXxSRvgKHuFa82PaCVuxyCb4ZnQ9yh7M8NaY9c+/PDD7TBkM3zS47/oXjnU1Dqt7rDGufbhtUcgm+GDA20vwWhnoVZ9+hRM49jA9dsGx5LS4Cj7O23Nno1GNJ6PODZwG5rBCW9reQmGnI2CJlz7SGANB16MjeOihpdgyN/5fP+Dxq59tOzG+zQ0CZ0qh+62vPNZZ/4syYacrqHp1ewSDCVTapWeCaTxPo2MT6XKoTtKVg4169pHLF6oYfFGCq2cXFGjckhGQq22a7t4pYbESoaTF68bqnLo3dYDgsN8gNdqxMyab+zCRvNpDm0qh1pNc1SCF2s40bqZ5MZ1ZVNq76hcOQTXOnLydJ5+8ZrWl2C0XzlUI9EB1wwMHS9sd2LlUEPP4JrBxrRw6mLHVg41NA2uGQdneHVd+XtltascgmudgoudK8jt2ad2mkNuQg2uGZAuTzi1tanfJRjNzZ5teQbXjIHDN3dxQ6dLMJo6YqyEax9//DHetO58ls1tKX8JxjstXYKhUOUQXDN2SMAsb8mtHNIqzSE3oQbXjIQtmL6+c5Qqh2p59jFc03ulxuRfVTzNoW/lUE3T4JqukIn8tryj7EqmOZStHIJrhtwmGFiQeRRP/cqh9xTyTKoaXNNvqcYsN0xz7Ly+u134zu7W1u5b2lYOKZRS+/gQeOd65W/Dy9sNXLuaz2YSDBOOBZPPvLTTKZVDtUyDa3phGVitM3++sb35F3Msw/hIbvwzmcyEyT/83Vc7o3IIrhkN3/J2rYDgR5trL85FfITVJj3m1kWdfUODSzDarxyqZRpc0wlivlbHvr9cnUtEfLZqUWtGi0swGrsmyzO4phvMZvWF2ivPn/a7yRpfRL2pb+UQXOtE7IuvVxHt+6/Mh6lua+0vi+92QOUQXDMWgUtVZs9rc2GKqL/NsKh+5dD7CibU4JoBiG1VuPbGD8cpW6Ovo3c0uQRDuTQHXNPftYoJ9HtnGWsTmZJk63d9al05VJNP8N71SK4xZfPnj/7z+Geb+kqyoOS9sqpUDsE1Y0GV7RFca1I1kyXypjaXYLQcejaYPz/+5BO4pgvk9UOu/WXc0exX2pY1rRxqN82x79kncE0nulakGwVvzJPNf2kpPGj1rs82EmrtulZU7ad477qQku6xv9JK52JL8k3l7pVVJiBoNH/CNX0D0Z2DGrW3nm/pS12XDHSUvbmFmmDaT+GaPjDXD1zbGW/ta0NvtrzzqdpR9uYWaj8VwWvXBc/mQeXtTqzFjElGs8qhjz6Cax1P9+pBhfeNRItfTF8ycuUQXDNaIJqRnCXItnpdU+SH6lcOfaiQZyXR4JpunJW49h1Hi19sndfuEoz20hyHwFvXh3/zw4PTUa+1fFuTb0t2mkP1yiG4ZjTCVyVH8YZbnoIjOyqk1JSpHKo+f8I1/fAVJIc+4y1/OZE0bOVQ9TENrumHU+ragrXlr3dtyehtpdT8Kcs0uKYXRF5ylv2Co/UPCBu1cgiuGQ1LVtIzoUDJ+ISE8ik1pRNqcM0YpCRt1N4MyvgAe8YAR9kbBwRwTX/GpS37onI+gVrT/yh7s0MaXNOTiLTnUFzWRa5hBS7BUL5yCK4ZjsC2pDlHWpZrlpxhKoc+aWwaXNMNalPSB2bJJuszrBc1qxxqb/bk+PTTT/HSdcJ1TeLaOinvQzwbbQQE6m5IlYsG1/TDIT19d52S9yGWoZ027/pUvnIIrhkO25LEtZ0BuZ+SvK3OvbLKebavGlzTLZk7Kz1SPCz3Y7oXjFY5VEM0uKabaSZLUtp0KCH7kxwFVY/itevap5/CNSMkcyWuzbYR0P6V6pdgtJHogGtGICp1LdfGBw3tqX0JhgLzJ1zTkwHpeu0SIf+Dusb3VL4Eo+3ZE67pi/+GxLUC2cYnORLvlIee7yt6CYb8NAdcMwS+KxLXrnja+Sjy/F1F0xxKJDrgmoGgLkl6W133teftlnEqh2qIBtd0xJ2XuLY30NZnmQtaVQ59LGehxvOzn/0M71wvyGVpz75IW5/Ve13jDalWXfvZz+CajnTnpI3U2DY+yR757t8Zp3IIrhkP24K0i9ozsj+HGJl/1UiVQ7VMg2v6YU1LXZvrkrlSsw+/ufcTI1UO1RANrukIv/l+0LMvI69a0tQ7tfV3yqQ5lKocqqkaXNPPtZS0OeSfyNo4IIYv3lPmXtmPld6QgmtGcm1S2kUt3936J3RRYzc1uQRDbkJNKhpc05OE1LWV1o++2+gX3viJdkfZf9rG7Mnx85//HK9cN8akzSELrpZVe6Lw9xpdgqGAaz+Ha3oyLm019MMTLc7Artmbml2C0e5CDa4ZyrW3Wtx898ze/ntjVg5VeiaIBtf0JCZ17VZLvSWtzCt3lEmofaRumuNANLimJ4c6LO+20j6me+RHxq0cgmsGJCztQ7obbkE1dlPbSzDaSXTANUO4dkvShfSvmi/0cD6zq1Hl0CcKLdTgmu6u3ZblGhG/ZZyj7I3SHHDNaOPa3bt395q9KIiM/1DVNEdbiQ64ZlCC25ImpP97rLkvcsR3G8yfelcO1VQNrumHb1tyMcHfNtd3noi/0czO590793auf3BPu5RaQ8/gmnFcu9OUa7bx3cah54f33ltORJlYfP71PTUSHa2kOeCaQVzbkTTxfrsp15gfNFHjvbc85jSZrBaTxzWzrn3lEFwzIH03JP1um3LN81bj+fPD6+MH2/jd3rySKbVPZS7U4Jre0DuS1srvpBp/gT3TMNHxwc2CX9p710Jc1GlDCq4ZyrXXJa7dmav3R4ULRrviuw03Ca7Hg2X3DTkKGlcOwTUjrtduSJp4vz3XxDjYcEPqYrSylNxzRdvKIbhmQJhdSQ/vt+cbpjvyDebPd7dS1QqTLPR1TSuH4JoBeWRP4trdhq4x79afPvfywepnFrqi13VLc8A1YxCQuvZupsGftuT+b91qjrsz/lo3Q9qit1RIdLTq2i9+gVeuG0N70n7xjVzz/bjeqHbvQpislwL+scqVQz+Ha4YmclvaLL5Rx9xUHdO4lVr9Y1hE6o5mlUM1VYNr+hHelbh2N1n/D5OFOpVDq5FGbSm75+9oVTlU3bNfwDU9Gd6TuPbXU40Wd7Xrbi/QjRs02Mtla6NnX+uzp8Av8cp1Y0x6CcZf19+jMkdqzZ/vXTnb1Hc7JJualUNwzYAkpPcSNHDNMl5j/vygQDfZCcSevqNJ5VAt034J1wzi2t0GriVqHFq5EbI2+/0OZPtEq5TagWe/hGt6kpK61qix5ER11z640MItt6JsGm1IVaoG1/RjVnrZSiPXYjVci5hNLcmmfuUQXDMe5rT0Wp93Gpxtod+uepbgbmt3cNjTd1WvHCoTbd80uKYflkWpa283OItsf6XqqZW1FttRirIpd5S9yRENrukLkZXeIrXXoJ+HebKqa7lW21Ha0/e0SHTANUNBLklvxftxo1u4Xe9XOxG12nLrU/v8PU02pOCagXBckl7r838a3vierDautd4ikJPtb1WtHKo+psE1PXEWpK79oGGvP8dyFdf2/K1/Y/vcbTUrh2qIBtd0hNqR3lb2cuOAsm+jimynZXxn+8RN1eZPuGZI1/5GetnKWhO9mWNVXFu2yPjWROymugu1X8I1QxH4G2m722b6gBNVZtFdWs73tp3eVj6l9ou6osE1HfmtQ641lbwQj0Qd3jiYl/XNbcw1lTek4JqBiBy6RWq+maSsJbBTIdsdp6zvbqUKKlUOwTXDYUn9RNrbKtXUuqtrrLIVzKTM709eVLBy6BeNTYNr+rmWPdRGbbzJEHKnomXCrRMWWT+A2bqqSuUQXDOea3OHXIs2G73uVTTnSMv+GfIaLdTgmq6mmSwvSFXba9Y1E3On3LU7jOwfY+WemvsEcM0g2Nakrm01fb2BJXGvrBfM/ys4Zf8UiTWtRINr+kFuSw98bjV/RRCZe6+sO8cHZ82yf4zAkhazJ1zTFepQ376NFmoevdfKO8H8hGnj55hRuHKoNv8fiWJsy/2RR4AAAAAASUVORK5CYII=); background-repeat: repeat-x, no-repeat; background-attachment: fixed, fixed; From d92b13876216064317ed28efa60a5a525a40a646 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Wed, 21 Aug 2013 22:53:48 +1000 Subject: [PATCH 15/35] Accidentally merged noko50 stuff --- inc/functions.php | 86 +---------------------------------------------- 1 file changed, 1 insertion(+), 85 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 9ea6aa9a..afebd5d8 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -1781,95 +1781,11 @@ function buildThread($id, $return = false, $mod = false) { if ($return) { return $body; } else { - $noko50fn = $board['dir'] . $config['dir']['res'] . sprintf($config['file_page50'], $id); - if ($hasnoko50 || file_exists($noko50fn)) { - buildThread50($id, $return, $mod, $thread); - } - file_write($board['dir'] . $config['dir']['res'] . sprintf($config['file_page'], $id), $body); } } -function buildThread50($id, $return = false, $mod = false, $thread = null) { - global $board, $config, $build_pages; - $id = round($id); - - if (event('build-thread', $id)) - return; - - if (!$thread) { - $query = prepare(sprintf("SELECT * FROM ``posts_%s`` WHERE (`thread` IS NULL AND `id` = :id) OR `thread` = :id ORDER BY `thread`,`id` DESC LIMIT :limit", $board['uri'])); - $query->bindValue(':id', $id, PDO::PARAM_INT); - $query->bindValue(':limit', $config['noko50_count']+1, PDO::PARAM_INT); - $query->execute() or error(db_error($query)); - - $num_images = 0; - while ($post = $query->fetch(PDO::FETCH_ASSOC)) { - if (!isset($thread)) { - $thread = new Thread($post, $mod ? '?/' : $config['root'], $mod); - } else { - if ($post['file']) - $num_images++; - - $thread->add(new Post($post, $mod ? '?/' : $config['root'], $mod)); - } - } - - // Check if any posts were found - if (!isset($thread)) - error($config['error']['nonexistant']); - - - if ($query->rowCount() == $config['noko50_count']+1) { - $count = prepare(sprintf("SELECT COUNT(`id`) as `num` FROM ``posts_%s`` WHERE `thread` = :thread UNION ALL SELECT COUNT(`id`) FROM ``posts_%s`` WHERE `file` IS NOT NULL AND `thread` = :thread", $board['uri'], $board['uri'])); - $count->bindValue(':thread', $id, PDO::PARAM_INT); - $count->execute() or error(db_error($count)); - - $c = $count->fetch(); - $thread->omitted = $c['num'] - $config['noko50_count']; - - $c = $count->fetch(); - $thread->omitted_images = $c['num'] - $num_images; - } - - $thread->posts = array_reverse($thread->posts); - } else { - $allPosts = $thread->posts; - - $thread->posts = array_slice($allPosts, -$config['noko50_count']); - $thread->omitted += count($allPosts) - count($thread->posts); - foreach ($allPosts as $index => $post) { - if ($index == count($allPosts)-count($thread->posts)) - break; - if ($post->file) - $thread->omitted_images++; - } - } - - $hasnoko50 = $thread->postCount() >= $config['noko50_min']; - - $body = Element('thread.html', array( - 'board' => $board, - 'thread' => $thread, - 'body' => $thread->build(false, true), - 'config' => $config, - 'id' => $id, - 'mod' => $mod, - 'hasnoko50' => $hasnoko50, - 'isnoko50' => true, - 'antibot' => $mod ? false : create_antibot($board['uri'], $id), - 'boardlist' => createBoardlist($mod), - 'return' => ($mod ? '?' . $board['url'] . $config['file_index'] : $config['root'] . $board['dir'] . $config['file_index']) - )); - - if ($return) { - return $body; - } else { - file_write($board['dir'] . $config['dir']['res'] . sprintf($config['file_page50'], $id), $body); - } -} - - function rrmdir($dir) { +function rrmdir($dir) { if (is_dir($dir)) { $objects = scandir($dir); foreach ($objects as $object) { From ec3eecd2e5032bcef7748704fa4b8b521133c1f0 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Wed, 21 Aug 2013 22:58:15 +1000 Subject: [PATCH 16/35] dark_roach.css: move images to stylesheets/img --- stylesheets/dark_roach.css | 2 +- stylesheets/img/dark_roach_bg.png | Bin 0 -> 13292 bytes stylesheets/img/dark_roach_top.png | Bin 0 -> 2886 bytes 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 stylesheets/img/dark_roach_bg.png create mode 100644 stylesheets/img/dark_roach_top.png diff --git a/stylesheets/dark_roach.css b/stylesheets/dark_roach.css index a0e2be44..6de59553 100644 --- a/stylesheets/dark_roach.css +++ b/stylesheets/dark_roach.css @@ -6,7 +6,7 @@ } body { background: #000112; - background-image: url(data:image/png;base64,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), url(data:image/png;base64,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); + background-image: url('img/dark_roach_top.png'), url('img/dark_roach_bg.png'); background-repeat: repeat-x, no-repeat; background-attachment: fixed, fixed; background-position: top, right bottom; diff --git a/stylesheets/img/dark_roach_bg.png b/stylesheets/img/dark_roach_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..5cc59dff50b2213f10e10e70b82e9a1b50586d50 GIT binary patch literal 13292 zcmcgzg_e0p4b02y%!7;p&yWWpt6#3f|H zBVfcMV8SD0!~@ENckqaq@Q46p#v@|EBWA`Yx`R&yAPYWFCI%3UPr`yv0w4sR6hc4@ zCLjR>LO=>80KSs45|FYHkU@DNkpB?bY)MGWF5 zrs5)|0+5H8nwyv!Kwe@RUJ@!E5^8P|Y5;jjfUh)sBsBaaw0tDA01A-M@srRAkkasx z((seg@{!U4C_qZbPYNjM1WD-yNa+C-B4rRHWe_5x6Ck4#B%=pJh>TvC4A3wDC_=_4 zM8+sg#wbF@1c)d(gD^RR2sxuLIU|6g1+DOmuNpahFk0!pwXB}9S} z0w9!<6%ZH*ECB*bg1}G^L;?hn1VMlT48$r4VugZ$yR1?mHW-La3dAl=#R{cjg;B9e zQL(|O*Z`EKVwa)<^z1TJ9MV)A0LoHv0wPDvE=|oYL(L&g%>ke+HHREErz|y~zYB;w z^<4#OE(ID+IT}uRn!9o|cL7wO;gY8Tlw685+zK?@inLq`v|Ng`+zPZnnOljL2M}dC zUO*nu@v6}A0isIJ4~QDQ03hlD&GJC1-+T8^?SW}9_OR?G(h3u?id%TclcdF4l&Dk1 zNAEL!P+PQ~Q))rN7QL{c?_-Mn>0AX`{>6__{>fQdIv&|}A4+E7oZhD|9yXecK6%}Y z{g_so7h0vKxAN@;!y&B1v}b%TanXnEHfNbQs{f;e=n>uLiEBr`+Xn=VVXN}n;cvIY z-V8s(16JAp$6vanlSDc0WWx1P#~LS&UhCJO)rep+oOayLL{a@eqO3aXr-)u>qlp%y zVC^&n&+u6giyYu6cc22!BF;1X|0;5TaXcL20bO)W0&_dAo};Rb^rTv6P$)( zx>VnGdlo{L5vN3VW$@xbpj@3%Wtn?0g);e3?7voA#JG!5`R$x{2#Y@wm%4ur$N5Z@ zZV^6731|oTpW$0R>HB+o-4=}_xh~QFuU*QowdbFSUR$=Gv{xyBCv{;TiIwrc5GbXn zz$!mwr*&6w_i=(JRbbipC%BV%V6hU)30xPP!JdmnQF-D@+~rbt&;dJ+NWOdE^zO%O zN|)%lUH`j^I9TCD=|#5IZ!$RN_m$p_B?yBjpNaSPCz8|G+uHeV0uMK|G#K z=K)W;F>YG>1#ZaT=s6h`jK!xJRpM?DOJl4p+Homcvr6HANpxU9U!q^Cm1``1vil;l z3e}^{JNT0x)4NEdT>N};jpK4ZC0jv3L0@<=iio-tuJAVIF?~bE;`mrXGF9a#;l1yU zAB4$X7pqSN;kpdo0L}9|8t9m9DsfeUm4Q(r-s3;>k2YVArd!7~WIt=+kjG)M-*hrc zRsnp%dFUfOWkA%Bfkuxd{J?REiVZ@iaf1!nQ|`5L0u`TJ{=f&n$FCfG#~Vkypn$`o zOd*P@8g;)LU)T?hsNX+-AQfKZ5GzLyCX{>uVyl&r*9ya#yMOrud6jvAv!{BBoZ|b}ek$7F&cZ*9pURyDYE~S|YWzWH zX#?J#0}Zc{QgIm0xzF@-5EEcHl6KSVOlcvW4|jG*m$aSp6Om-08X?B;F#&MB4#s#_ zlDSYPk>$8l;2Yq%NG9E-CA8G5Zg`5%qFZfi|KwV@oWQV*%@!3!Ooi8wZsUz$3aeHQ zXpX=Iu|y0;|0ahe2O-$E8>4<__PgEi3*+t0+)M;x{xpme?ip0`U5Bu}%fV?=*s}CkfIhm|8hNYY@sZh-;`MO>&APq=Dd^$HqpjvBcn4KKE9Z zAOS_ef|&`3mBC+Q;EM7jmCre3fqpf;ge#3WSd<^r7%EXSxjpet; z-|O3A{XWQPRH>M|hHcq+fHSvCV;we`3WP1;yDoWr>osV0c(90(>}u+issS`L(Wg-} zjo*-d=+_U|+QK_;Bimq!eI{zinoCXl6lbAu|@RkuRqs~RtB*|(_bX^@_CM7aZiQrvGReNeaB%b{oQY`b(bv8rZa1rcG(@8wX6i#c7;lpG zt>cai7q!Wc$!I=-?tEoYlT7sf4OEyf_zMLe_T$I6?8j8Yzfz-^G71V1!}~9_QcuDY zRQx?u2AdOkMoVCF;0Gk;EQ_HwndoPCH3{c<-;+5h5QWS*InzlKi9QJk9k6B6*C=i_ zdVrFCwYJw};D0Ejwar*@e`G|1=FV1Zzbk~y^GUB0q47?JrKK53`(nm`yXh2^&}**M zAgR6Wm!2Z`Qk%g(R&xWdKCeX0gR2_u^GpFWHt+Yvu_G>IhBhhAH>ZKev{bF_NzNbr z3i*)fu&)`M!Gyq_2b+rRcK#lWBAK9(3y%7wo8eHK2k9wDfne?;ee`(w#yhl|F$tg8 zQrimKAq%6a4!?!ZRig1!0Ash_hxzXuZ2n;95)=<94%2XfUHK~r7oEdOA~j2ApLHlktD zx#4Ejx`WMpO2r|`)sTNnd(iX?UH7XhZBMbTJ%m=)Wnt?~U`@uKqWhwI75ZVQpi6KC&ynkDYW zYx%pm=?Xw!)Dz5YReVCb%`th178e{!m#ej=>L&4<5k5;vLZWM$8;0m2-;Ir3tljUg zOcLmY)lITg3*9ZuA5p6`LsV7JZcUXt6x{4Qq<1vomu-!co(QHTQG<{2j@JwyJ?lqF zCY2lQh_Uha<_|vjiltg!86FEf?WXj2-C!x+Xqb$)@)t>~)f{jXnPVa$a5+8^TnOM! zMhd)s^}U?NreydiY&ifiHa0SUT%T^D?3P4t6Q*6X6ICiWUd4|_{QY`pTKV>^(pms( zO;wty--7zDlwy+TGmXEsmoSY&b9Jju`gm&)4E*3c?QRn9L{eUk#!B4`9b9 z0=L24JJ@3ZzfT(*>X?K{>&lbkkPPY9W%8o;@nkrAVK*hoe*)E$k(6A6^J*8d^1IuP zVLOGTk2)#zQa&;)(mN(2*X4b4RF_oy-EW_7w@I7m=$)az$*5@ShTZyA!yRm>8%L_r z3exOdGR}9D#P@+^*G(&PKiQyFL|9mNo!>0yjO>9=-N~=7ns4Vn+xhEdm5UwC#=L8$ zV}YiO360Jl^JM(?YH-js)yraBxyXjINzeXiLT|FKIf6+-SvAAT;QwN4%u%5e{@DKz zVh!KDTn5t*!;}JEd-(AHX>2&;RPkNS%G&ymn;3!HZqUzY<&jR}B(74={p4Jt&f89< zS7E+J92>Dp2(&1yDdUlcxA%_8$?3%#au4}DNq~@L6OWxo}F$oh>k?T9w@Z$(- zdXWZR>Yp;Kn*5~SZrU5LJ$`&k!76Rp@NOK$4gcVVGmr&0TbUJiGjtK%Ar_Jd#P}$I zm9Ga0Qs92hZzVCHHAH&7oi%Mb)UAC7oBF4pIEz|sY|}Smtv{O9p#+iWq&9OGvVQLn z#U9^1JNlxjOiqeN?s&bBPH$|}d%sWIzo_hQ@CzEIQ{Ax;?u8{K^dv7i`CXiA8a#9dvxWF)Rt<>0_uP|T7jE&;-BF|vM2NPKAnUcbY}%uI0}Ho(2;vDf@!u&7iF#u;HfFU%s^6F5%^oh79w=?c2{ zx~2H@kIzgh8NYjI#&=t3%uOC?Qur~}eaFhYEoGN7_Z3N!F&!Am<-WOHUtiDiWlIAQ z7DXP_DCkQ)%rvL7hrRY&&Z%}QN^Q3A7k=bRIdD4*p%sox+;e7Q&HBwqcSSpm(aa+VZ##uA?*Ktq1qhf{<&a5=x5}6vvmVnN-&;t?go)H##ACPty!~dLrN@{F#d2t~-w>sH1`RO7v zC51Ub@&sC`Cm>a5B^vV^>OelMXKS9g#7d&BvCyX0{~lWYF~?-e#FnYiSNr3TywJtL z#;V1L+ zi>sblYuNQy>zH$?y{Br&eT?s49TaBHJ>_MkpJ7i)=j8>3@P17m9@LM!@k|L_jw-bi zY2xLCu0CPjS`IqAU%I&83L$-vEvX^F}692Uc$^mKc7bzH<%@9+tW74yF zJUAf2O6qz^REWQNVY5-BtHzvRlV>pqwQ?#pB$lz0(%@jyLGr9y)`PqQv!50=w(+IZ z9rp41AE!^*s$6Zv4Epyt~WJsN*GrWP!(8yx;^H`{sU<tC=wP%Iuflntb~+Cd}FPadW{aODJS9_hR%7XEJjdyK91RJo=U1= z3U&JO9%25~gs+ysX*^W&B{aoC^-dMxxt{y!%qeGW&Ti?h@*U@$6@}SuuR6%wzgjk& zgu}uV3yPDkV)fs!KuFSk;Igd-EcbP)Hw;6$sG_rPX1yf6 z=4*C35u>V~~NIGF*77m(VSDF8Fl9mJo9ZO2wLjB@NPtW%BVu9bQ z77y!{P}b?s<(}^4x9hV7I^{_ov&OsY%8!JYr3A~-uyqzIujuEUwU&yj=JOwDW$jj9 z{feoS9@RkhB&&4yv<&+L?dn&r(f=`_Wfe-YOz^fSy@B!X7-!6 zO!(lAVWDGe1|b(Y&&=r+&(&0G(QWLQBND}J=`letwN^|UTQUQV_^m8i{?q++OH$#i zHYSaXyGevf#{t(){KE9~s-Qjcb$jvk{9btHPSLv;2X9G=nhKSy0uWm-hT1wh@E_B^ zTYny2&0QRPDyl%|BbA$WYx&_<$9GGCZF|&A*6tVWK&6+Rq4(-Yzl<%@K{fy0tY4jJ zFJI9icVXR+!*#=tl%eHq9FHSUzK7JcxqRX>9($0pfa}g*WFzwgHI4d{+s{xEFS{CC z)w%fP&tnI?czCisEF@;4t`_N7S08>(%VFA76mPk^ko@Us0FkDInAwYbG0hcHCd}++ z0&tv5v>#>_khc^X=Kk?I7V=v=cbt$RPhtG1yv=H@sW-e-x^yHX9G(_LC5 zPUdIH1*NlPdjvXF4wZBfzMVh#Bam5a^Z2prD2hP_L#dF7gv{8E%jf9p+u7cX51ECW z@L8Kk@dqS|H{xR$85tLp=Mbv0S5J{6_YoHB%e8J-f24*j^ARZs5`~s6i&4Ge<>#UD z<(AJ+uvw5;BGpW4vi;_TkeADeuiM~&Ho`PS6R=INS*6<0%BQL|`FwE!<8i(WdboYckB=`kV-Uw%c4M+Sj&PFt&Ly z`F+Q>{gj$G#ndy)t}a7Z6T(1pDR)d*w*~gbr#Z>Hu(@{bz`GDq`{}F5d@IV-ob;JW zsMCesM$(ZT(`HFQhw-a=%Xk<0ZRc=30SALzJ}(Jr7p$wa2H#5&x^h8V?9PiWiAdNX zc5|#iLRW&0sZ2~vAN;;=U)p`;L!!C+gu`a95uFGlh*aEkGCbS=K>;X zwEyudk}Gg0ihjRc=Ie-CZ1F)n_GMEwX`PR-7%^A`8@?*MdP(s#dM>Ll(ZV&&GF)0b z%WM)=%*CSlV9mJuwi-Q}|0 zEA{$1)AZg!_&mTdNQQadJrV5PcyWxm!CGFxkEXO_^meYk-}n8{PO)MRt5K^kGIXvc zk+VmzLs_bVIOe;?gFe@E-wEk?jM&7*-0u-0vd!~M3fo7{GpyQ8fb)TAEm8fY%!sVK zhxr&4WOb5Ny{cgjBCZ^L91bf~Qpe1TwLDtAy0GCF>nr<6kG)EP`KnAwYQ{z>3l?jk zpjMCit3S%6J_;as)}bqCEi?P&Amp8U-YoHz@fA&DfOTacxAgs0U1bjVt@{Bs?>${! zNXMk*)ycP1hT~l=0+JIRGpSm#^8}R8BQgfPug#68H3wi}+nK2sRJ4MoARAA8$#SJ@ zzKA#**__{}o0_Q@pGYjtNIRO}PUiJ|a8;mpc4g+n@ia{?KJ7gJspOjAf;LxgN$+R5 zEfBX6=xyaTRztCbHT;XNx6*|5r=p2w{FYrU<#AzDkD*o0{YZ1y67PVTdu1GrgW>UT zQpp>|yM8-Av$3w}L*;oEW9YKKEex8AHNiTX`1^&Sd8%eJkn=E*P2)jP>(Q{4?Ag$pH{gHQpaLvU@g&Fn#~xQGNwkOlSPUdDoU z^4|e=lVH`RGM#(o3`J_Luz}9bko`#aBWqpt%ytRTe4H*OSF6FyT7S(eY`Wu0^K5C! zYnibd^~mDX-bI4!P3f03JoRbFre1%Ce^`4!)`k#RDljNW9pia?c<7-vdHMOyLc&}V z#7z`!v|=T*58wZYTh<@wU?;gEnU0`uib%`CFY-`8PZ_Hq=5+4azL0UG@_5D_3BSyA z#%p1+nEk|zR;)y1mNy*tC_WO6euk zR|?5I3p%NV1-|MV$|7BN+^5Kci&gQ~`!<}|RFqiJcZ zwHl!Pj{^B5zCImaEm&+6DQDG`(x6_V5?#L?>nc0hhc6q6J=RbNN55BF{hGS;y83i&& ztKEfqEfvI(M};GDdEvKLjUMXOPw85NYicEAo3-DIRZn7Z0%h{DPHn66!lRiGyTCAoqF#{fZDwckjh7>AeWt9tS#8U~+F%dL;jHzSB8TeZ#CT zQJ+F6WJKD_~VGdJC@p@WPFxO!)m1}7;<;mECib!iTvRjG>boQb`q8@a` za?f3+1>1IwRu#LZ3pOo(TZCMiM;M-zpp%F&ZhD^U^~U#=9F49>O1SEEwe_B>AVM&n zHyq&Ri%M7tJtcCE#rsV@+EKzM|0G0L@VADo;SGP-7%7USZn+oL=OpWq!eiiE#_%}PxU-r2yaCE&QHCxX?r8lqSsX}KU#&dyGTI{kiPm*R!6yH`b+lTX! zB-iEil-YEf(}n(7``y6gcXsx6qFps2;#tssJu5n3!*D<-v6y+D8+mB^LhJeJPM$0| zN@|;5Mc(mATQ}Eknw2+#(UVs$@>gg3s$FrCfd=!Cx{pKY%c(og9YyZku3Q2IAGUcE zR*pQW?0H1~;C_#73D>`=IXI zLv<&>zRbMQ*-i)vm8;H6r*$7nDIv+vI>a{JOjw5;mf&|a%?%zUeezpu>Kb*ry_ciS zvv9)C`ZJr}eyh%0NQ}z9>i6S7;k*$M(ap1PtHL?sWslnuI9v!0@5yry_4Evv*C37< zJnJ$*w|IxF|f{fCEk;7(9jz#q_rry^bPz-MbNQ<{gD}goGB)B6GEshTH37 zv!Y6zcCs2jxqCJ~pQuu@vl?qbh-?;2%a~0=#03*AeLcLq(zg0Jjr#(O-OgGyhkDUt zx8u`Sy_nzD7B}-Lub8HFioR55cjXBb8Rz5~E+MM0E4I-m-FLyLL==0=x^zSHAX=NU zEyZqbn>JObU8?q3-7HHOuQ!jB$pS)@MXAY#{5`nGq;c#6#V`{Sg)gbbeo{JS$qzAF zl~-0=?sJg3!8i?TE#mib5iggkFx&1lKxMRdsE7OK7ST!gh~*iHlt1LvF9m zgv10wtcn;l7ajR#J*8lXFDQ&lclGCA8Vmgqu|^lW+vQg4cVj{kc~_B-F!SbiwqaLz zmyu1jF;(W;2lUVrS)^y=RPty zqu~l{2D}Ca^5~nkq~OylwJn1goz<(Ey!XWb1By%4Y9mn`*fO~=87c~+LeZR0Q=8S+ z_?%ACvf?G$&!$IAoswA{xNh(%4y#=f(Rx@CzNtuY_iTiAW(M znc2No^L3V|hR_;Quzh=SfUlpKk{+nzgXX89Y^DN+7Dz8rYtzu` zY`aXZi#pb=*5=Gegh9`F{w-pb=T36{tR)EvceT$S(x-;_qQBj5vx@r5^Hinib^UUG zkrR2%7j9JYwdYDb+srJDFV6hr*!ID5VGuoj8_lb^XoY~dMN|!)Z9qg{i{G1HGwstn zp+`Wl9#B6aM{;P~3JuC`};O*reOH!4c!z9X9%lJ5OeG z#Z4QL7H7#Yb96BDgEuWh0r76!c^{arI+>=BA)g@f5|gqhu@=Y8NfC7uH}#pLmF0JC z2rfK=3*UM(kXdgh2qViimA}_IT17U~?L%4hzAo<)f@w@*p z`JNdFr4TvqgLsA=2$S*|c$}y_-W;UAX=sL7DgR(~K;4j940z9gfHXa28HUAoEpWy` zrAI|ON^8oU)u)1Q7h<{=n5B)~06PPmbHTcF57$KkNy%)i?!3WZs!^_3Jh?XMcjs8DtB@`+fU8))ZZPgvPu@6J%UjKPe_vX0F8USvu<8O z0qN~y%u@s_VaEeP6wmevglCPfO9-UQRx_J6v0-@eB);sdTqR@z(0IW_H=;^o%t{{& zv?!|rJ8q8_N@PJ-$REG`d#ev=4~TO=`jq0v>i8C+QLMVhEXFbcyrclMoi0sjeTr~O z*Tably>4^U!&oW~tNOKa_%IX-SeU4&hG!xiNNi1RX;Wc)p8F{mw(&0P%AMnbT~2W| z2k3ek?eaXeSobn~RGszaWcl=&^Ua9KU2yf3e${jD<@>GMY zel+V!`mlTo%sY7qUgHmohoduGcsz$WO4f@eFZshHK3FL$W+W8s-fy z9whaGhmaWkR117wZFyRXtUJyP2W}(kN8pD$gA>y4)j#2$LH9K`O{c zwO;35vFy|4I5>OcpZhDXkHYiO9s-f1LvkWEF;#|})YcM#TJMCtMi7u`K{TgS70@tF zr0~rfzg!@vLN4xG&xE}k`fg_?kFN6CG}N0}D@JA}{pswlInb_?$wdG|N%+cp$09R? z-r#1m>XQCcw+Yx8c{L6ROX>O@^uOt|`89qtKYwI}H3tFoaQ%xqcsq2{>%-U*rFCEN zgcB_VT{>aTME~mEj)f=&l2QaXOF33Qbg8^o_9CsSPxG0YTVTJ4k7HM00rE@+Gm+6MiU`avZ4q zFF1_RI%ZB2a6kLxhPUX}hUc4;*JQl2YsI)PJj}W;hNy-GtZG_nf+TwgRC^?et!GwI z$)rESoSETtMcL2xgCg8)WK44meyp5pA@4h$ZD-t`{c!GJ`7gaU^`rQ7dx-F4*O&sC8(j$fw^9%1vL03%DG+m09cy!H5c&1=Dg zUSD$1k*)&rF6YPz7_Hl3FR*^=gO7VNvya-|PHS&J)j8GMJjyfU<#3UVd=HJlpDK%G zL3NdT?dsSRQ26H#^u!lG!5XX=E8fFUSuZhr9l}+;fR)gQHM-bhbW>s&gkuONwjOLA z>NkH%eW=kfT9u&~17|h3+UZn8e7l53w^wEvRs-94{RpfzL-dyPYK8KjP6m zNw6x9;gS=qoy~28G${h3zWKqxov64W5-F1<>Aq=rexDM#n`p9!jX`a;1bK|?07U#p zU<}xEGpX77=HJBBoKum2QU9fhj`_4imGa21xVTdR=u0{`Z}gp7Zjad&iqT&N5~+ZrQcs=t|Ltp;k7RsO`giAAlTQ5Mlg3 zmKAh&N=?o?7m-_H zF2EE=*shLNnd|?BmTD>HbqZFZT`BQSQRJM~|FOVtdp#YSlKt!+;|{8~J(qM`W}rE0 zVCqTUUOk`e5aVjGoSv0Kod=E9%H%ddni_$aO`vjL%Y3i+jFsj{5L+?XvR%x`Tgi-p zYxsH?W7S1gmw)s58#zcpfu-sRJe2;;bKEQ{Z$9JA(hk$gucScD#YpVX!N|#3#a~E^ zwbW28nv)!vZx&Yl(LeSbMRi$z4Zr-d zavSDg4l)bn1%mUFn>XOlTxWm>@kJ;o_hCast!MFuGBA;RiL%1SozseSVc#`}{xK*K zxA38AFWB<|G|*%TAhC!3@1UyPfjZOiKU<2$etIi*yfWIHdpz!b7e8f^@8$!|#(H8bW6LpzWW|G0o7LW=vwv&0vD zX1;NC_F;d6cnKKfG@d_7*DO@;I|5xkJ1a?s(Vjgx95KK zQ(yW=AZ>db4}rezV;3_S)8!7kcTff9m7MMij7b_}5Nh93Y%&cjxiIDk zuW`29@2Gz#b}|~^pc_g$WQSR;5!ZA>R~*~OEX@YVahCwwS#NX{ckLyuq&HgJ*5UR~ zrLxo_>BsUnH4irpMP{CpVkiKQiI6;Q5X&B#cG&d|=-IW}t6Xff^V>LhXm)n*d_&uJ zwE&D__vCw#u4;D{4=uAMAafneLl?EVFi)Y@DTR6>BX!UJO)T5i>z|etK`f;nRjbI* z{54N_koUq&kXZ{vkj`Tj$ZPg|^+vdtlyr)Fe<1qM>(jMQja9eJ@gq4J6xMg&}}Hi7xr?vU!rvi3_NgTCb_LO zcFTOq@{b3Qbw$ErT4qb|c_^J<{a2J+>H)jMW_j^7fj63CVZIjW0gV`l=J9E1X8Q71 zNZRo)eG(L*2K4p7Q->REH_N-jBbxieV5JH0X!xc_6%3wq%pd&4PZv) n2LHslZfqAC0Vg`xU-qP@?qfGuno000V4X+uL$P-t&- zZ*ypGa3D!TLm+T+Z)Rz1WdHzp+MQEpR8#2|J@?-9LQ9B%luK_?6$l_wLW_VDktQl3 z2@pz%A)(n7QNa;KMFbnjpojyGj)066Q7jCK3fKqaA)=0hqlk*i`{8?|Yu3E?=FR@K z*FNX0^PRKL2fzpnmPj*EHGmAMLLL#|gU7_i;p8qrfeIvW01ybXWFd3?BLM*Temp!Y zBESc}00DT@3kU$fO`E_l9Ebl8>Oz@Z0f2-7z;ux~O9+4z06=<WDR*FRcSTFz- zW=q650N5=6FiBTtNC2?60Km==3$g$R3;-}uh=nNt1bYBr$Ri_o0EC$U6h`t_Jn<{8 z5a%iY0C<_QJh>z}MS)ugEpZ1|S1ukX&Pf+56gFW3VVXcL!g-k)GJ!M?;PcD?0HBc- z5#WRK{dmp}uFlRjj{U%*%WZ25jX z{P*?XzTzZ-GF^d31o+^>%=Ap99M6&ogks$0k4OBs3;+Bb(;~!4V!2o<6ys46agIcq zjPo+3B8fthDa9qy|77CdEc*jK-!%ZRYCZvbku9iQV*~a}ClFY4z~c7+0P?$U!PF=S z1Au6Q;m>#f??3%Vpd|o+W=WE9003S@Bra6Svp>fO002awfhw>;8}z{#EWidF!3EsG z3;bXU&9EIRU@z1_9W=mEXoiz;4lcq~xDGvV5BgyU zp1~-*fe8db$Osc*A=-!mVv1NJjtCc-h4>-CNCXm#Bp}I%6j35eku^v$Qi@a{RY)E3 zJ#qp$hg?Rwkvqr$GJ^buyhkyVfwECO)C{#lxu`c9ghrwZ&}4KmnvWKso6vH!8a<3Q zq36)6Xb;+tK10Vaz~~qUGsJ8#F2=(`u{bOVlVi)VBCHIn#u~6ztOL7=^<&SmcLWlF zMZgI*1b0FpVIDz9SWH+>*hr`#93(Um+6gxa1B6k+CnA%mOSC4s5&6UzVlpv@SV$}* z))J2sFA#f(L&P^E5{W}HC%KRUNwK6<(h|}}(r!{C=`5+6G)NjFlgZj-YqAG9lq?`C z$c5yc>d>VnA`E_*3F2Qp##d8RZb=H01_mm@+|Cqnc9PsG(F5HIG_C zt)aG3uTh7n6Et<2In9F>NlT@zqLtGcXcuVrX|L#Xx)I%#9!{6gSJKPrN9dR61N3(c z4Tcqi$B1Vr8Jidf7-t!G7_XR2rWwr)$3XQ?}=hpK0&Z&W{| zep&sA23f;Q!%st`QJ}G3cbou<7-yIK2z4nfCCCtN2-XOGSWo##{8Q{ATurxr~;I`ytDs%xbip}RzP zziy}Qn4Z2~fSycmr`~zJ=lUFdFa1>gZThG6M+{g7vkW8#+YHVaJjFF}Z#*3@$J_By zLtVo_L#1JrVVB{Ak-5=4qt!-@Mh}c>#$4kh<88)m#-k<%CLtzEP3leVno>={htGUuD;o7bD)w_sX$S}eAxwzy?UvgBH(S?;#HZiQMoS*2K2 zT3xe7t(~nU*1N5{rxB;QPLocnp4Ml>u<^FZwyC!nu;thW+pe~4wtZn|Vi#w(#jeBd zlf9FDx_yoPJqHbk*$%56S{;6Kv~mM9!g3B(KJ}#RZ#@)!hR|78Dq|Iq-afF%KE1Brn_fm;Im z_u$xr8UFki1L{Ox>G0o)(&RAZ;=|I=wN2l97;cLaHH6leTB-XXa*h%dBOEvi`+x zi?=Txl?TadvyiL>SuF~-LZ;|cS}4~l2eM~nS7yJ>iOM;atDY;(?aZ^v+mJV$@1Ote z62cPUlD4IWOIIx&SmwQ~YB{nzae3Pc;}r!fhE@iwJh+OsDs9zItL;~pu715HdQEGA zUct(O!LkCy1<%NCg+}G`0PgpNm-?d@-hMgNe6^V+j6x$b<6@S<$+<4_1hi}Ti zncS4LsjI}fWY1>OX6feMEuLErma3QLmkw?X+1j)X-&VBk_4Y;EFPF_I+q;9dL%E~B zJh;4Nr^(LEJ3myURP{Rblsw%57T)g973R8o)DE9*xN#~;4_o$q%o z4K@u`jhx2fBXC4{U8Qn{*%*B$Ge=nny$HAYq{=vy|sI0 z_vss+H_qMky?OB#|JK!>IX&II^LlUh#rO5!7TtbwC;iULyV-Xq?ybB}ykGP{?LpZ? z-G|jbTmIbG@7#ZCz;~eY(cDM(28Dyq{*m>M4?_iynUBkc4TkHUI6gT!;y-fz>HMcd z&t%Ugo)`Y2{>!cx7B7DI)$7;J(U{Spm-3gBzioV_{p!H$8L!*M!p0uH$#^p{Ui4P` z?ZJ24cOCDe-w#jZd?0@)|7iKK^;6KN`;!@ylm7$*nDhK&GcDTy000JJOGiWi{{a60 z|De66lK=n!32;bRa{vGf6951U69E94oEQKA00(qQO+^RV1_lN%6GwGAlmGw#cu7P- zR4C7tlFJRiFbG2}cIXlvr2B95dZ>!X$7y=t5Q3ipq2s(lQb-C=NaVW>LYRDN+g!$k z3v750q`ojZ-c!SZ_01U>Z7+1ZKYC2xZ#`yqQT}>8WiI2*VrQagcl5KpFZMOw1;%T3 k3$^DBp1Co9XU=0_-@~$VqL-^)cmMzZ07*qoM6N<$f(p)H=>Px# literal 0 HcmV?d00001 From a65c4f2c1fa81073f380ad42b8073720de02fee2 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Wed, 21 Aug 2013 23:03:38 +1000 Subject: [PATCH 17/35] permissions fix --- stylesheets/img/rect820.png | Bin stylesheets/img/rect821.png | Bin 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 stylesheets/img/rect820.png mode change 100755 => 100644 stylesheets/img/rect821.png diff --git a/stylesheets/img/rect820.png b/stylesheets/img/rect820.png old mode 100755 new mode 100644 diff --git a/stylesheets/img/rect821.png b/stylesheets/img/rect821.png old mode 100755 new mode 100644 From 5cc7bd860fe653db831b20c7937a80ef35221f56 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Thu, 22 Aug 2013 03:35:57 +1000 Subject: [PATCH 18/35] Add cache and gm to pre-installation test. Don't die when we can't write to templates/cache. --- inc/template.php | 3 ++- install.php | 24 +++++++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/inc/template.php b/inc/template.php index 09e27c26..ef688944 100644 --- a/inc/template.php +++ b/inc/template.php @@ -26,7 +26,8 @@ function load_twig() { $loader->setPaths($config['dir']['template']); $twig = new Twig_Environment($loader, array( 'autoescape' => false, - 'cache' => "{$config['dir']['template']}/cache", + 'cache' => is_writable('templates') && (!is_dir('templates/cache') || is_writable('templates/cache')) ? + "{$config['dir']['template']}/cache" : false, 'debug' => $config['debug'] )); $twig->addExtension(new Twig_Extensions_Extension_Tinyboard()); diff --git a/install.php b/install.php index a6937028..8725df6d 100644 --- a/install.php +++ b/install.php @@ -512,6 +512,13 @@ if ($step == 0) { 'required' => false, 'message' => '(Optional) `identify` was not found or executable; command-line ImageMagick image processing cannot be enabled.', ), + array( + 'category' => 'Image processing', + 'name' => '`gm` (command-line GraphicsMagick)', + 'result' => $can_exec && shell_exec('which gm'), + 'required' => false, + 'message' => '(Optional) `gm` was not found or executable; command-line GraphicsMagick (faster than ImageMagick) cannot be enabled.', + ), array( 'category' => 'Image processing', 'name' => '`gifsicle` (command-line animted GIF thumbnailing)', @@ -526,12 +533,27 @@ if ($step == 0) { 'required' => true, 'message' => 'Tinyboard does not have permission to create directories (boards) here. You will need to chmod (or operating system equivalent) appropriately.' ), + array( + 'category' => 'File permissions', + 'name' => getcwd() . '/templates/cache', + 'result' => is_writable('templates') && (!is_dir('templates/cache') || is_writable('templates/cache')), + 'required' => true, + 'message' => 'You must give Tinyboard permission to create (and write to) the templates/cache directory or performance will be drastically reduced.' + ), array( 'category' => 'File permissions', 'name' => getcwd() . '/inc/instance-config.php', 'result' => is_writable('inc/instance-config.php'), 'required' => false, - 'message' => 'Tinyboard does not have permission to make changes to inc/instance-config.php. To complete the installation, you will be asked to manually copy and paste code into the file instead.' + 'message' => 'Tinyboard does not have permission to make changes to inc/instance-config.php. To complete the installation, you will be asked to manually copy and paste code into the file instead.' + ), + array( + 'category' => 'Misc', + 'name' => 'Caching available (APC, XCache, Memcached or Redis)', + 'result' => extension_loaded('apc') || extension_loaded('xcache') + || extension_loaded('memcached') || extension_loaded('redis'), + 'required' => false, + 'message' => 'You will not be able to enable the additional caching system, designed to minimize SQL queries and significantly improve performance. APC is the recommended method of caching, but XCache, Memcached and Redis are also supported.' ), array( 'category' => 'Misc', From cdeccbb9ba041b47aec0b6e6cc7acea268cf32ad Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Mon, 26 Aug 2013 12:13:40 +1000 Subject: [PATCH 19/35] Uploading files via URL: fix for URL parameters (eg. image.png?id=343543) --- post.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/post.php b/post.php index df7fe435..7ab1eb59 100644 --- a/post.php +++ b/post.php @@ -273,8 +273,12 @@ if (isset($_POST['delete'])) { if (!preg_match($config['url_regex'], $post['file_url'])) error($config['error']['invalidimg']); - - $post['extension'] = strtolower(mb_substr($post['file_url'], mb_strrpos($post['file_url'], '.') + 1)); + if (mb_strpos($post['file_url'], '?') !== false) + $url_without_params = mb_substr($post['file_url'], 0, mb_strpos($post['file_url'], '?')); + else + $url_without_params = $post['file_url']; + + $post['extension'] = strtolower(mb_substr($url_without_params, mb_strrpos($url_without_params, '.') + 1)); if (!in_array($post['extension'], $config['allowed_ext']) && !in_array($post['extension'], $config['allowed_ext_files'])) error($config['error']['unknownext']); @@ -305,7 +309,7 @@ if (isset($_POST['delete'])) { fclose($fp); $_FILES['file'] = array( - 'name' => basename($post['file_url']), + 'name' => basename($url_without_params), 'tmp_name' => $post['file_tmp'], 'error' => 0, 'size' => filesize($post['file_tmp']) From 09388f6588843d8042fc2542d049deb47c57ce07 Mon Sep 17 00:00:00 2001 From: Dan Saunders Date: Sun, 25 Aug 2013 23:23:02 -0400 Subject: [PATCH 20/35] Added a post-delete action for themes This would be really useful for themes that focus on posts --- post.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/post.php b/post.php index 7ab1eb59..2450bf06 100644 --- a/post.php +++ b/post.php @@ -75,6 +75,9 @@ if (isset($_POST['delete'])) { } buildIndex(); + + + rebuildThemes('post-delete', $board['uri']); $is_mod = isset($_POST['mod']) && $_POST['mod']; $root = $is_mod ? $config['root'] . $config['file_mod'] . '?/' : $config['root']; From bb06593ef5f21b4bc4432530e5ca634f09764773 Mon Sep 17 00:00:00 2001 From: Dan Saunders Date: Sun, 25 Aug 2013 23:26:41 -0400 Subject: [PATCH 21/35] Added post-delete handling on themes that should use it. --- templates/themes/catalog/theme.php | 2 +- templates/themes/recent/theme.php | 2 +- templates/themes/sitemap/theme.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/themes/catalog/theme.php b/templates/themes/catalog/theme.php index 96550683..424a813b 100644 --- a/templates/themes/catalog/theme.php +++ b/templates/themes/catalog/theme.php @@ -20,7 +20,7 @@ $b = new Catalog(); $b->build($settings, $board); } - } elseif ($action == 'post-thread' || ($settings['update_on_posts'] && $action == 'post') && in_array($board, $boards)) { + } elseif ($action == 'post-thread' || ($settings['update_on_posts'] && $action == 'post') || ($settings['update_on_posts'] && $action == 'post-delete') && in_array($board, $boards)) { $b = new Catalog(); $b->build($settings, $board); } diff --git a/templates/themes/recent/theme.php b/templates/themes/recent/theme.php index 08d910f8..4bbf1469 100644 --- a/templates/themes/recent/theme.php +++ b/templates/themes/recent/theme.php @@ -24,7 +24,7 @@ $this->excluded = explode(' ', $settings['exclude']); - if ($action == 'all' || $action == 'post' || $action == 'post-thread') + if ($action == 'all' || $action == 'post' || $action == 'post-thread' || $action == 'post-delete') file_write($config['dir']['home'] . $settings['html'], $this->homepage($settings)); } diff --git a/templates/themes/sitemap/theme.php b/templates/themes/sitemap/theme.php index 226f8357..7e5b1984 100644 --- a/templates/themes/sitemap/theme.php +++ b/templates/themes/sitemap/theme.php @@ -11,7 +11,7 @@ // - post (a post has been made) // - thread (a thread has been made) - if ($action != 'post' && $action != 'post-thread') + if ($action != 'post' && $action != 'post-thread' && $action != 'post-delete') return; $boards = explode(' ', $settings['boards']); From 08bb2894bc358600eeaa03014080c3c2dd910cef Mon Sep 17 00:00:00 2001 From: Dan Saunders Date: Sun, 25 Aug 2013 23:50:29 -0400 Subject: [PATCH 22/35] Rebuild themes when a post or file is deleted... Rebuild themes when a post or file is deleted in the moderation panel. --- inc/mod/pages.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 706513a3..5ee8221b 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -1241,6 +1241,8 @@ function mod_ban_post($board, $delete, $post, $token = false) { modLog("Deleted post #{$post}"); // Rebuild board buildIndex(); + // Rebuild themes + rebuildThemes('post-delete', $board['uri']); } header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']); @@ -1333,7 +1335,8 @@ function mod_delete($board, $post) { modLog("Deleted post #{$post}"); // Rebuild board buildIndex(); - + // Rebuild themes + rebuildThemes('post-delete', $board['uri']); // Redirect header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']); } @@ -1354,6 +1357,8 @@ function mod_deletefile($board, $post) { // Rebuild board buildIndex(); + // Rebuild themes + rebuildThemes('post-delete', $board['uri']); // Redirect header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']); @@ -1392,6 +1397,9 @@ function mod_spoiler_image($board, $post) { // Rebuild board buildIndex(); + + // Rebuild themes + rebuildThemes('post-delete', $board['uri']); // Redirect header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']); @@ -1442,6 +1450,8 @@ function mod_deletebyip($boardName, $post, $global = false) { deletePost($post['id'], false, false); + rebuildThemes('post-delete', $board['uri']); + if ($post['thread']) $threads_to_rebuild[$post['board']][$post['thread']] = true; else From 00a1841cbc6fb76cd96c51142a3a1b86a073d58a Mon Sep 17 00:00:00 2001 From: Dan Saunders Date: Mon, 26 Aug 2013 00:36:41 -0400 Subject: [PATCH 23/35] Regenerate themes after editing a post --- inc/mod/pages.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 5ee8221b..3305e0c2 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -1306,6 +1306,8 @@ function mod_edit_post($board, $edit_raw_html, $postID) { } buildIndex(); + + rebuildThemes('post', $board['uri']); header('Location: ?/' . sprintf($config['board_path'], $board) . $config['dir']['res'] . sprintf($config['file_page'], $post['thread'] ? $post['thread'] : $postID) . '#' . $postID, true, $config['redirect_http']); } else { From 319cd2520ff18120570e56eba04ceb1206007375 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Tue, 27 Aug 2013 08:13:23 +1000 Subject: [PATCH 24/35] Upload by URL: still use fatal_error_handler() on shutdown --- post.php | 1 + 1 file changed, 1 insertion(+) diff --git a/post.php b/post.php index 2450bf06..6de3edfa 100644 --- a/post.php +++ b/post.php @@ -288,6 +288,7 @@ if (isset($_POST['delete'])) { $post['file_tmp'] = tempnam($config['tmp'], 'url'); function unlink_tmp_file($file) { @unlink($file); + fatal_error_handler(); } register_shutdown_function('unlink_tmp_file', $post['file_tmp']); From 5003a692b4505eccdb57447fbf77e4333c8014c0 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Tue, 27 Aug 2013 08:55:03 +1000 Subject: [PATCH 25/35] exiftool: Use -overwrite_original. Important bugfix; old images were never being deleted when using exiftool (instead kept as *_original in /tmp) --- post.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/post.php b/post.php index 6de3edfa..21d294cd 100644 --- a/post.php +++ b/post.php @@ -526,7 +526,8 @@ if (isset($_POST['delete'])) { escapeshellarg($upload)); if ($config['use_exiftool'] && !$config['strip_exif']) { if ($exiftool_error = shell_exec_error( - 'exiftool -q -orientation=1 -n ' . escapeshellarg($upload))) + 'exiftool -overwrite_original -q -q -orientation=1 -n ' . + escapeshellarg($upload))) error('exiftool failed!', null, $exiftool_error); } else { // TODO: Find another way to remove the Orientation tag from the EXIF profile @@ -589,7 +590,8 @@ if (isset($_POST['delete'])) { if ($config['redraw_image'] || (!@$post['exif_stripped'] && $config['strip_exif'] && ($post['extension'] == 'jpg' || $post['extension'] == 'jpeg'))) { if (!$config['redraw_image'] && $config['use_exiftool']) { - if($error = shell_exec_error('exiftool -ignoreMinorErrors -q -q -all= ' . escapeshellarg($upload))) + if($error = shell_exec_error('exiftool -overwrite_original -ignoreMinorErrors -q -q -all= ' . + escapeshellarg($upload))) error('Could not strip EXIF metadata!', null, $error); } else { $image->to($post['file']); From b96314ef23121f0ad030e75755ea85a0c25bbe49 Mon Sep 17 00:00:00 2001 From: gtx Date: Tue, 27 Aug 2013 12:50:32 +1000 Subject: [PATCH 26/35] notsuba.css from tagechan --- stylesheets/notsuba.css | 88 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 stylesheets/notsuba.css diff --git a/stylesheets/notsuba.css b/stylesheets/notsuba.css new file mode 100644 index 00000000..ef9e1057 --- /dev/null +++ b/stylesheets/notsuba.css @@ -0,0 +1,88 @@ +/** +* notsuba.css +* Tagechan is the best +* you are forbidden by law from making any unauthorized edits to this file. I-I'll sue! ;_; +*/ +body { +background: #f2edd0 +} + +a:link, a:visited { +text-decoration: none; +color: #608673; +} + +a:link:hover, a:visited:hover { +color: #DD0000; +} + +a.post_no { +color: #000033; +} + +p.intro a.email span.name { +color: #608673; +} + +p.intro a.email:hover span.name { +color: #DD0000; +} + +h2, div.title, h1 { +color: #800000; +} + +form table tr th { +background: #ded8b7; +} + +div.banner { +background-color: #E04000; +} + +div.post.op hr { +border-color: #608673; +} + +p.intro span.subject { +color: #8a2e2e; +font-weight: 800; +} + +p.intro span.name { +color: #117743; +font-weight: 800; +} + +div.post.reply.highlighted { +background: #dcae9b; +} + +div.post.reply { +background: #e9d1be; +border-color: #dcae9b; +} + +div.ban { +border: 1px solid #B0C2B9; +} + +div.ban h2 { +background: #e9d1be; +color: #608673; +} + +div.pages { +color: #8899AA; +background: #e9d1be; +border-right: 1px solid #8FCCCD; +border-bottom: 1px solid #8FCCCD; +} + +hr { +border-color: #B0C2B9; +} + +div.boardlist { +color: #608673; +} \ No newline at end of file From 6ce78cb1a454e8556580eaf6b253e1111f848ac0 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Tue, 27 Aug 2013 17:27:17 +1000 Subject: [PATCH 27/35] Bugfix: post editing: should be $board here, not $board['uri'] --- inc/mod/pages.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 3305e0c2..105061e9 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -1307,7 +1307,7 @@ function mod_edit_post($board, $edit_raw_html, $postID) { buildIndex(); - rebuildThemes('post', $board['uri']); + rebuildThemes('post', $board); header('Location: ?/' . sprintf($config['board_path'], $board) . $config['dir']['res'] . sprintf($config['file_page'], $post['thread'] ? $post['thread'] : $postID) . '#' . $postID, true, $config['redirect_http']); } else { From a7ac3339def8d07d825d64f33e8ca69c21dd2743 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Wed, 28 Aug 2013 17:00:41 +1000 Subject: [PATCH 28/35] DNS(): Cache NXDOMAIN --- inc/functions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index afebd5d8..6ce0b29e 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -1940,7 +1940,7 @@ function DNS($host) { global $config; if ($config['cache']['enabled'] && ($ip_addr = cache::get('dns_' . $host))) { - return $ip_addr; + return $ip_addr != '?' ? $ip_addr : false; } if (!$config['dns_system']) { @@ -1956,7 +1956,7 @@ function DNS($host) { } if ($config['cache']['enabled']) - cache::set('dns_' . $host, $ip_addr, 3600); + cache::set('dns_' . $host, $ip_addr !== false ? $ip_addr : '?', 3600); return $ip_addr; } From dc8b1948ddb75ac9a620f1146ce2b19627de4222 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Wed, 28 Aug 2013 17:08:56 +1000 Subject: [PATCH 29/35] Performance: Only purge old antispam hashes once per request (at most) --- inc/anti-bot.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/inc/anti-bot.php b/inc/anti-bot.php index fde97096..41e0a1d6 100644 --- a/inc/anti-bot.php +++ b/inc/anti-bot.php @@ -178,11 +178,14 @@ class AntiBot { } function _create_antibot($board, $thread) { - global $config; + global $config, $purged_old_antispam; $antibot = new AntiBot(array($board, $thread)); - query('DELETE FROM ``antispam`` WHERE `expires` < UNIX_TIMESTAMP()') or error(db_error()); + if (!isset($purged_old_antispam)) { + $purged_old_antispam = true; + query('DELETE FROM ``antispam`` WHERE `expires` < UNIX_TIMESTAMP()') or error(db_error()); + } if ($thread) $query = prepare('UPDATE ``antispam`` SET `expires` = UNIX_TIMESTAMP() + :expires WHERE `board` = :board AND `thread` = :thread AND `expires` IS NULL'); From fcb88b16cdf79f7f543f923b3d5fa39bbdbfb239 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Wed, 28 Aug 2013 17:20:29 +1000 Subject: [PATCH 30/35] Bugfix: Corrupt images were not getting removed from /tmp (convert, gm, and gifsicle) --- inc/image.php | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/inc/image.php b/inc/image.php index 8088999d..d3692a39 100644 --- a/inc/image.php +++ b/inc/image.php @@ -314,16 +314,18 @@ class ImageConvert extends ImageBase { $this->destroy(); } - $this->temp = tempnam($config['tmp'], 'imagick'); + $this->temp = tempnam($config['tmp'], 'convert'); $config['thumb_keep_animation_frames'] = (int)$config['thumb_keep_animation_frames']; if ($this->format == 'gif' && ($config['thumb_ext'] == 'gif' || $config['thumb_ext'] == '') && $config['thumb_keep_animation_frames'] > 1) { if ($this->gifsicle) { if (($error = shell_exec("gifsicle -w --unoptimize -O2 --resize {$this->width}x{$this->height} < " . - escapeshellarg($this->src . '') . " \"#0-{$config['thumb_keep_animation_frames']}\" -o " . - escapeshellarg($this->temp))) || !file_exists($this->temp)) + escapeshellarg($this->src . '') . " \"#0-{$config['thumb_keep_animation_frames']}\" -o " . + escapeshellarg($this->temp))) || !file_exists($this->temp)) { + $this->destroy(); error('Failed to resize image!', null, $error); + } } else { if ($config['convert_manual_orient'] && ($this->format == 'jpg' || $this->format == 'jpeg')) $convert_args = str_replace('-auto-orient', ImageConvert::jpeg_exif_orientation($this->src), $config['convert_args']); @@ -338,8 +340,10 @@ class ImageConvert extends ImageBase { escapeshellarg($this->src), $this->width, $this->height, - escapeshellarg($this->temp)))) || !file_exists($this->temp)) + escapeshellarg($this->temp)))) || !file_exists($this->temp)) { + $this->destroy(); error('Failed to resize image!', null, $error); + } if ($size = $this->get_size($this->temp)) { $this->width = $size[0]; $this->height = $size[1]; @@ -359,8 +363,10 @@ class ImageConvert extends ImageBase { escapeshellarg($this->src . '[0]'), $this->width, $this->height, - escapeshellarg($this->temp)))) || !file_exists($this->temp)) + escapeshellarg($this->temp)))) || !file_exists($this->temp)) { + $this->destroy(); error('Failed to resize image!', null, $error); + } if ($size = $this->get_size($this->temp)) { $this->width = $size[0]; $this->height = $size[1]; From 5aaa6887f647a2d16b5db995c434cea4ce98538d Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Wed, 28 Aug 2013 18:02:28 +1000 Subject: [PATCH 31/35] Add `ip` index to post tables for faster ?/IP/ (and flood detection, etc.) --- install.php | 6 +++++- templates/posts.sql | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/install.php b/install.php index 8725df6d..3054bf10 100644 --- a/install.php +++ b/install.php @@ -1,7 +1,7 @@ Date: Wed, 28 Aug 2013 18:30:01 +1000 Subject: [PATCH 32/35] Sitemap theme: Minimum time between generating, and only generate on post-thread and post-delete --- templates/themes/sitemap/info.php | 9 +++++++++ templates/themes/sitemap/theme.php | 9 ++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/templates/themes/sitemap/info.php b/templates/themes/sitemap/info.php index 43b529fa..0ea2ba9d 100644 --- a/templates/themes/sitemap/info.php +++ b/templates/themes/sitemap/info.php @@ -35,6 +35,15 @@ 'default' => 'hourly', 'size' => '20' ); + + $theme['config'][] = Array( + 'title' => 'Minimum time between regenerating', + 'name' => 'regen_time', + 'type' => 'text', + 'comment' => '(in seconds)', + 'default' => '0', + 'size' => '8' + ); $__boards = listBoards(); $__default_boards = Array(); diff --git a/templates/themes/sitemap/theme.php b/templates/themes/sitemap/theme.php index 7e5b1984..1e36422d 100644 --- a/templates/themes/sitemap/theme.php +++ b/templates/themes/sitemap/theme.php @@ -11,9 +11,16 @@ // - post (a post has been made) // - thread (a thread has been made) - if ($action != 'post' && $action != 'post-thread' && $action != 'post-delete') + if ($action != 'post-thread' && $action != 'post-delete') return; + if ($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(); From 10d59e949aece5d4dd5c1b54d9b26300f792a3db Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Wed, 28 Aug 2013 18:39:45 +1000 Subject: [PATCH 33/35] Performance: Add `expires` index to antispm table to make purging old hashes a little quicker --- install.php | 4 +++- install.sql | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/install.php b/install.php index 3054bf10..35c1ecb7 100644 --- a/install.php +++ b/install.php @@ -1,7 +1,7 @@ Date: Wed, 28 Aug 2013 20:09:30 +1000 Subject: [PATCH 34/35] Allow Unix sockets for database connection --- inc/config.php | 8 +++++--- inc/database.php | 12 ++++++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/inc/config.php b/inc/config.php index b6c510a7..5d572010 100644 --- a/inc/config.php +++ b/inc/config.php @@ -77,16 +77,18 @@ // Database driver (http://www.php.net/manual/en/pdo.drivers.php) // Only MySQL is supported by Tinyboard at the moment, sorry. $config['db']['type'] = 'mysql'; - // Hostname or IP address + // Hostname, IP address or Unix socket (prefixed with ":") $config['db']['server'] = 'localhost'; + // Example: Unix socket + // $config['db']['server'] = ':/tmp/mysql.sock'; // Login $config['db']['user'] = ''; $config['db']['password'] = ''; // Tinyboard database $config['db']['database'] = ''; - // Table prefix + // Table prefix (optional) $config['db']['prefix'] = ''; - // Use a persistent connection (experimental) + // Use a persistent connection (experimental; benefits unknown) $config['db']['persistent'] = false; // Anything more to add to the DSN string (eg. port=xxx;foo=bar) $config['db']['dsn'] = ''; diff --git a/inc/database.php b/inc/database.php index 24959d0c..8daa20b4 100644 --- a/inc/database.php +++ b/inc/database.php @@ -43,9 +43,17 @@ class PreparedQueryDebug { function sql_open() { global $pdo, $config; - if ($pdo) return true; + if ($pdo) + return true; - $dsn = $config['db']['type'] . ':host=' . $config['db']['server'] . ';dbname=' . $config['db']['database']; + if (isset($config['db']['server'][0]) && $config['db']['server'][0] == ':') + $unix_socket = substr($config['db']['server'], 1); + else + $unix_socket = false; + + $dsn = $config['db']['type'] . ':' . + ($unix_socket ? 'unix_socket=' . $unix_socket : 'host=' . $config['db']['server']) . + ';dbname=' . $config['db']['database']; if (!empty($config['db']['dsn'])) $dsn .= ';' . $config['db']['dsn']; try { From 7a4e481333c3ab39ea63c2d961a7c05016b12460 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Wed, 28 Aug 2013 22:30:56 +1000 Subject: [PATCH 35/35] Huge bugfix: Old tracked cites where not being purged correctly. Recommend query: "TRUNCATE TABLE `cites`;" to start over --- inc/functions.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 6ce0b29e..e2289ef1 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -997,9 +997,8 @@ function deletePost($id, $error_if_doesnt_exist=true, $rebuild_after=true) { if (isset($tmp_board)) openBoard($tmp_board); - $query = prepare("DELETE FROM ``cites`` WHERE (`target_board` = :board AND `target` = :id) OR (`board` = :board AND `post` = :id)"); + $query = prepare("DELETE FROM ``cites`` WHERE (`target_board` = :board AND `target` = (" . implode(' OR `target` = ', $ids) . ")) OR (`board` = :board AND (`post` = " . implode(' OR `post` = ', $ids) . "))"); $query->bindValue(':board', $board['uri']); - $query->bindValue(':id', $id, PDO::PARAM_INT); $query->execute() or error(db_error($query)); if (isset($rebuild) && $rebuild_after) {