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 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)) 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-config.php b/inc/8chan-mod-config.php new file mode 100644 index 00000000..1c2ed2ac --- /dev/null +++ b/inc/8chan-mod-config.php @@ -0,0 +1,53 @@ + $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; - $config['mod']['noticeboard_post'] = GLOBALVOLUNTEER; - $config['mod']['search'] = GLOBALVOLUNTEER; - $config['mod']['clean_global'] = GLOBALVOLUNTEER; - $config['mod']['view_notes'] = DISABLED; - $config['mod']['create_notes'] = DISABLED; - $config['mod']['edit_config'] = DISABLED; - $config['mod']['debug_recent'] = ADMIN; - $config['mod']['debug_antispam'] = ADMIN; - $config['mod']['noticeboard_post'] = ADMIN; - $config['mod']['modlog'] = GLOBALVOLUNTEER; - $config['mod']['mod_board_log'] = MOD; - $config['mod']['editpost'] = BOARDVOLUNTEER; - $config['mod']['edit_banners'] = MOD; - $config['mod']['edit_flags'] = MOD; - $config['mod']['edit_settings'] = MOD; - $config['mod']['edit_volunteers'] = MOD; - $config['mod']['edit_tags'] = MOD; - $config['mod']['clean'] = BOARDVOLUNTEER; - // new perms - - $config['mod']['ban'] = BOARDVOLUNTEER; - $config['mod']['bandelete'] = BOARDVOLUNTEER; - $config['mod']['unban'] = BOARDVOLUNTEER; - $config['mod']['deletebyip'] = BOARDVOLUNTEER; - $config['mod']['sticky'] = BOARDVOLUNTEER; - $config['mod']['cycle'] = BOARDVOLUNTEER; - $config['mod']['lock'] = BOARDVOLUNTEER; - $config['mod']['postinlocked'] = BOARDVOLUNTEER; - $config['mod']['bumplock'] = BOARDVOLUNTEER; - $config['mod']['view_bumplock'] = BOARDVOLUNTEER; - $config['mod']['bypass_field_disable'] = BOARDVOLUNTEER; - $config['mod']['view_banlist'] = BOARDVOLUNTEER; - $config['mod']['view_banstaff'] = BOARDVOLUNTEER; - $config['mod']['public_ban'] = BOARDVOLUNTEER; - $config['mod']['recent'] = BOARDVOLUNTEER; - $config['mod']['ban_appeals'] = BOARDVOLUNTEER; - $config['mod']['view_ban_appeals'] = BOARDVOLUNTEER; - $config['mod']['view_ban'] = BOARDVOLUNTEER; - $config['mod']['reassign_board'] = GLOBALVOLUNTEER; - $config['mod']['move'] = GLOBALVOLUNTEER; - $config['mod']['shadow_capcode'] = 'Global Volunteer'; - - - $config['mod']['custom_pages']['/tags/(\%b)'] = function ($b) { + function mod_8_tags ($b) { global $board, $config; if (!openBoard($b)) @@ -114,9 +46,9 @@ $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) { + function mod_8_reassign($b) { global $board, $config; if (!openBoard($b)) @@ -147,9 +79,9 @@ 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) { + function mod_8_volunteers($b) { global $board, $config, $pdo; if (!hasPermission($config['mod']['edit_volunteers'], $b)) error($config['error']['noaccess']); @@ -228,9 +160,9 @@ 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) { + function mod_8_flags($b) { global $config, $mod, $board; require_once 'inc/image.php'; if (!hasPermission($config['mod']['edit_flags'], $b)) @@ -341,6 +273,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); } @@ -364,9 +301,9 @@ 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) { + function mod_8_banners($b) { global $config, $mod, $board; require_once 'inc/image.php'; @@ -427,9 +364,9 @@ FLAGS; $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) { + function mod_8_settings($b) { global $config, $mod; //if ($b === 'infinity' && $mod['type'] !== ADMIN) @@ -661,6 +598,7 @@ 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? // be smarter about rebuilds...only some changes really require us to rebuild all threads if ($_config['captcha']['enabled'] != $config['captcha']['enabled'] @@ -683,13 +621,18 @@ EOT; $query->bindValue(':board', $b); $query->execute() or error(db_error($query)); $board = $query->fetchAll()[0]; - - $css = @file_get_contents('stylesheets/board/' . $board['uri'] . '.css'); - + + // 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'); + 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/cache.php b/inc/cache.php index d1200919..852aefa2 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(); diff --git a/inc/config.php b/inc/config.php index b8e7617d..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 @@ -1239,9 +1244,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 diff --git a/inc/functions.php b/inc/functions.php index 8674ecd0..480df77f 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'; @@ -50,15 +52,42 @@ $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 ); + + define_groups(); + + 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 +115,6 @@ function loadConfig() { 'dashboard_links' ); - $config = array(); foreach ($arrays as $key) { $config[$key] = array(); } @@ -96,18 +124,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_' . $boardsuffix ) ) { + $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) { @@ -128,18 +166,13 @@ 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'])) $config['global_message'] = false; 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'] = '/^' . @@ -210,19 +243,26 @@ 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']); } - 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']; @@ -231,11 +271,21 @@ function loadConfig() { if (preg_match('/^\:\:(ffff\:)?(\d+\.\d+\.\d+\.\d+)$/', $__ip, $m)) $_SERVER['REMOTE_ADDR'] = $m[2]; + 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'; @@ -244,13 +294,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', ' $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']); } @@ -347,9 +410,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; @@ -403,6 +479,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)); @@ -412,6 +492,8 @@ function themeSettings($theme) { $settings[$s['name']] = $s['value']; } + Cache::set("theme_settings_".$theme, $settings); + return $settings; } @@ -469,6 +551,11 @@ function openBoard($uri) { $board = getBoardInfo($uri); if ($board) { setupBoard($board); + + if (function_exists('after_open_board')) { + after_open_board(); + } + return true; } return false; @@ -630,6 +717,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']) { @@ -1004,8 +1098,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); @@ -1491,56 +1586,65 @@ function checkMute() { } } -function buildIndex() { +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'] && $config['try_smarter'] && isset($build_pages) && !empty($build_pages) - && !in_array($page, $build_pages) && is_file($filename)) - 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'] && $config['try_smarter'] && isset($build_pages) && !empty($build_pages) - && !in_array($page, $build_pages) && is_file($filename)) + 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); @@ -1553,14 +1657,22 @@ function buildIndex() { } // json api catalog - if ($config['api']['enabled']) { - $json = json_encode($api->translateCatalog($catalog)); - $jsonFilename = $board['dir'] . 'catalog.json'; - file_write($jsonFilename, $json); + if ($config['api']['enabled'] && $global_api != "skip") { + 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']) @@ -2049,51 +2161,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)); - - 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)); + 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)); + } + } + + // 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']) + )); + + // 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); + } + } + 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'] . sprintf($config['file_page50'], $id); + file_unlink($noko50fn); + + file_unlink($board['dir'] . $config['dir']['res'] . sprintf($config['file_page'], $id)); + } else if ($return) { return $body; } else { $noko50fn = $board['dir'] . $config['dir']['res'] . sprintf($config['file_page50'], $id); diff --git a/inc/instance-config.php b/inc/instance-config.php index bc8d63da..1701fc64 100644 --- a/inc/instance-config.php +++ b/inc/instance-config.php @@ -7,9 +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 "8chan-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'; @@ -177,19 +174,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' => '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.' @@ -212,9 +197,14 @@ $config['enable_antibot'] = false; $config['spam']['unicode'] = false; $config['twig_cache'] = false; $config['report_captcha'] = true; + +$config['page_404'] = 'page_404'; + // 8chan specific mod pages -require '8chan-mod-pages.php'; +require '8chan-mod-config.php'; + +// Load instance functions later on +require_once 'instance-functions.php'; // Load database credentials require "secrets.php"; - diff --git a/inc/instance-functions.php b/inc/instance-functions.php new file mode 100644 index 00000000..71e933d1 --- /dev/null +++ b/inc/instance-functions.php @@ -0,0 +1,24 @@ + 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']); + } +} + +function page_404() { + include('404.php'); +} diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 027dd5bf..45cb92ba 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']); } diff --git a/smart_build.php b/smart_build.php new file mode 100644 index 00000000..ac016480 --- /dev/null +++ b/smart_build.php @@ -0,0 +1,200 @@ + $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, $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 == 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); + return 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; + $build_pages = array(-1); + buildIndex(); + return true; +} + +function sb_ukko() { + rebuildTheme("ukko", "post-thread"); + return true; +} + +function sb_catalog($b) { + if (!openBoard($b)) return false; + + rebuildTheme("catalog", "post-thread", $b); + return true; +} + +function sb_recent() { + rebuildTheme("recent", "post-thread"); + return true; +} + +function sb_sitemap() { + rebuildTheme("sitemap", "all"); + 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_slugcheck50'; +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'; +$entrypoints['/sitemap.xml'] = 'sb_sitemap'; + +$reached = false; + +$request = $_SERVER['REQUEST_URI']; +list($request) = explode('?', $request); + +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; + } +} + +function die_404() { global $config; + if (!$config['page_404']) { + header("HTTP/1.1 404 Not Found"); + header("Status: 404 Not Found"); + echo "
Page doesn't exist