forked from GithubBackups/vichan
Merge branch '8n-tech-master'
This commit is contained in:
commit
7523c9bc04
6
.gitignore
vendored
6
.gitignore
vendored
@ -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
|
||||
|
2
404.php
2
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))
|
||||
|
313
board-search.php
Normal file
313
board-search.php
Normal file
@ -0,0 +1,313 @@
|
||||
<?php
|
||||
|
||||
// We want to return a value if we're included.
|
||||
// Otherwise, we will be printing a JSON object-array.
|
||||
$Included = defined("TINYBOARD");
|
||||
if (!$Included) {
|
||||
include "inc/functions.php";
|
||||
}
|
||||
|
||||
$CanViewUnindexed = isset($mod["type"]) && $mod["type"] <= GlobalVolunteer;
|
||||
|
||||
|
||||
/* The expected output of this page is JSON. */
|
||||
$response = array();
|
||||
|
||||
|
||||
/* Determine search parameters from $_GET */
|
||||
$search = array(
|
||||
'lang' => false,
|
||||
'nsfw' => true,
|
||||
'page' => 0,
|
||||
'tags' => false,
|
||||
'time' => ( (int)( time() / 3600 ) * 3600 ) - 3600,
|
||||
'title' => false,
|
||||
|
||||
'index' => count( $_GET ) == 0,
|
||||
);
|
||||
|
||||
// Include NSFW boards?
|
||||
if (isset( $_GET['sfw'] ) && $_GET['sfw'] != "") {
|
||||
$search['nsfw'] = !$_GET['sfw'];
|
||||
}
|
||||
|
||||
// Bringing up more results
|
||||
if (isset( $_GET['page'] ) && $_GET['page'] != "") {
|
||||
$search['page'] = (int) $_GET['page'];
|
||||
|
||||
if ($search['page'] < 0) {
|
||||
$search['page'] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Include what language (if the language is not blank and we recognize it)?
|
||||
if (isset( $_GET['lang'] ) && $_GET['lang'] != "" && isset($config['languages'][$_GET['lang']])) {
|
||||
$search['lang'] = $_GET['lang'];
|
||||
}
|
||||
|
||||
// Include what tag?
|
||||
if (isset( $_GET['tags'] ) && $_GET['tags'] != "") {
|
||||
$search['tags'] = explode( " ", $_GET['tags'] );
|
||||
$search['tags'] = array_splice( $search['tags'], 0, 5 );
|
||||
}
|
||||
|
||||
// What time range?
|
||||
if (isset( $_GET['time'] ) && is_numeric( $_GET['time'] ) ) {
|
||||
$search['time'] = ( (int)( $_GET['time'] / 3600 ) * 3600 );
|
||||
}
|
||||
|
||||
// Include what in the uri / title / subtitle?
|
||||
if (isset( $_GET['title'] ) && $_GET['title'] != "") {
|
||||
$search['title'] = $_GET['title'];
|
||||
}
|
||||
|
||||
/* Search boards */
|
||||
$boards = listBoards();
|
||||
$response['boards'] = array();
|
||||
|
||||
// Loop through our available boards and filter out inapplicable ones based on standard filtering.
|
||||
foreach ($boards as $board) {
|
||||
// Checks we can do without looking at config.
|
||||
if (
|
||||
// Indexed, or we are staff,
|
||||
( $CanViewUnindexed !== true && !$board['indexed'] )
|
||||
// Not filtering NSFW, or board is SFW.
|
||||
|| ( $search['nsfw'] !== true && $board['sfw'] != 1 )
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Are we searching by title?
|
||||
if ($search['title'] !== false) {
|
||||
// This checks each component of the board's identity against our search terms.
|
||||
// The weight determines order.
|
||||
// "left" would match /leftypol/ and /nkvd/ which has /leftypol/ in the title.
|
||||
// /leftypol/ would always appear above it but it would match both.
|
||||
if (strpos("/{$board['uri']}/", $search['title']) !== false) {
|
||||
$board['weight'] = 30;
|
||||
}
|
||||
else if (strpos($board['title'], $search['title']) !== false) {
|
||||
$board['weight'] = 20;
|
||||
}
|
||||
else if (strpos($board['subtitle'], $search['title']) !== false) {
|
||||
$board['weight'] = 10;
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
}
|
||||
|
||||
unset( $boardTitleString );
|
||||
}
|
||||
else {
|
||||
$board['weight'] = 0;
|
||||
}
|
||||
|
||||
// Load board config.
|
||||
$boardConfig = loadBoardConfig( $board['uri'] );
|
||||
|
||||
// Determine language/locale and tags.
|
||||
$boardLang = strtolower( array_slice( explode( "_", $boardConfig['locale'] ?: "" ), 0 )[0] ); // en_US -> en OR en -> en
|
||||
|
||||
// Check against our config search options.
|
||||
if ($search['lang'] !== false && $search['lang'] != $boardLang) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($config['languages'][$boardLang])) {
|
||||
$board['locale'] = $config['languages'][$boardLang];
|
||||
}
|
||||
else {
|
||||
$board['locale'] = $boardLang;
|
||||
}
|
||||
|
||||
$response['boards'][ $board['uri'] ] = $board;
|
||||
}
|
||||
|
||||
unset( $boards );
|
||||
|
||||
/* Tag Fetching */
|
||||
// (We have do this even if we're not filtering by tags so that we know what each board's tags are)
|
||||
|
||||
// Fetch all board tags for our boards.
|
||||
$boardTags = fetchBoardTags( array_keys( $response['boards'] ) );
|
||||
|
||||
// Loop through each board and determine if there are tag matches.
|
||||
foreach ($response['boards'] as $boardUri => &$board) {
|
||||
// If we are filtering by tag and there is no match, remove from the response.
|
||||
if ( $search['tags'] !== false && ( !isset( $boardTags[ $boardUri ] ) || count(array_intersect($search['tags'], $boardTags[ $boardUri ])) !== count($search['tags']) ) ) {
|
||||
unset( $response['boards'][$boardUri] );
|
||||
continue;
|
||||
}
|
||||
// If we aren't filtering / there is a match AND we have tags, set the tags.
|
||||
else if ( isset( $boardTags[ $boardUri ] ) && $boardTags[ $boardUri ] ) {
|
||||
$board['tags'] = $boardTags[ $boardUri ];
|
||||
}
|
||||
// Othrwise, just declare our tag array blank.
|
||||
else {
|
||||
$board['tags'] = array();
|
||||
}
|
||||
|
||||
// Legacy support for API readers.
|
||||
$board['max'] = &$board['posts_total'];
|
||||
}
|
||||
|
||||
unset( $boardTags );
|
||||
|
||||
|
||||
/* Activity Fetching */
|
||||
$boardActivity = fetchBoardActivity( array_keys( $response['boards'] ), $search['time'], true );
|
||||
|
||||
// Loop through each board and record activity to it.
|
||||
// We will also be weighing and building a tag list.
|
||||
foreach ($response['boards'] as $boardUri => &$board) {
|
||||
$board['active'] = 0;
|
||||
$board['pph'] = 0;
|
||||
$board['ppd'] = 0;
|
||||
|
||||
if (isset($boardActivity['active'][ $boardUri ])) {
|
||||
$board['active'] = (int) $boardActivity['active'][ $boardUri ];
|
||||
}
|
||||
if (isset($boardActivity['average'][ $boardUri ])) {
|
||||
$precision = 1;
|
||||
|
||||
$board['pph'] = round( $boardActivity['average'][ $boardUri ], $precision );
|
||||
$board['ppd'] = round( $boardActivity['today'][ $boardUri ], $precision );
|
||||
|
||||
unset( $precision );
|
||||
}
|
||||
}
|
||||
|
||||
// Sort boards by their popularity, then by their total posts.
|
||||
$boardActivityValues = array();
|
||||
$boardTotalPostsValues = array();
|
||||
$boardWeightValues = array();
|
||||
|
||||
foreach ($response['boards'] as $boardUri => &$board) {
|
||||
$boardActivityValues[$boardUri] = (int) $board['active'];
|
||||
$boardTotalPostsValues[$boardUri] = (int) $board['posts_total'];
|
||||
$boardWeightValues[$boardUri] = (int) $board['weight'];
|
||||
}
|
||||
|
||||
array_multisort(
|
||||
$boardWeightValues, SORT_DESC, SORT_NUMERIC, // Sort by weight
|
||||
$boardActivityValues, SORT_DESC, SORT_NUMERIC, // Sort by number of active posters
|
||||
$boardTotalPostsValues, SORT_DESC, SORT_NUMERIC, // Then, sort by total number of posts
|
||||
$response['boards']
|
||||
);
|
||||
|
||||
if (php_sapi_name() == 'cli') {
|
||||
$response['boardsFull'] = $response['boards'];
|
||||
}
|
||||
|
||||
$boardLimit = $search['index'] ? 50 : 100;
|
||||
|
||||
$response['omitted'] = count( $response['boards'] ) - $boardLimit;
|
||||
$response['omitted'] = $response['omitted'] < 0 ? 0 : $response['omitted'];
|
||||
$response['boards'] = array_splice( $response['boards'], $search['page'], $boardLimit );
|
||||
$response['order'] = array_keys( $response['boards'] );
|
||||
|
||||
|
||||
// Loop through the truncated array to compile tags.
|
||||
$response['tags'] = array();
|
||||
$tagUsage = array( 'boards' => array(), 'users' => array() );
|
||||
|
||||
foreach ($response['boards'] as $boardUri => &$board) {
|
||||
if (isset($board['tags']) && count($board['tags']) > 0) {
|
||||
foreach ($board['tags'] as $tag) {
|
||||
if (!isset($tagUsage['boards'][$tag])) {
|
||||
$tagUsage['boards'][$tag] = 0;
|
||||
}
|
||||
if (!isset($tagUsage['users'][$tag])) {
|
||||
$tagUsage['users'][$tag] = 0;
|
||||
}
|
||||
|
||||
$response['tags'][$tag] = true;
|
||||
++$tagUsage['boards'][$tag];
|
||||
$tagUsage['users'][$tag] += $board['active'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the top most popular tags.
|
||||
if (count($response['tags']) > 0) {
|
||||
arsort( $tagUsage['boards'] );
|
||||
arsort( $tagUsage['users'] );
|
||||
|
||||
array_multisort(
|
||||
$tagUsage['boards'], SORT_DESC, SORT_NUMERIC,
|
||||
$tagUsage['users'], SORT_DESC, SORT_NUMERIC,
|
||||
$response['tags']
|
||||
);
|
||||
|
||||
// Get the first n most active tags.
|
||||
$response['tags'] = array_splice( $response['tags'], 0, 100 );
|
||||
$response['tagOrder'] = array_keys( $response['tags'] );
|
||||
$response['tagWeight'] = array();
|
||||
|
||||
$tagsMostUsers = max( $tagUsage['users'] );
|
||||
$tagsLeastUsers = min( $tagUsage['users'] );
|
||||
$tagsAvgUsers = array_sum( $tagUsage['users'] ) / count( $tagUsage['users'] );
|
||||
|
||||
$weightDepartureFurthest = 0;
|
||||
|
||||
foreach ($tagUsage['users'] as $tagUsers) {
|
||||
$weightDeparture = abs( $tagUsers - $tagsAvgUsers );
|
||||
|
||||
if( $weightDeparture > $weightDepartureFurthest ) {
|
||||
$weightDepartureFurthest = $weightDeparture;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($tagUsage['users'] as $tagName => $tagUsers) {
|
||||
if ($weightDepartureFurthest != 0) {
|
||||
$weightDeparture = abs( $tagUsers - $tagsAvgUsers );
|
||||
$response['tagWeight'][$tagName] = 75 + round( 100 * ( $weightDeparture / $weightDepartureFurthest ), 0);
|
||||
}
|
||||
else {
|
||||
$response['tagWeight'][$tagName] = 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Include our interpreted search terms. */
|
||||
$response['search'] = $search;
|
||||
|
||||
/* (Please) Respond */
|
||||
if (!$Included) {
|
||||
$json = json_encode( $response );
|
||||
|
||||
// Error Handling
|
||||
switch (json_last_error()) {
|
||||
case JSON_ERROR_NONE:
|
||||
$jsonError = false;
|
||||
break;
|
||||
case JSON_ERROR_DEPTH:
|
||||
$jsonError = 'Maximum stack depth exceeded';
|
||||
break;
|
||||
case JSON_ERROR_STATE_MISMATCH:
|
||||
$jsonError = 'Underflow or the modes mismatch';
|
||||
break;
|
||||
case JSON_ERROR_CTRL_CHAR:
|
||||
$jsonError = 'Unexpected control character found';
|
||||
break;
|
||||
case JSON_ERROR_SYNTAX:
|
||||
$jsonError = 'Syntax error, malformed JSON';
|
||||
break;
|
||||
case JSON_ERROR_UTF8:
|
||||
$jsonError = 'Malformed UTF-8 characters, possibly incorrectly encoded';
|
||||
break;
|
||||
default:
|
||||
$jsonError = 'Unknown error';
|
||||
break;
|
||||
}
|
||||
|
||||
if ($jsonError) {
|
||||
$json = "{\"error\":\"{$jsonError}\"}";
|
||||
}
|
||||
|
||||
// Successful output
|
||||
echo $json;
|
||||
}
|
||||
else {
|
||||
return $response;
|
||||
}
|
217
boards.php
217
boards.php
@ -1,152 +1,109 @@
|
||||
<?php
|
||||
|
||||
include "inc/functions.php";
|
||||
include "inc/countries.php";
|
||||
|
||||
$admin = isset($mod["type"]) && $mod["type"]<=30;
|
||||
$admin = isset($mod["type"]) && $mod["type"]<=30;
|
||||
$founding_date = "October 23, 2013";
|
||||
|
||||
if (php_sapi_name() == 'fpm-fcgi' && !$admin) {
|
||||
if (php_sapi_name() == 'fpm-fcgi' && !$admin && count($_GET) == 0) {
|
||||
error('Cannot be run directly.');
|
||||
}
|
||||
$boards = listBoards();
|
||||
$all_tags = array();
|
||||
$total_posts_hour = 0;
|
||||
$total_posts = 0;
|
||||
$write_maxes = false;
|
||||
|
||||
function to_tag($str) {
|
||||
$str = trim($str);
|
||||
$str = strtolower($str);
|
||||
$str = str_replace(['_', ' '], '-', $str);
|
||||
return $str;
|
||||
}
|
||||
/* Build parameters for page */
|
||||
$searchJson = include "board-search.php";
|
||||
$boards = array();
|
||||
$tags = array();
|
||||
|
||||
if (!file_exists('maxes.txt') || filemtime('maxes.txt') < (time() - (60*60))) {
|
||||
$fp = fopen('maxes.txt', 'w+');
|
||||
$write_maxes = true;
|
||||
}
|
||||
|
||||
foreach ($boards as $i => $board) {
|
||||
$query = prepare(sprintf("
|
||||
SELECT IFNULL(MAX(id),0) max,
|
||||
(SELECT COUNT(*) FROM ``posts_%s`` WHERE FROM_UNIXTIME(time) > DATE_SUB(NOW(), INTERVAL 1 HOUR)) pph,
|
||||
(SELECT COUNT(DISTINCT ip) FROM ``posts_%s`` WHERE FROM_UNIXTIME(time) > DATE_SUB(NOW(), INTERVAL 3 DAY)) uniq_ip
|
||||
FROM ``posts_%s``
|
||||
", $board['uri'], $board['uri'], $board['uri'], $board['uri'], $board['uri']));
|
||||
$query->execute() or error(db_error($query));
|
||||
$r = $query->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
$tquery = prepare("SELECT `tag` FROM ``board_tags`` WHERE `uri` = :uri");
|
||||
$tquery->execute([":uri" => $board['uri']]) or error(db_error($tquery));
|
||||
$r2 = $tquery->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$tags = array();
|
||||
if ($r2) {
|
||||
foreach ($r2 as $ii => $t) {
|
||||
$tag=to_tag($t['tag']);
|
||||
$tags[] = $tag;
|
||||
if (!isset($all_tags[$tag])) {
|
||||
$all_tags[$tag] = (int)$r['uniq_ip'];
|
||||
} else {
|
||||
$all_tags[$tag] += $r['uniq_ip'];
|
||||
}
|
||||
}
|
||||
if (count($searchJson)) {
|
||||
if (isset($searchJson['boards'])) {
|
||||
$boards = $searchJson['boards'];
|
||||
}
|
||||
|
||||
$pph = $r['pph'];
|
||||
|
||||
$total_posts_hour += $pph;
|
||||
$total_posts += $r['max'];
|
||||
|
||||
$boards[$i]['pph'] = $pph;
|
||||
$boards[$i]['ppd'] = $pph*24;
|
||||
$boards[$i]['max'] = $r['max'];
|
||||
$boards[$i]['uniq_ip'] = $r['uniq_ip'];
|
||||
$boards[$i]['tags'] = $tags;
|
||||
|
||||
if ($write_maxes) fwrite($fp, $board['uri'] . ':' . $boards[$i]['max'] . "\n");
|
||||
}
|
||||
if ($write_maxes) fclose($fp);
|
||||
|
||||
usort($boards,
|
||||
function ($a, $b) {
|
||||
$x = $b['uniq_ip'] - $a['uniq_ip'];
|
||||
if ($x) { return $x;
|
||||
//} else { return strcmp($a['uri'], $b['uri']); }
|
||||
} else { return $b['max'] - $a['max']; }
|
||||
});
|
||||
|
||||
$hidden_boards_total = 0;
|
||||
$rows = array();
|
||||
foreach ($boards as $i => &$board) {
|
||||
$board_config = @file_get_contents($board['uri'].'/config.php');
|
||||
$boardCONFIG = array();
|
||||
if ($board_config && $board['uri'] !== 'int') {
|
||||
$board_config = str_replace('$config', '$boardCONFIG', $board_config);
|
||||
$board_config = str_replace('<?php', '', $board_config);
|
||||
@eval($board_config);
|
||||
}
|
||||
$showboard = $board['indexed'];
|
||||
$locale = isset($boardCONFIG['locale'])?$boardCONFIG['locale']:'en';
|
||||
|
||||
$board['title'] = utf8tohtml($board['title']);
|
||||
$locale_arr = explode('_', $locale);
|
||||
$locale_short = isset($locale_arr[1]) ? strtolower($locale_arr[1]) : strtolower($locale_arr[0]);
|
||||
$locale_short = str_replace('.utf-8', '', $locale_short);
|
||||
$country = get_country($locale_short);
|
||||
if ($board['uri'] === 'int') {$locale_short = 'eo'; $locale = 'eo'; $country = 'Esperanto';}
|
||||
|
||||
$board['img'] = "<img class=\"flag flag-$locale_short\" src=\"/static/blank.gif\" style=\"width:16px;height:11px;\" alt=\"$country\" title=\"$country\">";
|
||||
|
||||
if ($showboard || $admin) {
|
||||
if (!$showboard) {
|
||||
$lock = ' <i class="fa fa-lock" title="No index"></i>';
|
||||
} else {
|
||||
$lock = '';
|
||||
}
|
||||
$board['ago'] = human_time_diff(strtotime($board['time']));
|
||||
} else {
|
||||
unset($boards[$i]);
|
||||
$hidden_boards_total += 1;
|
||||
if (isset($searchJson['tagWeight'])) {
|
||||
$tags = $searchJson['tagWeight'];
|
||||
}
|
||||
}
|
||||
|
||||
$n_boards = sizeof($boards);
|
||||
$t_boards = $hidden_boards_total + $n_boards;
|
||||
$boardQuery = prepare("SELECT COUNT(1) AS 'boards_total', SUM(indexed) AS 'boards_public', SUM(posts_total) AS 'posts_total' FROM ``boards``");
|
||||
$boardQuery->execute() or error(db_error($tagQuery));
|
||||
$boardResult = $boardQuery->fetchAll(PDO::FETCH_ASSOC)[0];
|
||||
|
||||
$boards = array_values($boards);
|
||||
arsort($all_tags);
|
||||
$boards_total = number_format( $boardResult['boards_total'], 0 );
|
||||
$boards_public = number_format( $boardResult['boards_public'], 0 );
|
||||
$boards_hidden = number_format( $boardResult['boards_total'] - $boardResult['boards_public'], 0 );
|
||||
$boards_omitted = (int) $searchJson['omitted'];
|
||||
|
||||
$config['additional_javascript'] = array('js/jquery.min.js', 'js/jquery.tablesorter.min.js');
|
||||
$body = Element("8chan/boards-tags.html", array("config" => $config, "n_boards" => $n_boards, "t_boards" => $t_boards, "hidden_boards_total" => $hidden_boards_total, "total_posts" => $total_posts, "total_posts_hour" => $total_posts_hour, "boards" => $boards, "last_update" => date('r'), "uptime_p" => shell_exec('uptime -p'), 'tags' => $all_tags, 'top2k' => false));
|
||||
$posts_hour = number_format( fetchBoardActivity(), 0 );
|
||||
$posts_total = number_format( $boardResult['posts_total'], 0 );
|
||||
|
||||
$html = Element("page.html", array("config" => $config, "body" => $body, "title" => "Boards on ∞chan"));
|
||||
$boards_top2k = $boards;
|
||||
array_splice($boards_top2k, 2000);
|
||||
$boards_top2k = array_values($boards_top2k);
|
||||
$body = Element("8chan/boards-tags.html", array("config" => $config, "n_boards" => $n_boards, "t_boards" => $t_boards, "hidden_boards_total" => $hidden_boards_total, "total_posts" => $total_posts, "total_posts_hour" => $total_posts_hour, "boards" => $boards_top2k, "last_update" => date('r'), "uptime_p" => shell_exec('uptime -p'), 'tags' => $all_tags, 'top2k' => true));
|
||||
$html_top2k = Element("page.html", array("config" => $config, "body" => $body, "title" => "Boards on ∞chan"));
|
||||
// This incredibly stupid looking chunk of code builds a query string using existing information.
|
||||
// It's used to make clickable tags for users without JavaScript for graceful degredation.
|
||||
// Because of how it orders tags, what you end up with is a prefix that always ends in tags=x+
|
||||
// ?tags= or ?sfw=1&tags= or ?title=foo&tags=bar+ - etc
|
||||
$tagQueryGet = $_GET;
|
||||
$tagQueryTags = isset($tagQueryGet['tags']) ? $tagQueryGet['tags'] : "";
|
||||
unset($tagQueryGet['tags']);
|
||||
$tagQueryGet['tags'] = $tagQueryTags;
|
||||
$tag_query = "?" . http_build_query( $tagQueryGet ) . ($tagQueryTags != "" ? "+" : "");
|
||||
|
||||
if ($admin) {
|
||||
echo $html;
|
||||
} else {
|
||||
foreach ($boards as $i => &$b) { unset($b['img']); }
|
||||
file_write("boards.json", json_encode($boards));
|
||||
file_write("tags.json", json_encode($all_tags));
|
||||
foreach ($boards as $i => $b) {
|
||||
if (in_array($b['uri'], $config['no_top_bar_boards'])) {
|
||||
unset($boards[$i]);
|
||||
}
|
||||
unset($boards[$i]['img']);
|
||||
}
|
||||
/* Create and distribute page */
|
||||
// buildJavascript();
|
||||
|
||||
array_splice($boards, 48);
|
||||
$boardsHTML = Element("8chan/boards-table.html", array(
|
||||
"config" => $config,
|
||||
"boards" => $boards,
|
||||
"tag_query" => $tag_query,
|
||||
|
||||
)
|
||||
);
|
||||
|
||||
$boards = array_values($boards);
|
||||
$tagsHTML = Element("8chan/boards-tags.html", array(
|
||||
"config" => $config,
|
||||
"tags" => $tags,
|
||||
"tag_query" => $tag_query,
|
||||
|
||||
)
|
||||
);
|
||||
|
||||
file_write("boards-top20.json", json_encode($boards));
|
||||
file_write("boards.html", $html_top2k);
|
||||
file_write("boards_full.html", $html);
|
||||
echo 'Done';
|
||||
$searchHTML = Element("8chan/boards-search.html", array(
|
||||
"config" => $config,
|
||||
"boards" => $boards,
|
||||
"tags" => $tags,
|
||||
"search" => $searchJson['search'],
|
||||
"languages" => $config['languages'],
|
||||
|
||||
"boards_total" => $boards_total,
|
||||
"boards_public" => $boards_public,
|
||||
"boards_hidden" => $boards_hidden,
|
||||
"boards_omitted" => $boards_omitted,
|
||||
|
||||
"posts_hour" => $posts_hour,
|
||||
"posts_total" => $posts_total,
|
||||
|
||||
"founding_date" => $founding_date,
|
||||
"page_updated" => date('r'),
|
||||
"uptime" => shell_exec('uptime -p'),
|
||||
|
||||
"html_boards" => $boardsHTML,
|
||||
"html_tags" => $tagsHTML
|
||||
)
|
||||
);
|
||||
|
||||
$pageHTML = Element("page.html", array(
|
||||
"title" => _("Boardlist"),
|
||||
"config" => $config,
|
||||
"body" => $searchHTML
|
||||
)
|
||||
);
|
||||
|
||||
// We only want to cache if this is not a dynamic form request.
|
||||
// Otherwise, our information will be skewed by the search criteria.
|
||||
if (php_sapi_name() == 'cli') {
|
||||
// Preserves the JSON output format of [{board},{board}].
|
||||
$nonAssociativeBoardList = array_values($response['boardsFull']);
|
||||
|
||||
file_write("boards.html", $pageHTML);
|
||||
file_write("boards.json", json_encode($nonAssociativeBoardList));
|
||||
file_write("boards-top20.json", json_encode(array_splice($nonAssociativeBoardList, 0, 48)));
|
||||
}
|
||||
|
||||
echo $pageHTML;
|
@ -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;
|
||||
}
|
||||
|
53
inc/8chan-mod-config.php
Normal file
53
inc/8chan-mod-config.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
$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';
|
||||
|
||||
// Mod pages assignment
|
||||
$config['mod']['custom_pages']['/tags/(\%b)'] = '8_tags';
|
||||
$config['mod']['custom_pages']['/reassign/(\%b)'] = '8_reassign';
|
||||
$config['mod']['custom_pages']['/volunteers/(\%b)'] = '8_volunteers';
|
||||
$config['mod']['custom_pages']['/flags/(\%b)'] = '8_flags';
|
||||
$config['mod']['custom_pages']['/banners/(\%b)'] = '8_banners';
|
||||
$config['mod']['custom_pages']['/settings/(\%b)'] = '8_settings';
|
@ -1,73 +1,5 @@
|
||||
<?php
|
||||
if (!function_exists('is_billion_laughs')){
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
$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']));
|
||||
};
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -348,6 +348,8 @@ function embed_html($link) {
|
||||
|
||||
|
||||
class Post {
|
||||
public $clean;
|
||||
|
||||
public function __construct($post, $root=null, $mod=false) {
|
||||
global $config;
|
||||
if (!isset($root))
|
||||
|
@ -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', '<?php '.
|
||||
'$config = array();'.
|
||||
'$config[\'cache\'] = '.var_export($config['cache'], true).';'.
|
||||
'$config[\'cache_config\'] = true;'.
|
||||
'$config[\'debug\'] = '.var_export($config['debug'], true).';'.
|
||||
'require_once(\'inc/cache.php\');'
|
||||
);
|
||||
|
||||
$config['cache_config_loaded'] = true;
|
||||
|
||||
Cache::set('config_'.$boardsuffix, $config);
|
||||
Cache::set('events_'.$boardsuffix, $events);
|
||||
}
|
||||
|
||||
if ($config['debug']) {
|
||||
if (!isset($debug)) {
|
||||
@ -327,8 +386,12 @@ function verbose_error_handler($errno, $errstr, $errfile, $errline) {
|
||||
function define_groups() {
|
||||
global $config;
|
||||
|
||||
foreach ($config['mod']['groups'] as $group_value => $group_name)
|
||||
defined($group_name) or define($group_name, $group_value, true);
|
||||
foreach ($config['mod']['groups'] as $group_value => $group_name) {
|
||||
$group_name = strtoupper($group_name);
|
||||
if(!defined($group_name)) {
|
||||
define($group_name, $group_value, true);
|
||||
}
|
||||
}
|
||||
|
||||
ksort($config['mod']['groups']);
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -434,9 +516,9 @@ function setupBoard($array) {
|
||||
$board = array(
|
||||
'uri' => $array['uri'],
|
||||
'title' => $array['title'],
|
||||
'subtitle' => $array['subtitle'],
|
||||
'indexed' => $array['indexed'],
|
||||
'public_logs' => $array['public_logs']
|
||||
'subtitle' => isset($array['subtitle']) ? $array['subtitle'] : "",
|
||||
'indexed' => isset($array['indexed']) ? $array['indexed'] : true,
|
||||
'public_logs' => isset($array['public_logs']) ? $array['public_logs'] : true,
|
||||
);
|
||||
|
||||
// older versions
|
||||
@ -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;
|
||||
@ -541,41 +628,47 @@ function purge($uri) {
|
||||
|
||||
function file_write($path, $data, $simple = false, $skip_purge = false) {
|
||||
global $config, $debug;
|
||||
|
||||
|
||||
if (preg_match('/^remote:\/\/(.+)\:(.+)$/', $path, $m)) {
|
||||
if (isset($config['remote'][$m[1]])) {
|
||||
require_once 'inc/remote.php';
|
||||
|
||||
|
||||
$remote = new Remote($config['remote'][$m[1]]);
|
||||
$remote->write($data, $m[2]);
|
||||
return;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
error('Invalid remote server: ' . $m[1]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$fp = dio_open($path, O_WRONLY | O_CREAT, 0644))
|
||||
else {
|
||||
// This will convert a local, relative path like "b/index.html" to a full path.
|
||||
// dio_open does not work with relative paths on Windows machines.
|
||||
$path = realpath(dirname($path)) . DIRECTORY_SEPARATOR . basename($path);
|
||||
}
|
||||
|
||||
if (!$fp = dio_open( $path, O_WRONLY | O_CREAT | O_TRUNC, 0644)) {
|
||||
error('Unable to open file for writing: ' . $path);
|
||||
|
||||
}
|
||||
|
||||
// File locking
|
||||
if (dio_fcntl($fp, F_SETLKW, array('type' => F_WRLCK)) === -1) {
|
||||
if (function_exists("dio_fcntl") && dio_fcntl($fp, F_SETLKW, array('type' => F_WRLCK)) === -1) {
|
||||
error('Unable to lock file: ' . $path);
|
||||
}
|
||||
|
||||
// Truncate file
|
||||
if (!dio_truncate($fp, 0))
|
||||
error('Unable to truncate file: ' . $path);
|
||||
|
||||
|
||||
// Write data
|
||||
if (($bytes = dio_write($fp, $data)) === false)
|
||||
if (($bytes = dio_write($fp, $data)) === false) {
|
||||
error('Unable to write to file: ' . $path);
|
||||
|
||||
}
|
||||
|
||||
// Unlock
|
||||
dio_fcntl($fp, F_SETLK, array('type' => F_UNLCK));
|
||||
|
||||
if (function_exists("dio_fcntl")) {
|
||||
dio_fcntl($fp, F_SETLK, array('type' => F_UNLCK));
|
||||
}
|
||||
|
||||
// Close
|
||||
dio_close($fp);
|
||||
|
||||
|
||||
/**
|
||||
* Create gzipped file.
|
||||
*
|
||||
@ -630,6 +723,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']) {
|
||||
@ -686,9 +786,24 @@ function listBoards($just_uri = false, $indexed_only = false) {
|
||||
return $boards;
|
||||
|
||||
if (!$just_uri) {
|
||||
$query = query("SELECT ``boards``.`uri` uri, ``boards``.`title` title, ``boards``.`subtitle` subtitle, ``board_create``.`time` time, ``boards``.`indexed` indexed, ``boards``.`sfw` sfw FROM ``boards``" . ( $indexed_only ? " WHERE `indexed` = 1 " : "" ) . "LEFT JOIN ``board_create`` ON ``boards``.`uri` = ``board_create``.`uri` ORDER BY ``boards``.`uri`") or error(db_error());
|
||||
$query = query(
|
||||
"SELECT
|
||||
``boards``.`uri` uri,
|
||||
``boards``.`title` title,
|
||||
``boards``.`subtitle` subtitle,
|
||||
``board_create``.`time` time,
|
||||
``boards``.`indexed` indexed,
|
||||
``boards``.`sfw` sfw,
|
||||
``boards``.`posts_total` posts_total
|
||||
FROM ``boards``
|
||||
LEFT JOIN ``board_create``
|
||||
ON ``boards``.`uri` = ``board_create``.`uri`" .
|
||||
( $indexed_only ? " WHERE `indexed` = 1 " : "" ) .
|
||||
"ORDER BY ``boards``.`uri`") or error(db_error());
|
||||
|
||||
$boards = $query->fetchAll(PDO::FETCH_ASSOC);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
$boards = array();
|
||||
$query = query("SELECT `uri` FROM ``boards``" . ( $indexed_only ? " WHERE `indexed` = 1" : "" ) . " ORDER BY ``boards``.`uri`") or error(db_error());
|
||||
while (true) {
|
||||
@ -704,6 +819,130 @@ function listBoards($just_uri = false, $indexed_only = false) {
|
||||
return $boards;
|
||||
}
|
||||
|
||||
function loadBoardConfig( $uri ) {
|
||||
$config = array(
|
||||
"locale" => "en_US",
|
||||
);
|
||||
$configPath = "./{$uri}/config.php";
|
||||
|
||||
if (file_exists( $configPath ) && is_readable( $configPath )) {
|
||||
include( $configPath );
|
||||
}
|
||||
|
||||
// **DO NOT** use $config outside of this local scope.
|
||||
// It's used by our global config array.
|
||||
return $config;
|
||||
}
|
||||
|
||||
function fetchBoardActivity( array $uris = array(), $forTime = false, $detailed = false ) {
|
||||
global $config;
|
||||
|
||||
// Set our search time for now if we didn't pass one.
|
||||
if (!is_integer($forTime)) {
|
||||
$forTime = time();
|
||||
}
|
||||
|
||||
// Get the last hour for this timestamp.
|
||||
$nowHour = ( (int)( time() / 3600 ) * 3600 );
|
||||
// Get the hour before. This is what we actually use for pulling data.
|
||||
$forHour = ( (int)( $forTime / 3600 ) * 3600 ) - 3600;
|
||||
// Get the hour from yesterday to calculate posts per day.
|
||||
$yesterHour = $forHour - ( 3600 * 23 );
|
||||
|
||||
$boardActivity = array(
|
||||
'active' => array(),
|
||||
'today' => array(),
|
||||
'average' => array(),
|
||||
);
|
||||
|
||||
// Query for stats for these boards.
|
||||
if (count($uris)) {
|
||||
$uriSearch = "`stat_uri` IN (\"" . implode( (array) $uris, "\",\"" ) . "\") AND ";
|
||||
}
|
||||
else {
|
||||
$uriSearch = "";
|
||||
}
|
||||
|
||||
if ($detailed === true) {
|
||||
$bsQuery = prepare("SELECT `stat_uri`, `stat_hour`, `post_count`, `author_ip_array` FROM ``board_stats`` WHERE {$uriSearch} ( `stat_hour` <= :hour AND `stat_hour` >= :hoursago )");
|
||||
$bsQuery->bindValue(':hour', $forHour, PDO::PARAM_INT);
|
||||
$bsQuery->bindValue(':hoursago', $forHour - ( 3600 * 72 ), PDO::PARAM_INT);
|
||||
$bsQuery->execute() or error(db_error($bsQuery));
|
||||
$bsResult = $bsQuery->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
|
||||
// Format the results.
|
||||
foreach ($bsResult as $bsRow) {
|
||||
// Do we need to define the arrays for this URI?
|
||||
if (!isset($boardActivity['active'][$bsRow['stat_uri']])) {
|
||||
if ($bsRow['stat_hour'] <= $forHour && $bsRow['stat_hour'] >= $yesterHour) {
|
||||
$boardActivity['today'][$bsRow['stat_uri']] = $bsRow['post_count'];
|
||||
}
|
||||
else {
|
||||
$boardActivity['today'][$bsRow['stat_uri']] = 0;
|
||||
}
|
||||
|
||||
$boardActivity['active'][$bsRow['stat_uri']] = unserialize( $bsRow['author_ip_array'] );
|
||||
$boardActivity['average'][$bsRow['stat_uri']] = $bsRow['post_count'];
|
||||
}
|
||||
else {
|
||||
if ($bsRow['stat_hour'] <= $forHour && $bsRow['stat_hour'] >= $yesterHour) {
|
||||
$boardActivity['today'][$bsRow['stat_uri']] += $bsRow['post_count'];
|
||||
}
|
||||
|
||||
$boardActivity['active'][$bsRow['stat_uri']] = array_merge( $boardActivity['active'][$bsRow['stat_uri']], unserialize( $bsRow['author_ip_array'] ) );
|
||||
$boardActivity['average'][$bsRow['stat_uri']] += $bsRow['post_count'];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($boardActivity['active'] as &$activity) {
|
||||
$activity = count( array_unique( $activity ) );
|
||||
}
|
||||
foreach ($boardActivity['average'] as &$activity) {
|
||||
$activity /= 72;
|
||||
}
|
||||
}
|
||||
// Simple return.
|
||||
else {
|
||||
$bsQuery = prepare("SELECT SUM(`post_count`) AS `post_count` FROM ``board_stats`` WHERE {$uriSearch} ( `stat_hour` = :hour )");
|
||||
$bsQuery->bindValue(':hour', $forHour, PDO::PARAM_INT);
|
||||
$bsQuery->execute() or error(db_error($bsQuery));
|
||||
$bsResult = $bsQuery->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$boardActivity = $bsResult[0]['post_count'];
|
||||
}
|
||||
|
||||
return $boardActivity;
|
||||
}
|
||||
|
||||
function fetchBoardTags( $uris ) {
|
||||
global $config;
|
||||
|
||||
$boardTags = array();
|
||||
$uris = "\"" . implode( (array) $uris, "\",\"" ) . "\"";
|
||||
|
||||
$tagQuery = prepare("SELECT * FROM ``board_tags`` WHERE `uri` IN ({$uris})");
|
||||
$tagQuery->execute() or error(db_error($tagQuery));
|
||||
$tagResult = $tagQuery->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($tagResult) {
|
||||
foreach ($tagResult as $tagRow) {
|
||||
$tag = $tagRow['tag'];
|
||||
$tag = trim($tag);
|
||||
$tag = strtolower($tag);
|
||||
$tag = str_replace(['_', ' '], '-', $tag);
|
||||
|
||||
if (!isset($boardTags[ $tagRow['uri'] ])) {
|
||||
$boardTags[ $tagRow['uri'] ] = array();
|
||||
}
|
||||
|
||||
$boardTags[ $tagRow['uri'] ][] = htmlentities( utf8_encode( $tag ) );
|
||||
}
|
||||
}
|
||||
|
||||
return $boardTags;
|
||||
}
|
||||
|
||||
function until($timestamp) {
|
||||
$difference = $timestamp - time();
|
||||
switch(TRUE){
|
||||
@ -916,70 +1155,70 @@ function insertFloodPost(array $post) {
|
||||
function post(array $post) {
|
||||
global $pdo, $board;
|
||||
$query = prepare(sprintf("INSERT INTO ``posts_%s`` VALUES ( NULL, :thread, :subject, :email, :name, :trip, :capcode, :body, :body_nomarkup, :time, :time, :files, :num_files, :filehash, :password, :ip, :sticky, :locked, :cycle, 0, :embed, NULL)", $board['uri']));
|
||||
|
||||
|
||||
// Basic stuff
|
||||
if (!empty($post['subject'])) {
|
||||
$query->bindValue(':subject', $post['subject']);
|
||||
} else {
|
||||
$query->bindValue(':subject', null, PDO::PARAM_NULL);
|
||||
}
|
||||
|
||||
|
||||
if (!empty($post['email'])) {
|
||||
$query->bindValue(':email', $post['email']);
|
||||
} else {
|
||||
$query->bindValue(':email', null, PDO::PARAM_NULL);
|
||||
}
|
||||
|
||||
|
||||
if (!empty($post['trip'])) {
|
||||
$query->bindValue(':trip', $post['trip']);
|
||||
} else {
|
||||
$query->bindValue(':trip', null, PDO::PARAM_NULL);
|
||||
}
|
||||
|
||||
|
||||
$query->bindValue(':name', $post['name']);
|
||||
$query->bindValue(':body', $post['body']);
|
||||
$query->bindValue(':body_nomarkup', $post['body_nomarkup']);
|
||||
$query->bindValue(':time', isset($post['time']) ? $post['time'] : time(), PDO::PARAM_INT);
|
||||
$query->bindValue(':password', $post['password']);
|
||||
$query->bindValue(':password', $post['password']);
|
||||
$query->bindValue(':ip', isset($post['ip']) ? $post['ip'] : $_SERVER['REMOTE_ADDR']);
|
||||
|
||||
|
||||
if ($post['op'] && $post['mod'] && isset($post['sticky']) && $post['sticky']) {
|
||||
$query->bindValue(':sticky', true, PDO::PARAM_INT);
|
||||
} else {
|
||||
$query->bindValue(':sticky', false, PDO::PARAM_INT);
|
||||
}
|
||||
|
||||
|
||||
if ($post['op'] && $post['mod'] && isset($post['locked']) && $post['locked']) {
|
||||
$query->bindValue(':locked', true, PDO::PARAM_INT);
|
||||
} else {
|
||||
$query->bindValue(':locked', false, PDO::PARAM_INT);
|
||||
}
|
||||
|
||||
|
||||
if ($post['op'] && $post['mod'] && isset($post['cycle']) && $post['cycle']) {
|
||||
$query->bindValue(':cycle', true, PDO::PARAM_INT);
|
||||
} else {
|
||||
$query->bindValue(':cycle', false, PDO::PARAM_INT);
|
||||
}
|
||||
|
||||
|
||||
if ($post['mod'] && isset($post['capcode']) && $post['capcode']) {
|
||||
$query->bindValue(':capcode', $post['capcode'], PDO::PARAM_INT);
|
||||
} else {
|
||||
$query->bindValue(':capcode', null, PDO::PARAM_NULL);
|
||||
}
|
||||
|
||||
|
||||
if (!empty($post['embed'])) {
|
||||
$query->bindValue(':embed', $post['embed']);
|
||||
} else {
|
||||
$query->bindValue(':embed', null, PDO::PARAM_NULL);
|
||||
}
|
||||
|
||||
|
||||
if ($post['op']) {
|
||||
// No parent thread, image
|
||||
$query->bindValue(':thread', null, PDO::PARAM_NULL);
|
||||
} else {
|
||||
$query->bindValue(':thread', $post['thread'], PDO::PARAM_INT);
|
||||
}
|
||||
|
||||
|
||||
if ($post['has_file']) {
|
||||
$query->bindValue(':files', json_encode($post['files']));
|
||||
$query->bindValue(':num_files', $post['num_files']);
|
||||
@ -989,12 +1228,12 @@ function post(array $post) {
|
||||
$query->bindValue(':num_files', 0);
|
||||
$query->bindValue(':filehash', null, PDO::PARAM_NULL);
|
||||
}
|
||||
|
||||
|
||||
if (!$query->execute()) {
|
||||
undoImage($post);
|
||||
error(db_error($query));
|
||||
}
|
||||
|
||||
|
||||
return $pdo->lastInsertId();
|
||||
}
|
||||
|
||||
@ -1004,8 +1243,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);
|
||||
@ -1287,6 +1527,65 @@ function index($page, $mod=false) {
|
||||
);
|
||||
}
|
||||
|
||||
// Handle statistic tracking for a new post.
|
||||
function updateStatisticsForPost( $post, $new = true ) {
|
||||
$postIp = isset($post['ip']) ? $post['ip'] : $_SERVER['REMOTE_ADDR'];
|
||||
$postUri = $post['board'];
|
||||
$postTime = (int)( $post['time'] / 3600 ) * 3600;
|
||||
|
||||
$bsQuery = prepare("SELECT * FROM ``board_stats`` WHERE `stat_uri` = :uri AND `stat_hour` = :hour");
|
||||
$bsQuery->bindValue(':uri', $postUri);
|
||||
$bsQuery->bindValue(':hour', $postTime, PDO::PARAM_INT);
|
||||
$bsQuery->execute() or error(db_error($bsQuery));
|
||||
$bsResult = $bsQuery->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Flesh out the new stats row.
|
||||
$boardStats = array();
|
||||
|
||||
// If we already have a row, we're going to be adding this post to it.
|
||||
if (count($bsResult)) {
|
||||
$boardStats = $bsResult[0];
|
||||
$boardStats['stat_uri'] = $postUri;
|
||||
$boardStats['stat_hour'] = $postTime;
|
||||
$boardStats['post_id_array'] = unserialize( $boardStats['post_id_array'] );
|
||||
$boardStats['author_ip_array'] = unserialize( $boardStats['author_ip_array'] );
|
||||
|
||||
++$boardStats['post_count'];
|
||||
$boardStats['post_id_array'][] = (int) $post['id'];
|
||||
$boardStats['author_ip_array'][] = less_ip( $postIp );
|
||||
$boardStats['author_ip_array'] = array_unique( $boardStats['author_ip_array'] );
|
||||
}
|
||||
// If this a new row, we're building the stat to only reflect this first post.
|
||||
else {
|
||||
$boardStats['stat_uri'] = $postUri;
|
||||
$boardStats['stat_hour'] = $postTime;
|
||||
$boardStats['post_count'] = 1;
|
||||
$boardStats['post_id_array'] = array( (int) $post['id'] );
|
||||
$boardStats['author_ip_count'] = 1;
|
||||
$boardStats['author_ip_array'] = array( less_ip( $postIp ) );
|
||||
}
|
||||
|
||||
// Cleanly serialize our array for insertion.
|
||||
$boardStats['post_id_array'] = str_replace( "\"", "\\\"", serialize( $boardStats['post_id_array'] ) );
|
||||
$boardStats['author_ip_array'] = str_replace( "\"", "\\\"", serialize( $boardStats['author_ip_array'] ) );
|
||||
|
||||
|
||||
// Insert this data into our statistics table.
|
||||
$statsInsert = "VALUES(\"{$boardStats['stat_uri']}\", \"{$boardStats['stat_hour']}\", \"{$boardStats['post_count']}\", \"{$boardStats['post_id_array']}\", \"{$boardStats['author_ip_count']}\", \"{$boardStats['author_ip_array']}\" )";
|
||||
|
||||
$postStatQuery = prepare(
|
||||
"REPLACE INTO ``board_stats`` (stat_uri, stat_hour, post_count, post_id_array, author_ip_count, author_ip_array) {$statsInsert}"
|
||||
);
|
||||
$postStatQuery->execute() or error(db_error($postStatQuery));
|
||||
|
||||
// Update the posts_total tracker on the board.
|
||||
if ($new) {
|
||||
query("UPDATE ``boards`` SET `posts_total`=`posts_total`+1 WHERE `uri`=\"{$postUri}\"");
|
||||
}
|
||||
|
||||
return $boardStats;
|
||||
}
|
||||
|
||||
function getPageButtons($pages, $mod=false) {
|
||||
global $config, $board;
|
||||
|
||||
@ -1491,56 +1790,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 +1861,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'])
|
||||
@ -1958,12 +2274,38 @@ function markup(&$body, $track_cites = false, $op = false) {
|
||||
|
||||
$tracked_cites = array_unique($tracked_cites, SORT_REGULAR);
|
||||
|
||||
$body = preg_replace("/^\s*>.*$/m", '<span class="quote">$0</span>', $body);
|
||||
//$body = preg_replace("/^\s*>.*$/m", '<span class="quote">$0</span>', $body);
|
||||
|
||||
if ($config['strip_superfluous_returns'])
|
||||
$body = preg_replace('/\s+$/', '', $body);
|
||||
|
||||
$body = preg_replace("/\n/", '<br/>', $body);
|
||||
|
||||
if ($config['markup_paragraphs']) {
|
||||
$paragraphs = explode("\n", $body);
|
||||
$bodyNew = "";
|
||||
|
||||
foreach ($paragraphs as $paragraph) {
|
||||
if (strlen(trim($paragraph)) > 0) {
|
||||
$paragraphDirection = is_rtl($paragraph) ? "rtl" : "ltr";
|
||||
}
|
||||
else {
|
||||
$paragraphDirection = "empty";
|
||||
}
|
||||
|
||||
if (strpos($paragraph, ">")===0) {
|
||||
$quoteClass = "quote";
|
||||
}
|
||||
else {
|
||||
$quoteClass = "";
|
||||
}
|
||||
|
||||
$bodyNew .= "<p class=\"body-line {$paragraphDirection} {$quoteClass}\">" . $paragraph . "</p>";
|
||||
}
|
||||
|
||||
$body = $bodyNew;
|
||||
}
|
||||
else {
|
||||
$body = preg_replace("/\n/", '<br/>', $body);
|
||||
}
|
||||
|
||||
if ($config['markup_repair_tidy']) {
|
||||
$tidy = new tidy();
|
||||
@ -2020,6 +2362,40 @@ function ordutf8($string, &$offset) {
|
||||
return $code;
|
||||
}
|
||||
|
||||
function uniord($u) {
|
||||
$k = mb_convert_encoding($u, 'UCS-2LE', 'UTF-8');
|
||||
$k1 = ord(substr($k, 0, 1));
|
||||
$k2 = ord(substr($k, 1, 1));
|
||||
return $k2 * 256 + $k1;
|
||||
}
|
||||
|
||||
function is_rtl($str) {
|
||||
if(mb_detect_encoding($str) !== 'UTF-8') {
|
||||
$str = mb_convert_encoding($str, mb_detect_encoding($str),'UTF-8');
|
||||
}
|
||||
|
||||
preg_match_all('/[^\n\s]+/', $str, $matches);
|
||||
preg_match_all('/.|\n\s/u', $str, $matches);
|
||||
$chars = $matches[0];
|
||||
$arabic_count = 0;
|
||||
$latin_count = 0;
|
||||
$total_count = 0;
|
||||
|
||||
foreach ($chars as $char) {
|
||||
$pos = uniord($char);
|
||||
|
||||
if ($pos >= 1536 && $pos <= 1791) {
|
||||
$arabic_count++;
|
||||
}
|
||||
else if ($pos > 123 && $pos < 123) {
|
||||
$latin_count++;
|
||||
}
|
||||
$total_count++;
|
||||
}
|
||||
|
||||
return (($arabic_count/$total_count) > 0.5);
|
||||
}
|
||||
|
||||
function strip_combining_chars($str) {
|
||||
$chars = preg_split('//u', $str, -1, PREG_SPLIT_NO_EMPTY);
|
||||
$str = '';
|
||||
@ -2049,51 +2425,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);
|
||||
|
@ -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';
|
||||
@ -129,6 +126,7 @@
|
||||
$config['additional_javascript'][] = 'js/thread-watcher.js';
|
||||
$config['additional_javascript'][] = 'js/ajax.js';
|
||||
$config['additional_javascript'][] = 'js/quick-reply.js';
|
||||
$config['additional_javascript'][] = 'js/quick-post-controls.js';
|
||||
$config['additional_javascript'][] = 'js/show-own-posts.js';
|
||||
$config['additional_javascript'][] = 'js/youtube.js';
|
||||
$config['additional_javascript'][] = 'js/comment-toolbar.js';
|
||||
@ -143,6 +141,7 @@
|
||||
$config['additional_javascript'][] = 'js/auto-scroll.js';
|
||||
$config['additional_javascript'][] = 'js/twemoji/twemoji.js';
|
||||
$config['additional_javascript'][] = 'js/file-selector.js';
|
||||
$config['additional_javascript'][] = 'js/board-directory.js';
|
||||
// Oekaki (now depends on config.oekaki so can be in all scripts)
|
||||
$config['additional_javascript'][] = 'js/jquery-ui.custom.min.js';
|
||||
$config['additional_javascript'][] = 'js/wPaint/8ch.js';
|
||||
@ -155,46 +154,63 @@
|
||||
$config['stylesheets']['Dark'] = 'dark.css';
|
||||
$config['stylesheets']['Photon'] = 'photon.css';
|
||||
$config['stylesheets']['Redchanit'] = 'redchanit.css';
|
||||
|
||||
|
||||
$config['stylesheets_board'] = true;
|
||||
$config['markup'][] = array("/^[ |\t]*==(.+?)==[ |\t]*$/m", "<span class=\"heading\">\$1</span>");
|
||||
$config['markup'][] = array("/\[spoiler\](.+?)\[\/spoiler\]/", "<span class=\"spoiler\">\$1</span>");
|
||||
$config['markup'][] = array("/~~(.+?)~~/", "<s>\$1</s>");
|
||||
$config['markup'][] = array("/__(.+?)__/", "<u>\$1</u>");
|
||||
$config['markup'][] = array("/###([^\s']+)###/", "<a href='/boards.html#\$1'>###\$1###</a>");
|
||||
|
||||
|
||||
$config['markup_paragraphs'] = true;
|
||||
$config['markup_rtl'] = true;
|
||||
|
||||
$config['boards'] = array(array('<i class="fa fa-home" title="Home"></i>' => '/', '<i class="fa fa-tags" title="Boards"></i>' => '/boards.html', '<i class="fa fa-question" title="FAQ"></i>' => '/faq.html', '<i class="fa fa-random" title="Random"></i>' => '/random.php', '<i class="fa fa-plus" title="New board"></i>' => '/create.php', '<i class="fa fa-ban" title="Public ban list"></i>' => '/bans.html', '<i class="fa fa-search" title="Search"></i>' => '/search.php', '<i class="fa fa-cog" title="Manage board"></i>' => '/mod.php', '<i class="fa fa-quote-right" title="Chat"></i>' => 'https://qchat.rizon.net/?channels=#8chan'), array('b', 'news+', 'boards'), array('operate', 'meta'), array('<i class="fa fa-twitter" title="Twitter"></i>'=>'https://twitter.com/infinitechan'));
|
||||
//$config['boards'] = array(array('<i class="fa fa-home" title="Home"></i>' => '/', '<i class="fa fa-tags" title="Boards"></i>' => '/boards.html', '<i class="fa fa-question" title="FAQ"></i>' => '/faq.html', '<i class="fa fa-random" title="Random"></i>' => '/random.php', '<i class="fa fa-plus" title="New board"></i>' => '/create.php', '<i class="fa fa-search" title="Search"></i>' => '/search.php', '<i class="fa fa-cog" title="Manage board"></i>' => '/mod.php', '<i class="fa fa-quote-right" title="Chat"></i>' => 'https://qchat.rizon.net/?channels=#8chan'), array('b', 'meta', 'int'), array('v', 'a', 'tg', 'fit', 'pol', 'tech', 'mu', 'co', 'sp', 'boards'), array('<i class="fa fa-twitter" title="Twitter"></i>'=>'https://twitter.com/infinitechan'));
|
||||
|
||||
|
||||
$config['footer'][] = 'All posts on 8chan are the responsibility of the individual poster and not the administration of 8chan, pursuant to 47 U.S.C. § 230.';
|
||||
$config['footer'][] = 'We have not been served any secret court orders and are not under any gag orders.';
|
||||
$config['footer'][] = 'To make a DMCA request or report illegal content, please email <a href="mailto:admin@8chan.co">admin@8chan.co</a>.';
|
||||
|
||||
|
||||
$config['search']['enable'] = true;
|
||||
|
||||
|
||||
$config['syslog'] = true;
|
||||
|
||||
|
||||
$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.'
|
||||
);
|
||||
|
||||
$config['languages'] = array(
|
||||
'ch' => "汉语",
|
||||
'cz' => "Čeština",
|
||||
'dk' => "Dansk",
|
||||
'de' => "Deutsch",
|
||||
'eo' => "Esperanto",
|
||||
'en' => "English",
|
||||
'es' => "Español",
|
||||
'fi' => "Suomi",
|
||||
'fr' => "Français",
|
||||
'hu' => "Magyar",
|
||||
'it' => "Italiano",
|
||||
'jp' => "日本語",
|
||||
'jbo' => "Lojban",
|
||||
'lt' => "Lietuvių Kalba",
|
||||
'lv' => "Latviešu Valoda",
|
||||
'no' => "Norsk",
|
||||
'nl' => "Nederlands Vlaams",
|
||||
'pl' => "Polski",
|
||||
'pt' => "Português",
|
||||
'ru' => "Русский",
|
||||
'sk' => "Slovenský Jazyk",
|
||||
'tw' => "Taiwanese",
|
||||
);
|
||||
|
||||
|
||||
$config['gzip_static'] = false;
|
||||
$config['hash_masked_ip'] = true;
|
||||
$config['force_subject_op'] = false;
|
||||
@ -212,9 +228,18 @@ $config['enable_antibot'] = false;
|
||||
$config['spam']['unicode'] = false;
|
||||
$config['twig_cache'] = false;
|
||||
$config['report_captcha'] = true;
|
||||
|
||||
$config['page_404'] = 'page_404';
|
||||
|
||||
// Flavor and design.
|
||||
$config['site_name'] = "∞chan";
|
||||
$config['site_logo'] = "/static/logo_33.svg";
|
||||
|
||||
// 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";
|
||||
|
||||
|
24
inc/instance-functions.php
Normal file
24
inc/instance-functions.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
require_once("inc/8chan-functions.php");
|
||||
require_once("inc/8chan-mod-pages.php");
|
||||
|
||||
require_once "lib/htmlpurifier-4.6.0/library/HTMLPurifier.auto.php";
|
||||
|
||||
|
||||
function max_posts_per_hour($post) {
|
||||
global $config, $board;
|
||||
if (!$config['hour_max_threads']) return false;
|
||||
|
||||
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']);
|
||||
}
|
||||
}
|
||||
|
||||
function page_404() {
|
||||
include('404.php');
|
||||
}
|
@ -76,7 +76,7 @@ function twig_remove_whitespace_filter($data) {
|
||||
}
|
||||
|
||||
function twig_date_filter($date, $format) {
|
||||
return gmstrftime($format, $date);
|
||||
return gmstrftime($format, (int) $date);
|
||||
}
|
||||
|
||||
function twig_hasPermission_filter($mod, $permission, $board = null) {
|
||||
@ -86,7 +86,7 @@ function twig_hasPermission_filter($mod, $permission, $board = null) {
|
||||
function twig_extension_filter($value, $case_insensitive = true) {
|
||||
$ext = mb_substr($value, mb_strrpos($value, '.') + 1);
|
||||
if($case_insensitive)
|
||||
$ext = mb_strtolower($ext);
|
||||
$ext = mb_strtolower($ext);
|
||||
return $ext;
|
||||
}
|
||||
|
||||
|
@ -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']);
|
||||
}
|
||||
|
||||
|
@ -9,3 +9,5 @@ if ($query) {
|
||||
|
||||
$index = Element("8chan/index.html", array("config" => $config, "newsplus" => $newsplus));
|
||||
file_write('index.html', $index);
|
||||
|
||||
echo $index;
|
349
js/board-directory.js
Normal file
349
js/board-directory.js
Normal file
@ -0,0 +1,349 @@
|
||||
// ============================================================
|
||||
// Purpose : Board directory handling
|
||||
// Contributors : 8n-tech
|
||||
// ============================================================
|
||||
|
||||
;( function( window, $, undefined ) {
|
||||
var boardlist = {
|
||||
options : {
|
||||
$boardlist : false,
|
||||
|
||||
// Selectors for finding and binding elements.
|
||||
selector : {
|
||||
'boardlist' : "#boardlist",
|
||||
|
||||
'board-head' : ".board-list-head",
|
||||
'board-body' : ".board-list-tbody",
|
||||
'board-loading' : ".board-list-loading",
|
||||
'board-omitted' : ".board-list-omitted",
|
||||
|
||||
'search' : "#search-form",
|
||||
'search-lang' : "#search-lang-input",
|
||||
'search-sfw' : "#search-sfw-input",
|
||||
'search-tag' : "#search-tag-input",
|
||||
'search-title' : "#search-title-input",
|
||||
'search-submit' : "#search-submit",
|
||||
|
||||
'tag-list' : ".tag-list",
|
||||
'tag-link' : ".tag-link",
|
||||
|
||||
'footer-page' : ".board-page-num",
|
||||
'footer-count' : ".board-page-count",
|
||||
'footer-total' : ".board-page-total",
|
||||
'footer-more' : "#board-list-more"
|
||||
},
|
||||
|
||||
// HTML Templates for dynamic construction
|
||||
template : {
|
||||
// Board row item
|
||||
'board-row' : "<tr></tr>",
|
||||
|
||||
// Individual cell definitions
|
||||
'board-cell-meta' : "<td class=\"board-meta\"></td>",
|
||||
'board-cell-uri' : "<td class=\"board-uri\"></td>",
|
||||
'board-cell-title' : "<td class=\"board-title\"></td>",
|
||||
'board-cell-pph' : "<td class=\"board-pph\"></td>",
|
||||
'board-cell-posts_total' : "<td class=\"board-max\"></td>",
|
||||
'board-cell-active' : "<td class=\"board-unique\"></td>",
|
||||
'board-cell-tags' : "<td class=\"board-tags\"></td>",
|
||||
|
||||
// Content wrapper
|
||||
// Used to help constrain contents to their <td>.
|
||||
'board-content-wrap' : "<div class=\"board-cell\"></div>",
|
||||
|
||||
// Individual items or parts of a single table cell.
|
||||
'board-datum-lang' : "<span class=\"board-lang\"></span>",
|
||||
'board-datum-uri' : "<a class=\"board-link\"></a>",
|
||||
'board-datum-sfw' : "<i class=\"fa fa-briefcase board-sfw\" title=\"SFW\"></i>",
|
||||
'board-datum-nsfw' : "<i class=\"fa fa-briefcase board-nsfw\" title=\"NSFW\"></i>",
|
||||
'board-datum-tags' : "<a class=\"tag-link\" href=\"#\"></a>",
|
||||
|
||||
|
||||
// Tag list.
|
||||
'tag-list' : "<ul class=\"tag-list\"></ul>",
|
||||
'tag-item' : "<li class=\"tag-item\"></li>",
|
||||
'tag-link' : "<a class=\"tag-link\" href=\"#\"></a>"
|
||||
}
|
||||
},
|
||||
|
||||
lastSearch : {},
|
||||
|
||||
bind : {
|
||||
form : function() {
|
||||
var selectors = boardlist.options.selector;
|
||||
|
||||
var $search = $( selectors['search'] ),
|
||||
$searchLang = $( selectors['search-lang'] ),
|
||||
$searchSfw = $( selectors['search-sfw'] ),
|
||||
$searchTag = $( selectors['search-tag'] ),
|
||||
$searchTitle = $( selectors['search-title'] ),
|
||||
$searchSubmit = $( selectors['search-submit'] );
|
||||
|
||||
var searchForms = {
|
||||
'boardlist' : boardlist.$boardlist,
|
||||
'search' : $search,
|
||||
'searchLang' : $searchLang,
|
||||
'searchSfw' : $searchSfw,
|
||||
'searchTag' : $searchTag,
|
||||
'searchTitle' : $searchTitle,
|
||||
'searchSubmit' : $searchSubmit
|
||||
};
|
||||
|
||||
if ($search.length > 0) {
|
||||
// Bind form events.
|
||||
boardlist.$boardlist
|
||||
// Load more
|
||||
.on( 'click', selectors['board-omitted'], searchForms, boardlist.events.loadMore )
|
||||
// Tag click
|
||||
.on( 'click', selectors['tag-link'], searchForms, boardlist.events.tagClick )
|
||||
// Form Submission
|
||||
.on( 'submit', selectors['search'], searchForms, boardlist.events.searchSubmit )
|
||||
// Submit click
|
||||
.on( 'click', selectors['search-submit'], searchForms, boardlist.events.searchSubmit );
|
||||
|
||||
$searchSubmit.prop( 'disabled', false );
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
build : {
|
||||
boardlist : function(data) {
|
||||
boardlist.build.boards(data['boards'], data['order']);
|
||||
boardlist.build.lastSearch(data['search']);
|
||||
boardlist.build.footer(data);
|
||||
boardlist.build.tags(data['tagWeight']);
|
||||
|
||||
},
|
||||
|
||||
boards : function(boards, order) {
|
||||
// Find our head, columns, and body.
|
||||
var $head = $( boardlist.options.selector['board-head'], boardlist.$boardlist ),
|
||||
$cols = $("[data-column]", $head ),
|
||||
$body = $( boardlist.options.selector['board-body'], boardlist.$boardlist );
|
||||
|
||||
$.each( order, function( index, uri ) {
|
||||
var row = boards[uri];
|
||||
$row = $( boardlist.options.template['board-row'] );
|
||||
|
||||
$cols.each( function( index, col ) {
|
||||
boardlist.build.board( row, col ).appendTo( $row );
|
||||
} );
|
||||
|
||||
$row.appendTo( $body );
|
||||
} );
|
||||
|
||||
},
|
||||
board : function(row, col) {
|
||||
var $col = $(col),
|
||||
column = $col.attr('data-column'),
|
||||
value = row[column]
|
||||
$cell = $( boardlist.options.template['board-cell-' + column] ),
|
||||
$wrap = $( boardlist.options.template['board-content-wrap'] );
|
||||
|
||||
if (typeof boardlist.build.boardcell[column] === "undefined") {
|
||||
if (value instanceof Array) {
|
||||
if (typeof boardlist.options.template['board-datum-' + column] !== "undefined") {
|
||||
$.each( value, function( index, singleValue ) {
|
||||
$( boardlist.options.template['board-datum-' + column] )
|
||||
.text( singleValue )
|
||||
.appendTo( $wrap );
|
||||
} );
|
||||
}
|
||||
else {
|
||||
$wrap.text( value.join(" ") );
|
||||
}
|
||||
}
|
||||
else {
|
||||
$wrap.text( value );
|
||||
}
|
||||
}
|
||||
else {
|
||||
var $content = boardlist.build.boardcell[column]( row, value );
|
||||
|
||||
if ($content instanceof jQuery) {
|
||||
// We use .append() instead of .appendTo() as we do elsewhere
|
||||
// because $content can be multiple elements.
|
||||
$wrap.append( $content );
|
||||
}
|
||||
else if (typeof $content === "string") {
|
||||
$wrap.html( $content );
|
||||
}
|
||||
else {
|
||||
console.log("Special cell constructor returned a " + (typeof $content) + " that board-directory.js cannot interpret.");
|
||||
}
|
||||
}
|
||||
|
||||
$wrap.appendTo( $cell );
|
||||
return $cell;
|
||||
},
|
||||
boardcell : {
|
||||
'meta' : function(row, value) {
|
||||
return $( boardlist.options.template['board-datum-lang'] ).text( row['locale'] );
|
||||
},
|
||||
'uri' : function(row, value) {
|
||||
var $link = $( boardlist.options.template['board-datum-uri'] ),
|
||||
$sfw = $( boardlist.options.template['board-datum-' + (row['sfw'] == 1 ? "sfw" : "nsfw")] );
|
||||
|
||||
$link
|
||||
.attr( 'href', "/"+row['uri']+"/" )
|
||||
.text( "/"+row['uri']+"/" );
|
||||
|
||||
// I decided against NSFW icons because it clutters the index.
|
||||
// Blue briefcase = SFW. No briefcase = NSFW. Seems better.
|
||||
if (row['sfw'] == 1) {
|
||||
return $link[0].outerHTML + $sfw[0].outerHTML;
|
||||
}
|
||||
else {
|
||||
return $link[0].outerHTML;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
lastSearch : function(search) {
|
||||
return boardlist.lastSearch = {
|
||||
'lang' : search.lang === false ? "" : search.lang,
|
||||
'page' : search.page,
|
||||
'tags' : search.tags === false ? "" : search.tags.join(" "),
|
||||
'time' : search.time,
|
||||
'title' : search.title === false ? "" : search.title,
|
||||
'sfw' : search.nsfw ? 0 : 1
|
||||
};
|
||||
},
|
||||
|
||||
footer : function(data) {
|
||||
var selector = boardlist.options.selector,
|
||||
$page = $( selector['footer-page'], boardlist.$boardlist ),
|
||||
$count = $( selector['footer-count'], boardlist.$boardlist ),
|
||||
$total = $( selector['footer-total'], boardlist.$boardlist ),
|
||||
$more = $( selector['footer-more'], boardlist.$boardlist ),
|
||||
$omitted = $( selector['board-omitted'], boardlist.$boardlist );
|
||||
|
||||
var boards = Object.keys(data['boards']).length,
|
||||
omitted = data['omitted'] - data['search']['page'];
|
||||
|
||||
if (omitted < 0) {
|
||||
omitted = 0;
|
||||
}
|
||||
|
||||
var total = boards + omitted + data['search']['page'];
|
||||
|
||||
//$page.text( data['search']['page'] );
|
||||
$count.text( data['search']['page'] + boards );
|
||||
$total.text( total );
|
||||
$more.toggleClass( "board-list-hasmore", omitted != 0 );
|
||||
$omitted.toggle( boards + omitted > 0 );
|
||||
},
|
||||
|
||||
tags : function(tags) {
|
||||
var selector = boardlist.options.selector,
|
||||
template = boardlist.options.template,
|
||||
$list = $( selector['tag-list'], boardlist.$boardlist );
|
||||
|
||||
if ($list.length) {
|
||||
|
||||
$.each( tags, function(tag, weight) {
|
||||
var $item = $( template['tag-item'] ),
|
||||
$link = $( template['tag-link'] );
|
||||
|
||||
$link
|
||||
.css( 'font-size', weight+"%" )
|
||||
.text( tag )
|
||||
.appendTo( $item );
|
||||
|
||||
$item.appendTo( $list );
|
||||
} );
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
events : {
|
||||
loadMore : function(event) {
|
||||
var parameters = $.extend( {}, boardlist.lastSearch );
|
||||
|
||||
parameters.page = $( boardlist.options.selector['board-body'], boardlist.$boardlist ).children().length;
|
||||
|
||||
boardlist.submit( parameters );
|
||||
},
|
||||
|
||||
searchSubmit : function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
$( boardlist.options.selector['tag-list'], boardlist.$boardlist ).html("");
|
||||
$( boardlist.options.selector['board-body'], boardlist.$boardlist ).html("");
|
||||
|
||||
boardlist.submit( {
|
||||
'lang' : event.data.searchLang.val(),
|
||||
'tags' : event.data.searchTag.val(),
|
||||
'title' : event.data.searchTitle.val(),
|
||||
'sfw' : event.data.searchSfw.prop('checked') ? 1 : 0
|
||||
} );
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
tagClick : function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
var $this = $(this),
|
||||
$input = $( boardlist.options.selector['search-tag'] );
|
||||
|
||||
$input
|
||||
.val( ( $input.val() + " " + $this.text() ).replace(/\s+/g, " ").trim() )
|
||||
.trigger( 'change' )
|
||||
.focus();
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
submit : function( parameters ) {
|
||||
var $boardlist = boardlist.$boardlist,
|
||||
$boardload = $( boardlist.options.selector['board-loading'], $boardlist ),
|
||||
$searchSubmit = $( boardlist.options.selector['search-submit'], $boardlist ),
|
||||
$footerMore = $( boardlist.options.selector['board-omitted'], $boardlist );
|
||||
|
||||
$searchSubmit.prop( 'disabled', true );
|
||||
$boardload.show();
|
||||
$footerMore.hide();
|
||||
|
||||
return $.get(
|
||||
"/board-search.php",
|
||||
parameters,
|
||||
function(data) {
|
||||
$searchSubmit.prop( 'disabled', false );
|
||||
$boardload.hide();
|
||||
|
||||
boardlist.build.boardlist( $.parseJSON(data) );
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
init : function( target ) {
|
||||
if (typeof target !== "string") {
|
||||
target = boardlist.options.selector.boardlist;
|
||||
}
|
||||
|
||||
var $boardlist = $(target);
|
||||
|
||||
if ($boardlist.length > 0 ) {
|
||||
$( boardlist.options.selector['board-loading'], $boardlist ).hide();
|
||||
|
||||
boardlist.$boardlist = $boardlist;
|
||||
boardlist.bind.form();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Tie to the vichan object.
|
||||
if (typeof window.vichan === "undefined") {
|
||||
window.vichan = {};
|
||||
}
|
||||
window.vichan.boardlist = boardlist;
|
||||
|
||||
// Initialize the boardlist when the document is ready.
|
||||
$( document ).on( 'ready', window.vichan.boardlist.init );
|
||||
// Run it now if we're already ready.
|
||||
if (document.readyState === 'complete') {
|
||||
window.vichan.boardlist.init();
|
||||
}
|
||||
} )( window, jQuery );
|
@ -90,5 +90,12 @@ $(document).ready(function(){
|
||||
$(document).on('new_post', function(e, post) {
|
||||
$(post).find('input[type=checkbox].delete').each(init_qpc);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Bottom of the page quick reply function
|
||||
$("#thread-quick-reply").show();
|
||||
$("#link-quick-reply").on( 'click', function(event) {
|
||||
event.preventDefault();
|
||||
$(window).trigger('cite', ['']);
|
||||
return false;
|
||||
} );
|
||||
} );
|
54
post.php
54
post.php
@ -209,11 +209,16 @@ if (isset($_POST['delete'])) {
|
||||
}
|
||||
}
|
||||
elseif (isset($_POST['post'])) {
|
||||
if (!isset($_POST['body'], $_POST['board']))
|
||||
if (!isset($_POST['body'], $_POST['board'])) {
|
||||
error($config['error']['bot']);
|
||||
|
||||
$post = array('board' => $_POST['board'], 'files' => array());
|
||||
|
||||
}
|
||||
|
||||
$post = array(
|
||||
'board' => $_POST['board'],
|
||||
'files' => array(),
|
||||
'time' => time(), // Timezone independent UNIX timecode.
|
||||
);
|
||||
|
||||
// Check if board exists
|
||||
if (!openBoard($post['board']))
|
||||
error($config['error']['noboard']);
|
||||
@ -228,17 +233,13 @@ elseif (isset($_POST['post'])) {
|
||||
$_POST['subject'] = '';
|
||||
|
||||
if (!isset($_POST['password']))
|
||||
$_POST['password'] = '';
|
||||
$_POST['password'] = '';
|
||||
|
||||
if (isset($_POST['thread'])) {
|
||||
$post['op'] = false;
|
||||
$post['thread'] = round($_POST['thread']);
|
||||
} else
|
||||
$post['op'] = true;
|
||||
|
||||
// Check if board exists
|
||||
if (!openBoard($post['board']))
|
||||
error($config['error']['noboard']);
|
||||
|
||||
// Check if banned
|
||||
checkBan($board['uri']);
|
||||
@ -642,7 +643,8 @@ elseif (isset($_POST['post'])) {
|
||||
|
||||
if (mysql_version() >= 50503) {
|
||||
$post['body_nomarkup'] = $post['body']; // Assume we're using the utf8mb4 charset
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// MySQL's `utf8` charset only supports up to 3-byte symbols
|
||||
// Remove anything >= 0x010000
|
||||
|
||||
@ -712,7 +714,7 @@ elseif (isset($_POST['post'])) {
|
||||
do_filters($post);
|
||||
}
|
||||
|
||||
if ($post['has_file']) {
|
||||
if ($post['has_file']) {
|
||||
foreach ($post['files'] as $key => &$file) {
|
||||
if ($file['is_an_image'] && $config['ie_mime_type_detection'] !== false) {
|
||||
// Check IE MIME type detection XSS exploit
|
||||
@ -910,10 +912,15 @@ elseif (isset($_POST['post'])) {
|
||||
$post['files'] = $post['files'];
|
||||
$post['num_files'] = sizeof($post['files']);
|
||||
|
||||
// Commit the post to the database.
|
||||
$post['id'] = $id = post($post);
|
||||
|
||||
insertFloodPost($post);
|
||||
|
||||
|
||||
// Update statistics for this board.
|
||||
updateStatisticsForPost( $post );
|
||||
|
||||
|
||||
// Handle cyclical threads
|
||||
if (!$post['op'] && isset($thread['cycle']) && $thread['cycle']) {
|
||||
// Query is a bit weird due to "This version of MariaDB doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'" (MariaDB Ver 15.1 Distrib 10.0.17-MariaDB, for Linux (x86_64))
|
||||
@ -1009,17 +1016,20 @@ elseif (isset($_POST['post'])) {
|
||||
event('post-after', $post);
|
||||
|
||||
buildIndex();
|
||||
|
||||
// We are already done, let's continue our heavy-lifting work in the background (if we run off FastCGI)
|
||||
if (function_exists('fastcgi_finish_request'))
|
||||
@fastcgi_finish_request();
|
||||
|
||||
if ($post['op'])
|
||||
rebuildThemes('post-thread', $board['uri']);
|
||||
else
|
||||
rebuildThemes('post', $board['uri']);
|
||||
|
||||
} elseif (isset($_POST['appeal'])) {
|
||||
// We are already done, let's continue our heavy-lifting work in the background (if we run off FastCGI)
|
||||
if (function_exists('fastcgi_finish_request')) {
|
||||
@fastcgi_finish_request();
|
||||
}
|
||||
|
||||
if ($post['op']) {
|
||||
rebuildThemes('post-thread', $board['uri']);
|
||||
}
|
||||
else {
|
||||
rebuildThemes('post', $board['uri']);
|
||||
}
|
||||
}
|
||||
elseif (isset($_POST['appeal'])) {
|
||||
if (!isset($_POST['ban_id']))
|
||||
error($config['error']['bot']);
|
||||
|
||||
|
200
smart_build.php
Normal file
200
smart_build.php
Normal file
@ -0,0 +1,200 @@
|
||||
<?php
|
||||
require_once("inc/functions.php");
|
||||
|
||||
if (!$config['smart_build']) {
|
||||
die('You need to enable $config["smart_build"]');
|
||||
}
|
||||
|
||||
$config['smart_build'] = false; // Let's disable it, so we can build the page for real
|
||||
|
||||
function after_open_board() { global $config;
|
||||
$config['smart_build'] = false;
|
||||
};
|
||||
|
||||
function sb_board($b, $page = 1) { global $config, $build_pages; $page = (int)$page;
|
||||
if ($page < 1) return false;
|
||||
if (!openBoard($b)) return false;
|
||||
if ($page > $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 "<h1>404 Not Found</h1><p>Page doesn't exist<hr><address>vichan</address>";
|
||||
}
|
||||
elseif (is_callable($config['page_404'])) {
|
||||
$config['page_404']();
|
||||
}
|
||||
else {
|
||||
header("Location: ".$config['page_404']);
|
||||
}
|
||||
header("X-Accel-Expires: 120");
|
||||
die();
|
||||
}
|
||||
|
||||
if ($reached) {
|
||||
if ($request[strlen($request)-1] == '/') {
|
||||
$request .= 'index.html';
|
||||
}
|
||||
$request = '.'.$request;
|
||||
|
||||
if (!file_exists($request)) {
|
||||
die_404();
|
||||
}
|
||||
|
||||
header("HTTP/1.1 200 OK");
|
||||
header("Status: 200 OK");
|
||||
if (preg_match('/\.json$/', $request)) {
|
||||
header("Content-Type", "application/json");
|
||||
}
|
||||
elseif (preg_match('/\.js$/', $request)) {
|
||||
header("Content-Type", "text/javascript; charset=utf-8");
|
||||
}
|
||||
elseif (preg_match('/\.xml$/', $request)) {
|
||||
header("Content-Type", "application/xml");
|
||||
}
|
||||
else {
|
||||
header("Content-Type", "text/html; charset=utf-8");
|
||||
}
|
||||
header("Cache-Control: public, nocache, no-cache, max-age=0, must-revalidate");
|
||||
header("Expires: Fri, 22 Feb 1991 06:00:00 GMT");
|
||||
header("Last-Modified: ".date('r', filemtime($request)));
|
||||
|
||||
//if (isset ($_SERVER['HTTP_ACCEPT_ENCODING']) && preg_match('/gzip/', $_SERVER['HTTP_ACCEPT_ENCODING']) && file_exists($request.".gz")) {
|
||||
// header("Content-Encoding: gzip");
|
||||
// $file = fopen($request.".gz", 'r');
|
||||
//}
|
||||
//else {
|
||||
$file = fopen($request, 'r');
|
||||
//}
|
||||
fpassthru($file);
|
||||
fclose($file);
|
||||
}
|
||||
else {
|
||||
die_404();
|
||||
}
|
BIN
static/infinity-small.gif
Normal file
BIN
static/infinity-small.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.0 KiB |
@ -198,3 +198,34 @@ div.report {
|
||||
.modlog tr:nth-child(even), .modlog th {
|
||||
background-color: #282A2E;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.box {
|
||||
background: #333333;
|
||||
border-color: #555555;
|
||||
color: #C5C8C6;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.box-title {
|
||||
background: transparent;
|
||||
color: #32DD72;
|
||||
}
|
||||
|
||||
table thead th {
|
||||
background: #333333;
|
||||
border-color: #555555;
|
||||
color: #C5C8C6;
|
||||
border-radius: 4px;
|
||||
}
|
||||
table tbody tr:nth-of-type( even ) {
|
||||
background-color: #333333;
|
||||
}
|
||||
|
||||
table.board-list-table .board-uri .board-sfw {
|
||||
color: #446655;
|
||||
}
|
||||
tbody.board-list-omitted td {
|
||||
background: #333333;
|
||||
border-color: #555555;
|
||||
}
|
@ -329,3 +329,31 @@ form table tr td div {
|
||||
.desktop-style div.boardlist:not(.bottom) {
|
||||
background-color: #DDDDDD;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.box {
|
||||
background: #DDDDDD;
|
||||
border-color: #CCCCCC;
|
||||
color: #333333;
|
||||
border-radius: 7px;
|
||||
}
|
||||
.box-title {
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
table thead th {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
table tbody tr:nth-of-type( even ) {
|
||||
background-color: #DDDDDD;
|
||||
}
|
||||
|
||||
table.board-list-table .board-uri .board-sfw {
|
||||
color: #333333;
|
||||
}
|
||||
tbody.board-list-omitted td {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
@ -57,4 +57,33 @@ p.intro a.email span.name {
|
||||
|
||||
a {
|
||||
color: #8020FF;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.box {
|
||||
background: #343C4E;
|
||||
border: none;
|
||||
color: #FFF;
|
||||
}
|
||||
.box-title {
|
||||
background: #7F8CA8;
|
||||
color: #0F0C5D;
|
||||
}
|
||||
|
||||
table thead th {
|
||||
background: #343C4E;
|
||||
border: none;
|
||||
color: #FFF;
|
||||
}
|
||||
table tbody tr:nth-of-type( even ) {
|
||||
background-color: #343C4E;
|
||||
}
|
||||
|
||||
table.board-list-table .board-uri .board-sfw {
|
||||
color: #D00;
|
||||
}
|
||||
tbody.board-list-omitted td {
|
||||
background: #343C4E;
|
||||
border: none;
|
||||
}
|
@ -1,3 +1,6 @@
|
||||
/* === GENERAL TAG SETTINGS === */
|
||||
|
||||
/* Page Layouts */
|
||||
body {
|
||||
background: #EEF2FF url('img/fade-blue.png') repeat-x 50% 0%;
|
||||
color: black;
|
||||
@ -8,6 +11,69 @@ body {
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
main,
|
||||
aside,
|
||||
section {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 1110px;
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
table {
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table {
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
}
|
||||
table tbody td {
|
||||
margin: 0;
|
||||
padding: 4px 15px 4px 4px;
|
||||
|
||||
text-align: left;
|
||||
}
|
||||
table thead th {
|
||||
border: 1px solid #000333;
|
||||
padding: 4px 15px 5px 5px;
|
||||
|
||||
background: #98E;
|
||||
color: #000333;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
}
|
||||
table tbody tr:nth-of-type( even ) {
|
||||
background-color: #D6DAF0;
|
||||
}
|
||||
|
||||
td.minimal,th.minimal {
|
||||
width: 1%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
table.mod.config-editor {
|
||||
font-size: 9pt;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table.mod.config-editor td {
|
||||
text-align: left;
|
||||
padding: 5px;
|
||||
border-bottom: 1px solid #98e;
|
||||
}
|
||||
|
||||
table.mod.config-editor input[type="text"] {
|
||||
width: 98%;
|
||||
}
|
||||
|
||||
|
||||
/* Uncategorized */
|
||||
#post-form-outer {
|
||||
text-align: center;
|
||||
}
|
||||
@ -20,6 +86,10 @@ body {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#post-form-inner .post-table tr {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.post-table th, .post-table-options th {
|
||||
width: 85px;
|
||||
}
|
||||
@ -297,15 +367,17 @@ p.intro a {
|
||||
color: maroon;
|
||||
}
|
||||
|
||||
div.delete {
|
||||
float: right;
|
||||
p.body-line,
|
||||
div.post p {
|
||||
display: block;
|
||||
margin: 0;
|
||||
|
||||
line-height: 1.16em;
|
||||
font-size: 13px;
|
||||
min-height: 1.16em;
|
||||
}
|
||||
|
||||
div.post.reply p {
|
||||
margin: 0.3em 0 0 0;
|
||||
}
|
||||
|
||||
div.post.reply div.body {
|
||||
div.post div.body {
|
||||
margin-left: 1.8em;
|
||||
margin-top: 0.8em;
|
||||
padding-right: 3em;
|
||||
@ -332,7 +404,7 @@ div.post div.body {
|
||||
div.post.reply {
|
||||
background: #D6DAF0;
|
||||
margin: 0.2em 4px;
|
||||
padding: 0.2em 0.3em 0.5em 0.6em;
|
||||
padding: 0.5em 0.3em 0.5em 0.6em;
|
||||
border-width: 1px;
|
||||
border-style: none solid solid none;
|
||||
border-color: #B7C5D9;
|
||||
@ -347,12 +419,14 @@ div.post.reply.has-file.body-not-empty {
|
||||
div.post_modified {
|
||||
margin-left: 1.8em;
|
||||
}
|
||||
|
||||
div.post_modified div.content-status {
|
||||
margin-top: 0.5em;
|
||||
padding-bottom: 0em;
|
||||
font-size: 72%;
|
||||
}
|
||||
div.post_modified div.content-status:first-child {
|
||||
margin-top: 1.3em;
|
||||
}
|
||||
|
||||
div.post_modified div.content-status:first-child {
|
||||
margin-top: 1.3em;
|
||||
@ -367,7 +441,7 @@ span.trip {
|
||||
color: #228854;
|
||||
}
|
||||
|
||||
span.quote {
|
||||
.quote {
|
||||
color: #789922;
|
||||
}
|
||||
|
||||
@ -536,50 +610,10 @@ hr {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
div.boardlist {
|
||||
color: #89A;
|
||||
font-size: 9pt;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
div.boardlist.bottom {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
div.boardlist a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
div.report {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
table.modlog {
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table.modlog tr td {
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
padding: 4px 15px 0 0;
|
||||
}
|
||||
|
||||
table.modlog tr th {
|
||||
text-align: left;
|
||||
padding: 4px 15px 5px 5px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
table.modlog tr th {
|
||||
background: #98E;
|
||||
}
|
||||
|
||||
td.minimal,th.minimal {
|
||||
width: 1%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
div.top_notice {
|
||||
text-align: center;
|
||||
margin: 5px auto;
|
||||
@ -603,21 +637,6 @@ div.blotter {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
table.mod.config-editor {
|
||||
font-size: 9pt;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table.mod.config-editor td {
|
||||
text-align: left;
|
||||
padding: 5px;
|
||||
border-bottom: 1px solid #98e;
|
||||
}
|
||||
|
||||
table.mod.config-editor input[type="text"] {
|
||||
width: 98%;
|
||||
}
|
||||
|
||||
.desktop-style div.boardlist:not(.bottom) {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@ -1010,7 +1029,6 @@ span.pln {
|
||||
color:grey;
|
||||
}
|
||||
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
p.intro {
|
||||
clear: none;
|
||||
@ -1021,8 +1039,169 @@ span.pln {
|
||||
}
|
||||
}
|
||||
|
||||
/* threadwatcher */
|
||||
/* === SITE-WIDE ASSETS === */
|
||||
#logo {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 0 0 0 0;
|
||||
text-align: center;
|
||||
}
|
||||
#logo-link {
|
||||
display: inline;
|
||||
}
|
||||
#logo-img {
|
||||
display: inline-block;
|
||||
height: 128px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
/* === GENERAL CLASSES === */
|
||||
.loading {
|
||||
background: none;
|
||||
background-color: none;
|
||||
background-image: url('/static/infinity.gif');
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
min-height: 76px;
|
||||
min-width: 128px;
|
||||
}
|
||||
.loading-small {
|
||||
background: none;
|
||||
background-color: none;
|
||||
background-image: url('/static/infinity-small.gif');
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
min-height: 24px;
|
||||
min-width: 48px;
|
||||
}
|
||||
|
||||
/* Text and accessibility */
|
||||
.ltr {
|
||||
direction: ltr;
|
||||
}
|
||||
.rtl {
|
||||
direction: rtl;
|
||||
font-family: Tahoma;
|
||||
}
|
||||
|
||||
/* Responsive helpers */
|
||||
.col {
|
||||
box-sizing: border-box;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.col-12 { width: 100%; }
|
||||
.col-11 { width: 91.66666667%; }
|
||||
.col-10 { width: 83.33333333%; }
|
||||
.col-9 { width: 75%; }
|
||||
.col-8 { width: 66.66666667%; }
|
||||
.col-7 { width: 58.33333333%; }
|
||||
.col-6 { width: 50%; }
|
||||
.col-5 { width: 41.66666667%; }
|
||||
.col-4 { width: 33.33333333%; }
|
||||
.col-3 { width: 25%; }
|
||||
.col-2 { width: 16.66666667%; }
|
||||
.col-1 { width: 8.33333333%; }
|
||||
|
||||
.left-push {
|
||||
float: left;
|
||||
}
|
||||
.right-push {
|
||||
float: right;
|
||||
}
|
||||
|
||||
/* Layout design */
|
||||
.box {
|
||||
background: #D6DAF0;
|
||||
border: 1px solid #000333;
|
||||
color: #000333;
|
||||
margin: 0 0 12px 0;
|
||||
}
|
||||
.box-title {
|
||||
background: #98E;
|
||||
color: #000333;
|
||||
font-size: 120%;
|
||||
font-weight: bold;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
.box-content {
|
||||
padding: 0 8px;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
|
||||
.clearfix {
|
||||
display: block;
|
||||
clear: both;
|
||||
visibility: hidden;
|
||||
overflow: hidden;
|
||||
|
||||
font-size: 0px;
|
||||
line-height: 0px;
|
||||
|
||||
box-sizing: border-box;
|
||||
border: none;
|
||||
height: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
zoom: 1;
|
||||
}
|
||||
|
||||
/* === SPECIFIC PAGES & FEATURES === */
|
||||
|
||||
/* Board List */
|
||||
div.boardlist {
|
||||
margin-top: 3px;
|
||||
|
||||
color: #89A;
|
||||
font-size: 9pt;
|
||||
}
|
||||
div.boardlist.bottom {
|
||||
margin-top: 12px;
|
||||
clear: both;
|
||||
}
|
||||
div.boardlist a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Threads */
|
||||
/* Thread Footer */
|
||||
#thread-interactions {
|
||||
margin: 8px 0;
|
||||
clear: both;
|
||||
}
|
||||
#thread-links {
|
||||
float: left;
|
||||
}
|
||||
#thread-links > a {
|
||||
padding-left: none;
|
||||
padding-right: 10px;
|
||||
}
|
||||
#thread-quick-reply {
|
||||
display: none;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
right: 50%;
|
||||
text-align: center;
|
||||
width: 100px;
|
||||
margin-left: -50px;
|
||||
}
|
||||
#thread_stats {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#post-moderation-fields {
|
||||
float: right;
|
||||
text-align: right;
|
||||
}
|
||||
#delete-fields {
|
||||
}
|
||||
#report-fields {
|
||||
}
|
||||
|
||||
/* threadwatcher */
|
||||
#watchlist {
|
||||
display: none;
|
||||
max-height: 250px;
|
||||
@ -1065,25 +1244,25 @@ div.mix {
|
||||
}
|
||||
|
||||
/* Mona Font */
|
||||
|
||||
.aa {
|
||||
font-family: Mona, "MS PGothic", "MS Pゴシック", sans-serif;
|
||||
display: block!important;
|
||||
font-size: 12pt;
|
||||
}
|
||||
|
||||
.dx,.dy,.dz {
|
||||
.dx,
|
||||
.dy,
|
||||
.dz {
|
||||
width: 30px;
|
||||
text-align: right;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Dice */
|
||||
.dice-option table {
|
||||
border: 1px dotted black;
|
||||
margin: 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.dice-option table td {
|
||||
text-align: center;
|
||||
border-left: 1px dotted black;
|
||||
@ -1093,7 +1272,6 @@ div.mix {
|
||||
}
|
||||
|
||||
/* Quick reply (why was most of this ever in the script?) */
|
||||
|
||||
#quick-reply {
|
||||
position: fixed;
|
||||
right: 5%;
|
||||
@ -1259,3 +1437,181 @@ div.mix {
|
||||
.dropzone .remove-btn:hover {
|
||||
color: rgba(125, 125, 125, 1);
|
||||
}
|
||||
|
||||
table.board-list-table {
|
||||
display: table;
|
||||
margin: -2px;
|
||||
margin-bottom: 10px;
|
||||
overflow: hidden;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
table.board-list-table .board-meta {
|
||||
padding-right: 4px;
|
||||
width: 70px;
|
||||
}
|
||||
table.board-list-table .board-uri {
|
||||
max-width: 196px;
|
||||
}
|
||||
table.board-list-table .board-title {
|
||||
width: auto;
|
||||
}
|
||||
table.board-list-table .board-pph {
|
||||
width: 55px;
|
||||
padding: 4px;
|
||||
}
|
||||
table.board-list-table .board-max {
|
||||
width: 90px;
|
||||
padding: 4px;
|
||||
}
|
||||
table.board-list-table .board-unique {
|
||||
width: 100px;
|
||||
padding: 4px;
|
||||
}
|
||||
table.board-list-table .board-tags {
|
||||
width: auto;
|
||||
padding: 0 15px 0 4px;
|
||||
}
|
||||
|
||||
table.board-list-table .board-uri .board-nsfw {
|
||||
color: rgb(230,0,0);
|
||||
margin: 0 0 0 0.6em;
|
||||
float: right;
|
||||
}
|
||||
table.board-list-table .board-uri .board-sfw {
|
||||
/* I'm using blue instead of green to help users with Deuteranopia (most common form of colorblndness). */
|
||||
color: rgb(0,0,230);
|
||||
margin: 0 0 0 0.6em;
|
||||
float: right;
|
||||
}
|
||||
|
||||
table.board-list-table div.board-cell {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
tbody.board-list-loading {
|
||||
display: none;
|
||||
}
|
||||
tbody.board-list-loading .loading {
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
tbody.board-list-omitted td {
|
||||
background: #98E;
|
||||
border-top: 1px solid #000333;
|
||||
padding: 8px;
|
||||
font-size: 125%;
|
||||
text-align: center;
|
||||
}
|
||||
tbody.board-list-omitted #board-list-more {
|
||||
cursor: default;
|
||||
}
|
||||
tbody.board-list-omitted #board-list-more.board-list-hasmore {
|
||||
cursor: pointer;
|
||||
}
|
||||
tbody.board-list-omitted .board-page-loadmore {
|
||||
display: none;
|
||||
}
|
||||
tbody.board-list-omitted .board-list-hasmore .board-page-loadmore {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
aside.search-container {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
aside.search-container .box {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.board-search {
|
||||
margin: 8px 0;
|
||||
}
|
||||
.search-item {
|
||||
margin: 8px 0;
|
||||
}
|
||||
.search-sfw {
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
font-size: 110%;
|
||||
line-height: 120%;
|
||||
}
|
||||
#search-sfw-input {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
transform: scale(1.20);
|
||||
}
|
||||
#search-lang-input,
|
||||
#search-title-input,
|
||||
#search-tag-input {
|
||||
box-sizing: border-box;
|
||||
font-size: 110%;
|
||||
line-height: 120%;
|
||||
vertical-align: top;
|
||||
padding: 2px 0 2px 4px;
|
||||
max-width: 100%;
|
||||
min-width: 100%;
|
||||
width: 100%:
|
||||
}
|
||||
#search-loading {
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
ul.tag-list {
|
||||
display: block;
|
||||
list-style: none;
|
||||
margin: 8px 8px -9px 8px;
|
||||
padding: 8px 0 0 0;
|
||||
border-top: 1px solid #000333;
|
||||
}
|
||||
ul.tag-list::after {
|
||||
content: ' ';
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
li.tag-item {
|
||||
display: inline-block;
|
||||
float: left;
|
||||
font-size: 100%;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0 4px 0 0;
|
||||
}
|
||||
li.tag-item:last-child {
|
||||
padding-bottom: 17px;
|
||||
}
|
||||
a.tag-link {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
li.tag-item a.tag-link {
|
||||
}
|
||||
td.board-tags a.tag-link {
|
||||
display: inline-block;
|
||||
margin: 0 0.4em 0 0;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1100px) {
|
||||
aside.search-container {
|
||||
width: 100%;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
aside.search-container .box {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
section.board-list {
|
||||
margin-top: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table.board-list-table .board-meta,
|
||||
table.board-list-table .board-pph,
|
||||
table.board-list-table .board-tags {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 0;
|
||||
width: 0;
|
||||
}
|
||||
}
|
@ -156,3 +156,32 @@ div#watchlist a:hover,a.watchThread:hover {
|
||||
.modlog tr:nth-child(even), .modlog th {
|
||||
background-color: #282A2E;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.box {
|
||||
background: #282a2e;
|
||||
border-color: #111;
|
||||
color: #C5C8C6;
|
||||
}
|
||||
.box-title {
|
||||
background: #282a2e;
|
||||
color: #C5C8C6;
|
||||
}
|
||||
|
||||
table thead th {
|
||||
background: #282a2e;
|
||||
border-color: #111;
|
||||
color: #C5C8C6;
|
||||
}
|
||||
table tbody tr:nth-of-type( even ) {
|
||||
background-color: #282a2e;
|
||||
}
|
||||
|
||||
table.board-list-table .board-uri .board-sfw {
|
||||
color: #C5C8C6;
|
||||
}
|
||||
tbody.board-list-omitted td {
|
||||
background: #282a2e;
|
||||
border-color: #111;
|
||||
}
|
@ -3,10 +3,10 @@ body {
|
||||
color: #800000;
|
||||
}
|
||||
a:link, a:visited, p.intro a.email span.name {
|
||||
color: #0000ff;
|
||||
color: #800;
|
||||
}
|
||||
a:link:hover {
|
||||
color: #d00;
|
||||
color: #e00;
|
||||
}
|
||||
a.post_no {
|
||||
color: #800000;
|
||||
@ -66,3 +66,32 @@ table.modlog tr th {
|
||||
.desktop-style div.boardlist:nth-child(1) {
|
||||
background-color: #F0E0D6;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.box {
|
||||
background: #fff;
|
||||
border-color: #800;
|
||||
color: #000;
|
||||
}
|
||||
.box-title {
|
||||
background: #fca;
|
||||
color: #800;
|
||||
}
|
||||
|
||||
table thead th {
|
||||
border-color: #800;
|
||||
background: #fca;
|
||||
color: #800;
|
||||
}
|
||||
table tbody tr:nth-of-type( even ) {
|
||||
background-color: #ede2d4;
|
||||
}
|
||||
|
||||
table.board-list-table .board-uri .board-sfw {
|
||||
color: #800;
|
||||
}
|
||||
tbody.board-list-omitted td {
|
||||
background: #fca;
|
||||
border-color: #800;
|
||||
}
|
111
templates/8chan/boards-search.html
Normal file
111
templates/8chan/boards-search.html
Normal file
@ -0,0 +1,111 @@
|
||||
<main id="boardlist">
|
||||
<section class="description box col col-12">
|
||||
<h2 class="box-title">Global Statistics</h2>
|
||||
<p class="box-content">{% trans %}There are currently <strong>{{boards_public}}</strong> public boards, <strong>{{boards_total}}</strong> total. Site-wide, <strong>{{posts_hour}}</strong> posts have been made in the last hour, with <strong>{{posts_total}}</strong> being made on all active boards since {{founding_date}}.{% endtrans %}</p>
|
||||
{% if uptime %}<p class="box-content">{{uptime}} without interruption</p>{% endif %}
|
||||
<p class="box-content">This page last updated {{page_updated}}.</p>
|
||||
</section>
|
||||
|
||||
<div class="board-list">
|
||||
<aside class="search-container col col-2">
|
||||
<form id="search-form" class="box" method="get" action="/boards.php">
|
||||
<h2 class="box-title">Search</h2>
|
||||
|
||||
<div class="board-search box-content">
|
||||
<label class="search-item search-sfw">
|
||||
<input type="checkbox" id="search-sfw-input" name="sfw" value="1" {% if not search.nsfw %}checked="checked"{% endif %} /> Hide NSFW boards
|
||||
</label>
|
||||
|
||||
<div class="search-item search-title">
|
||||
<input type="text" id="search-title-input" name="title" name="title" value="{{search.title}}" placeholder="Search titles..." />
|
||||
</div>
|
||||
|
||||
<div class="search-item search-lang">
|
||||
<select id="search-lang-input" name="lang">
|
||||
<optgroup label="Popular">
|
||||
<option value="">All languages</option>
|
||||
<option value="en">English</option>
|
||||
<option value="es">Spanish</option>
|
||||
</optgroup>
|
||||
<optgroup label="All">
|
||||
{% for lang_code, lang_name in languages %}
|
||||
<option value="{{lang_code}}">{{lang_name}}</option>
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="search-item search-tag">
|
||||
<input type="text" id="search-tag-input" name="tags" value="{{ search.tags|join(' ') }}" placeholder="Search tags..." />
|
||||
</div>
|
||||
|
||||
<div class="search-item search-submit">
|
||||
<button id="search-submit">Search</button>
|
||||
<span id="search-loading" class="loading-small board-list-loading" style="display: none;"></span>
|
||||
<script type="text/javascript">
|
||||
/* Cheeky hack.
|
||||
DOM Mutation is now depreceated, but board-directory.js fires before this button is added.
|
||||
Since .ready() only fires after the entire page loads, we have this here to disable it as soon
|
||||
as we pass it in the DOM structure.
|
||||
We don't just disable="disable" it because then it would be broken for all non-JS browsers. */
|
||||
document.getElementById( 'search-submit' ).disabled = "disabled";
|
||||
document.getElementById( 'search-loading' ).style.display = "inline-block";
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="tag-list box-content">
|
||||
{{html_tags}}
|
||||
</ul>
|
||||
</form>
|
||||
</aside>
|
||||
|
||||
<section class="board-list col col-10">
|
||||
<table class="board-list-table">
|
||||
<colgroup>
|
||||
<col class="board-meta" />
|
||||
<col class="board-uri" />
|
||||
<col class="board-title" />
|
||||
<col class="board-pph" />
|
||||
<col class="board-max" />
|
||||
<col class="board-unique" />
|
||||
<col class="board-tags" />
|
||||
</colgroup>
|
||||
<thead class="board-list-head">
|
||||
<tr>
|
||||
<th class="board-meta" data-column="meta"></th>
|
||||
<th class="board-uri" data-column="uri">{% trans %}Board{% endtrans %}</th>
|
||||
<th class="board-title" data-column="title">{% trans %}Title{% endtrans %}</th>
|
||||
<th class="board-pph" data-column="pph" title="Posts per hour">{% trans %}PPH{% endtrans %}</th>
|
||||
<th class="board-max" data-column="posts_total">{% trans %}Total posts{% endtrans %}</th>
|
||||
<th class="board-unique" data-column="active" title="Unique IPs to post in the last 72 hours">{% trans %}Active users{% endtrans %}</th>
|
||||
<th class="board-tags" data-column="tags">{% trans %}Tags{% endtrans %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody class="board-list-tbody">{{html_boards}}</tbody>
|
||||
|
||||
<tbody class="board-list-loading">
|
||||
<tr>
|
||||
<td colspan="7" class="loading"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<tbody class="board-list-omitted" data-omitted="{{boards_omitted}}">
|
||||
<tr>
|
||||
<td colspan="7" id="board-list-more">Displaying results <span class="board-page-num">{{search.page + 1}}</span> through <span class="board-page-count">{{ boards|count + search.page}}</span> out of <span class="board-page-total">{{ boards|count + boards_omitted }}</span>. <span class="board-page-loadmore">Click to load more.</span></td>
|
||||
|
||||
{% if boards_omitted > 0 %}
|
||||
<script type="text/javascript">
|
||||
/* Cheeky hack redux.
|
||||
We want to show the loadmore for JS users when we have omitted boards.
|
||||
However, the board-directory.js isn't designed to manipulate the page index on immediate load. */
|
||||
document.getElementById("board-list-more").className = "board-list-hasmore";
|
||||
</script>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
14
templates/8chan/boards-table.html
Normal file
14
templates/8chan/boards-table.html
Normal file
@ -0,0 +1,14 @@
|
||||
{% for board in boards %}
|
||||
<tr>
|
||||
<td class="board-meta">{{ board.locale }}</td>
|
||||
<td class="board-uri"><div class="board-cell">
|
||||
<a href='/{{board['uri']}}/'>/{{board['uri']}}/</a>
|
||||
{% if board['sfw'] %}<i class="fa fa-briefcase board-sfw" title="SFW"></i>{% endif %}
|
||||
</div></td>
|
||||
<td class="board-title"><div class="board-cell" title="Created {{board['time']}} ({{board['ago']}} ago)">{{ board['title'] }}</div></td>
|
||||
<td class="board-pph"><div class="board-cell">{{board['pph']}}</td>
|
||||
<td class="board-max"><div class="board-cell">{{board['posts_total']}}</td>
|
||||
<td class="board-unique"><div class="board-cell">{{board['active']}}</td>
|
||||
<td class="board-tags"><div class="board-cell">{% for tag in board.tags %}<a class="tag-link" href="{{ tag_query }}{{ tag }}">{{ tag }}</a>{% endfor %}</div></td>
|
||||
</tr>
|
||||
{% endfor %}
|
@ -1,162 +1,5 @@
|
||||
<style>
|
||||
th.header {
|
||||
background-image: url(/static/bg.gif);
|
||||
cursor: pointer;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center right;
|
||||
padding-left: 20px;
|
||||
margin-left: -1px;
|
||||
}
|
||||
th.headerSortUp {
|
||||
background-image: url(/static/asc.gif);
|
||||
}
|
||||
th.headerSortDown {
|
||||
background-image: url(/static/desc.gif);
|
||||
}
|
||||
table.modlog tr td.expand-td {
|
||||
position: relative;
|
||||
}
|
||||
table.modlog tr td.expand-td:hover div{
|
||||
background-color: #FFF;
|
||||
position: absolute;
|
||||
width: auto;
|
||||
box-shadow: 0px 0px 5px #000;
|
||||
padding: 0px 0 3px;
|
||||
top: 5px;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
.flag-eo {
|
||||
background-image: url(/static/eo.png);
|
||||
}
|
||||
.flag-en {
|
||||
background-image: url(/static/en.png);
|
||||
}
|
||||
.flag-jbo {
|
||||
background-image: url(/static/jbo.png);
|
||||
}
|
||||
.uri {
|
||||
overflow: hidden; width: 75px; white-space: nowrap;
|
||||
}
|
||||
.tags {
|
||||
overflow: hidden; width: 150px; white-space: nowrap;
|
||||
}
|
||||
.board-name {
|
||||
overflow: hidden; width: 200px; white-space: nowrap;
|
||||
}
|
||||
tr:nth-child(even) { background-color: #D6DAF0 }
|
||||
</style>
|
||||
|
||||
<p style='text-align:center'>{% trans %}There are currently <strong>{{n_boards}}</strong> boards + <strong>{{hidden_boards_total}}</strong> unindexed boards = <strong>{{t_boards}}</strong> total boards. Site-wide, {{total_posts_hour}} posts have been made in the last hour, with {{total_posts}} being made on all active boards since October 23, 2013.{% endtrans %}</p>
|
||||
|
||||
{% if top2k %}
|
||||
<p style='text-align:center'><a href="/boards_full.html">{% trans %}This list only shows the top 2000 boards. Until we can move tag searching onto the server side, click here for the full list.{% endtrans %}</a></p>
|
||||
{% endif %}
|
||||
|
||||
<div style='height:100px; overflow-y:scroll' class="tags-container">
|
||||
<strong class="tags-strong">Tags:</strong>
|
||||
{% for tag, pop in tags %}
|
||||
{% if pop > 1000 %}
|
||||
<a class="tag" href="#" style="font-size:1.75em">{{ tag }}</a>
|
||||
{% elseif pop > 500 %}
|
||||
<a class="tag" href="#" style="font-size:1.5em">{{ tag }}</a>
|
||||
{% elseif pop > 100 %}
|
||||
<a class="tag" href="#" style="font-size:1.25em">{{ tag }}</a>
|
||||
{% else %}
|
||||
<a class="tag" href="#">{{ tag }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<table class="modlog" style="width:auto"><thead>
|
||||
<tr>
|
||||
<th>B</th>
|
||||
<th>{% trans %}Board{% endtrans %}</th>
|
||||
<th>{% trans %}Title{% endtrans %}</th>
|
||||
<th title="Posts per hour">{% trans %}PPH{% endtrans %}</th>
|
||||
<th>{% trans %}Total posts{% endtrans %}</th>
|
||||
<th title="Unique IPs to post in the last 72 hours">{% trans %}Active users{% endtrans %}</th>
|
||||
<th>{% trans %}Tags{% endtrans %}</th>
|
||||
</tr></thead><tbody>
|
||||
{% for board in boards %}
|
||||
<tr>
|
||||
<td>{{ board.img|raw }} {% if board['sfw'] %}<img src="/static/sfw.png" title="Safe for work">{% else %}<img src="/static/nsfw.png" title="Not safe for work">{% endif %}</td>
|
||||
<td><div class="uri"><a href='/{{board['uri']}}/'>/{{board['uri']}}/</a>{{lock|raw}}</div></td>
|
||||
<td class="expand-td" title="Created {{board['time']}} ({{board['ago']}} ago)"><div class="board-name">{{ board['title'] }}</div></td>
|
||||
<td style='text-align:right'>{{board['pph']}}</td>
|
||||
<td style='text-align:right'>{{board['max']}}</td>
|
||||
<td style='text-align:right'>{{board['uniq_ip']}}</td>
|
||||
<td class="expand-td"><div class="tags">{% for tag in board.tags %}<span class="board-tag">{{ tag }}</span> {% endfor %}</div></td>
|
||||
{% endfor %}
|
||||
</tbody></table>
|
||||
<p style='text-align:center'><em>Page last updated: {{last_update}}</em></p>
|
||||
<p style='text-align:center'>{{uptime_p}} without interruption (read)</p>
|
||||
<script>
|
||||
|
||||
$(function() {
|
||||
$('table').tablesorter({sortList: [[5,1]],
|
||||
textExtraction: function(node) {
|
||||
childNode = node.childNodes[0];
|
||||
if (!childNode) { return node.innerHTML; }
|
||||
if (childNode.tagName == 'IMG') {
|
||||
return childNode.getAttribute('class');
|
||||
} else {
|
||||
return (childNode.innerHTML ? childNode.innerHTML : childNode.textContent);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function filter_table(search) {
|
||||
$("tbody>tr").css("display", "table-row");
|
||||
|
||||
if ($('#clear-selection').length === 0) {
|
||||
$('.tags-strong').before('<a href="#" id="clear-selection">[clear selection]</a>');
|
||||
$('#clear-selection').on('click', function(e){
|
||||
e.preventDefault();
|
||||
$("tbody>tr").css("display", "table-row");
|
||||
window.location.hash = '';
|
||||
});
|
||||
}
|
||||
|
||||
window.location.hash = search;
|
||||
|
||||
var tags = $(".board-tag").filter(function() {
|
||||
return $(this).text() === search;
|
||||
});
|
||||
|
||||
$("tbody>tr").css("display", "none");
|
||||
|
||||
tags.parents("tr").css("display", "table-row");
|
||||
|
||||
}
|
||||
|
||||
$("a.tag").on("click", function(e) {
|
||||
e.preventDefault();
|
||||
filter_table($(this).text());
|
||||
});
|
||||
|
||||
$('.tags-strong').before('<label>Filter tags: <input type="text" id="filter-tags"></label> ');
|
||||
|
||||
$('#filter-tags').on('keyup', function(e) {
|
||||
$("a.tag").css("display", "inline-block");
|
||||
|
||||
var search = $(this).val();
|
||||
|
||||
if (!search) return;
|
||||
|
||||
var tags = $("a.tag").filter(function() {
|
||||
return (new RegExp(search)).test($(this).text());
|
||||
});
|
||||
|
||||
$("a.tag").css("display", "none");
|
||||
|
||||
tags.css("display", "inline-block");
|
||||
});
|
||||
|
||||
if (window.location.hash) {
|
||||
filter_table(window.location.hash.replace('#',''));
|
||||
}
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
{% for tag, weight in tags %}
|
||||
<li class="tag-item">
|
||||
<a class="tag-link" href="{{ tag_query }}{{ tag }}" style="font-size: {{weight}}%;">{{tag}}</a>
|
||||
</li>
|
||||
{% endfor %}
|
@ -1,68 +1,80 @@
|
||||
<style>
|
||||
th.header {
|
||||
background-image: url(/static/bg.gif);
|
||||
cursor: pointer;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center right;
|
||||
padding-left: 20px;
|
||||
margin-left: -1px;
|
||||
}
|
||||
th.headerSortUp {
|
||||
background-image: url(/static/asc.gif);
|
||||
}
|
||||
th.headerSortDown {
|
||||
background-image: url(/static/desc.gif);
|
||||
}
|
||||
.flag-eo {
|
||||
background-image: url(/static/eo.png);
|
||||
}
|
||||
.flag-en {
|
||||
background-image: url(/static/en.png);
|
||||
}
|
||||
.flag-jbo {
|
||||
background-image: url(/static/jbo.png);
|
||||
}
|
||||
</style>
|
||||
|
||||
<p style='text-align:center'>{% trans %}There are currently <strong>{{n_boards}}</strong> boards + <strong>{{hidden_boards_total}}</strong> unindexed boards = <strong>{{t_boards}}</strong> total boards. Site-wide, {{total_posts_hour}} posts have been made in the last hour, with {{total_posts}} being made on all active boards since October 23, 2013.{% endtrans %}</p>
|
||||
|
||||
<table class="modlog" style="width:auto"><thead>
|
||||
<tr>
|
||||
<th>L</th>
|
||||
<th>{% trans %}Board{% endtrans %}</th>
|
||||
<th>{% trans %}Board title{% endtrans %}</th>
|
||||
<th>{% trans %}Posts in last hour{% endtrans %}</th>
|
||||
<th>{% trans %}Total posts{% endtrans %}</th>
|
||||
<th>{% trans %}Unique IPs{% endtrans %}</th>
|
||||
<th>{% trans %}Created{% endtrans %}</th>
|
||||
</tr></thead><tbody>
|
||||
{% for board in boards %}
|
||||
<tr>
|
||||
<td>{{ board.img|raw }}</td>
|
||||
<td><a href='/{{board['uri']}}/'>/{{board['uri']}}/</a>{{lock|raw}}</td>
|
||||
<td>{{ board['title'] }}</td>
|
||||
<td style='text-align:right'>{{board['pph']}}</td>
|
||||
<td style='text-align:right'>{{board['max']}}</td>
|
||||
<td style='text-align:right'>{{board['uniq_ip']}}</td>
|
||||
<td>{{board['time']}} ({{board['ago']}} ago)</td></tr>
|
||||
{% endfor %}
|
||||
</tbody></table>
|
||||
<p style='text-align:center'><em>Page last updated: {{last_update}}</em></p>
|
||||
<p style='text-align:center'>{{uptime_p}} without interruption</p>
|
||||
<script>
|
||||
|
||||
$(function() {
|
||||
$('table').tablesorter({sortList: [[5,1]],
|
||||
textExtraction: function(node) {
|
||||
childNode = node.childNodes[0];
|
||||
if (!childNode) { return node.innerHTML; }
|
||||
if (childNode.tagName == 'IMG') {
|
||||
return childNode.getAttribute('class');
|
||||
} else {
|
||||
return (childNode.innerHTML ? childNode.innerHTML : childNode.textContent);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<main id="boardlist">
|
||||
<section class="description box col col-12">
|
||||
<h2 class="box-title">Global Statistics</h2>
|
||||
<p class="box-content">{% trans %}There are currently <strong>{{boards_public}}</strong> public boards, <strong>{{boards_total}}</strong> total. Site-wide, {{posts_hour}} posts have been made in the last hour, with {{posts_total}} being made on all active boards since {{founding_date}}.{% endtrans %}</p>
|
||||
{% if uptime %}<p class="box-content">{{uptime}} without interruption</p>{% endif %}
|
||||
<p class="box-content">This page last updated {{page_updated}}.</p>
|
||||
</section>
|
||||
|
||||
<div class="board-list">
|
||||
<aside class="search-container col col-2">
|
||||
<form id="search-form" class="box" method="post" target="/board-search.php">
|
||||
<h2 class="box-title">Search</h2>
|
||||
|
||||
<div class="board-search box-content">
|
||||
<label class="search-item search-sfw">
|
||||
<input type="checkbox" id="search-sfw-input" name="sfw" checked="checked" /> NSFW boards
|
||||
</label>
|
||||
|
||||
<div class="search-item search-title">
|
||||
<input type="text" id="search-title-input" name="title" placeholder="Search titles..." />
|
||||
</div>
|
||||
|
||||
<div class="search-item search-lang">
|
||||
<select id="search-lang-input" name="lang">
|
||||
<optgroup label="Popular">
|
||||
<option>All languages</option>
|
||||
<option>English</option>
|
||||
<option>Spanish</option>
|
||||
</optgroup>
|
||||
<optgroup label="All">
|
||||
<option>Chinese</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="search-item search-tag">
|
||||
<input type="text" id="search-tag-input" name="tag" placeholder="Search tags..." />
|
||||
</div>
|
||||
|
||||
<div class="search-item search-submit">
|
||||
<button id="search-submit">Search</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="tag-list box-content">
|
||||
<li class="tag-item">
|
||||
<a class="tag-link" href="#">{{html_tags}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</form>
|
||||
</aside>
|
||||
|
||||
<section class="board-list col col-10">
|
||||
<table class="board-list-table">
|
||||
<colgroup>
|
||||
<col class="board-meta" />
|
||||
<col class="board-uri" />
|
||||
<col class="board-title" />
|
||||
<col class="board-pph" />
|
||||
<col class="board-max" />
|
||||
<col class="board-unique" />
|
||||
<col class="board-tags" />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="board-meta" data-column="meta"></th>
|
||||
<th class="board-uri" data-column="uri">{% trans %}Board{% endtrans %}</th>
|
||||
<th class="board-title" data-column="title">{% trans %}Title{% endtrans %}</th>
|
||||
<th class="board-pph" data-column="pph" title="Posts per hour">{% trans %}PPH{% endtrans %}</th>
|
||||
<th class="board-max" data-column="max">{% trans %}Total posts{% endtrans %}</th>
|
||||
<th class="board-unique" data-column="unique" title="Unique IPs to post in the last 72 hours">{% trans %}Active users{% endtrans %}</th>
|
||||
<th class="board-tags" data-column="tags">{% trans %}Tags{% endtrans %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody class="board-list-tbody">{{html_boards}}</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
@ -4,33 +4,6 @@
|
||||
<meta charset="utf-8">
|
||||
<title>∞chan</title>
|
||||
<style type="text/css">
|
||||
/* Responsive helpers */
|
||||
|
||||
.col {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.col-12 { width: 100%; }
|
||||
.col-11 { width: 91.66666667%; }
|
||||
.col-10 { width: 83.33333333%; }
|
||||
.col-9 { width: 75%; }
|
||||
.col-8 { width: 66.66666667%; }
|
||||
.col-7 { width: 58.33333333%; }
|
||||
.col-6 { width: 50%; }
|
||||
.col-5 { width: 41.66666667%; }
|
||||
.col-4 { width: 33.33333333%; }
|
||||
.col-3 { width: 25%; }
|
||||
.col-2 { width: 16.66666667%; }
|
||||
.col-1 { width: 8.33333333%; }
|
||||
|
||||
.left-push {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.right-push {
|
||||
float: right;
|
||||
}
|
||||
|
||||
/* Main */
|
||||
|
||||
* {
|
||||
|
@ -12,7 +12,15 @@
|
||||
<body class="8chan {% if mod %}is-moderator{% else %}is-not-moderator{% endif %} stylesheet-{% if config.default_stylesheet.1 != '' and not mod %}{{ config.default_stylesheet.1 }}{% else %}default{% endif %}">
|
||||
{% if pm %}<div class="top_notice">You have <a href="?/PM/{{ pm.id }}">an unread PM</a>{% if pm.waiting > 0 %}, plus {{ pm.waiting }} more waiting{% endif %}.</div><hr>{% endif %}
|
||||
<header>
|
||||
<h1>{{ title }}</h1>
|
||||
{% if config.site_logo %}
|
||||
<figure id="logo">
|
||||
<a id="logo-link" href="/" title="Return to the front page">
|
||||
<img id="logo-img" src="{{config.site_logo}}" alt="{{config.site_nane}}" />
|
||||
</a>
|
||||
</figure>
|
||||
{% endif %}
|
||||
|
||||
{% if title %}<h1>{{ title }}</h1>{% endif %}
|
||||
<div class="subtitle">
|
||||
{% if subtitle %}
|
||||
{{ subtitle }}
|
||||
|
@ -1,14 +1,17 @@
|
||||
{% if config.allow_delete %}
|
||||
<div class="delete">
|
||||
{% trans %}Delete Post{% endtrans %} [<input title="Delete file only" type="checkbox" name="file" id="delete_file" />
|
||||
<label for="delete_file">{% trans %}File{% endtrans %}</label>] <label for="password">{% trans %}Password{% endtrans %}</label>
|
||||
<div id="post-moderation-fields">
|
||||
{% if config.allow_delete %}
|
||||
<div id="delete-fields">
|
||||
{% trans %}Delete Post{% endtrans %} [<input title="Delete file only" type="checkbox" name="file" id="delete_file" />
|
||||
<label for="delete_file">{% trans %}File{% endtrans %}</label>] <label for="password">{% trans %}Password{% endtrans %}</label>
|
||||
<input id="password" type="password" name="password" size="12" maxlength="18" />
|
||||
<input type="submit" name="delete" value="{% trans %}Delete{% endtrans %}" />
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="delete" style="clear:both">
|
||||
<label for="reason">{% trans %}Reason{% endtrans %}</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div id="report-fields">
|
||||
<label for="reason">{% trans %}Reason{% endtrans %}</label>
|
||||
<input id="reason" type="text" name="reason" size="20" maxlength="30" />
|
||||
[<input title="Global Report" type="checkbox" name="global" id="global_report" /><label for="global_report" title="Report rule violation (CP, etc) to global staff">{% trans %}Global{% endtrans %}</label>]
|
||||
<input type="submit" name="report" value="{% trans %}Report{% endtrans %}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -20,12 +20,25 @@
|
||||
if ($action == 'all') {
|
||||
foreach ($boards as $board) {
|
||||
$b = new Catalog();
|
||||
$b->build($settings, $board);
|
||||
|
||||
if ($config['smart_build']) {
|
||||
file_unlink($config['dir']['home'] . $board . '/catalog.html');
|
||||
}
|
||||
else {
|
||||
$b->build($settings, $board);
|
||||
}
|
||||
|
||||
if (php_sapi_name() === "cli") echo "Rebuilding $board catalog...\n";
|
||||
}
|
||||
} elseif ($action == 'post-thread' || ($settings['update_on_posts'] && $action == 'post') || ($settings['update_on_posts'] && $action == 'post-delete') && (in_array($board, $boards) | $settings['all'])) {
|
||||
$b = new Catalog();
|
||||
$b->build($settings, $board);
|
||||
|
||||
if ($config['smart_build']) {
|
||||
file_unlink($config['dir']['home'] . $board . '/catalog.html');
|
||||
}
|
||||
else {
|
||||
$b->build($settings, $board);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,8 +46,12 @@
|
||||
class Catalog {
|
||||
public function build($settings, $board_name) {
|
||||
global $config, $board;
|
||||
|
||||
openBoard($board_name);
|
||||
|
||||
if ($board['uri'] != $board_name) {
|
||||
if (!openBoard($board_name)) {
|
||||
error(sprintf(_("Board %s doesn't exist"), $board_name));
|
||||
}
|
||||
}
|
||||
|
||||
$recent_images = array();
|
||||
$recent_posts = array();
|
||||
|
@ -24,8 +24,14 @@
|
||||
|
||||
$this->excluded = explode(' ', $settings['exclude']);
|
||||
|
||||
if ($action == 'all' || $action == 'post' || $action == 'post-thread' || $action == 'post-delete')
|
||||
file_write($config['dir']['home'] . $settings['html'], $this->homepage($settings));
|
||||
if ($action == 'all' || $action == 'post' || $action == 'post-thread' || $action == 'post-delete') {
|
||||
if ($config['smart_build']) {
|
||||
file_unlink($config['dir']['home'] . $settings['html']);
|
||||
}
|
||||
else {
|
||||
file_write($config['dir']['home'] . $settings['html'], $this->homepage($settings));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build news page
|
||||
|
@ -10,30 +10,37 @@
|
||||
// - boards (board list changed)
|
||||
// - post (a post has been made)
|
||||
// - thread (a thread has been made)
|
||||
|
||||
if ($action != 'all') {
|
||||
if ($action != 'post-thread' && $action != 'post-delete')
|
||||
return;
|
||||
|
||||
if ($action != 'post-thread' && $action != 'post-delete')
|
||||
return;
|
||||
|
||||
if (isset($settings['regen_time']) && $settings['regen_time'] > 0) {
|
||||
if ($last_gen = @filemtime($settings['path'])) {
|
||||
if (time() - $last_gen < (int)$settings['regen_time'])
|
||||
return; // Too soon
|
||||
if (isset($settings['regen_time']) && $settings['regen_time'] > 0) {
|
||||
if ($last_gen = @filemtime($settings['path'])) {
|
||||
if (time() - $last_gen < (int)$settings['regen_time'])
|
||||
return; // Too soon
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$boards = explode(' ', $settings['boards']);
|
||||
|
||||
$threads = array();
|
||||
|
||||
foreach ($boards as $board) {
|
||||
$query = query(sprintf("SELECT `id` AS `thread_id`, (SELECT `time` FROM ``posts_%s`` WHERE `thread` = `thread_id` OR `id` = `thread_id` ORDER BY `time` DESC LIMIT 1) AS `lastmod` FROM ``posts_%s`` WHERE `thread` IS NULL", $board, $board)) or error(db_error());
|
||||
$threads[$board] = $query->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($config['smart_build']) {
|
||||
file_unlink($settings['path']);
|
||||
}
|
||||
else {
|
||||
$boards = explode(' ', $settings['boards']);
|
||||
|
||||
$threads = array();
|
||||
|
||||
foreach ($boards as $board) {
|
||||
$query = query(sprintf("SELECT `id` AS `thread_id`, (SELECT `time` FROM ``posts_%s`` WHERE `thread` = `thread_id` OR `id` = `thread_id` ORDER BY `time` DESC LIMIT 1) AS `lastmod` FROM ``posts_%s`` WHERE `thread` IS NULL", $board, $board)) or error(db_error());
|
||||
$threads[$board] = $query->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
file_write($settings['path'], Element('themes/sitemap/sitemap.xml', Array(
|
||||
'settings' => $settings,
|
||||
'config' => $config,
|
||||
'threads' => $threads,
|
||||
'boards' => $boards,
|
||||
)));
|
||||
file_write($settings['path'], Element('themes/sitemap/sitemap.xml', Array(
|
||||
'settings' => $settings,
|
||||
'config' => $config,
|
||||
'threads' => $threads,
|
||||
'boards' => $boards,
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
@ -49,6 +49,7 @@
|
||||
function ukko_install($settings) {
|
||||
if (!file_exists($settings['uri']))
|
||||
@mkdir($settings['uri'], 0777) or error("Couldn't create " . $settings['uri'] . ". Check permissions.", true);
|
||||
file_write($settings['uri'] . '/ukko.js', Element('themes/ukko/ukko.js', array()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,8 @@
|
||||
require 'info.php';
|
||||
|
||||
function ukko_build($action, $settings) {
|
||||
global $config;
|
||||
|
||||
$ukko = new ukko();
|
||||
$ukko->settings = $settings;
|
||||
|
||||
@ -9,8 +11,12 @@
|
||||
return;
|
||||
}
|
||||
|
||||
file_write($settings['uri'] . '/index.html', $ukko->build());
|
||||
file_write($settings['uri'] . '/ukko.js', Element('themes/ukko/ukko.js', array()));
|
||||
if ($config['smart_build']) {
|
||||
file_unlink($settings['uri'] . '/index.html');
|
||||
}
|
||||
else {
|
||||
file_write($settings['uri'] . '/index.html', $ukko->build());
|
||||
}
|
||||
}
|
||||
|
||||
class ukko {
|
||||
|
@ -22,6 +22,7 @@
|
||||
<title>{{ board.url }} - {{ meta_subject }}</title>
|
||||
</head>
|
||||
<body class="8chan {% if mod %}is-moderator{% else %}is-not-moderator{% endif %}" data-stylesheet="{% if config.default_stylesheet.1 != '' and not mod %}{{ config.default_stylesheet.1 }}{% else %}default{% endif %}">
|
||||
<a name="top"></a>
|
||||
{{ boardlist.top }}
|
||||
{% if pm %}<div class="top_notice">You have <a href="?/PM/{{ pm.id }}">an unread PM</a>{% if pm.waiting > 0 %}, plus {{ pm.waiting }} more waiting{% endif %}.</div><hr />{% endif %}
|
||||
{% if config.url_banner %}<img class="board_image" src="{{ config.url_banner }}?board={{ board.uri|url_encode }}" {% if config.banner_width or config.banner_height %}style="{% if config.banner_width %}width:{{ config.banner_width }}px{% endif %};{% if config.banner_width %}height:{{ config.banner_height }}px{% endif %}" {% endif %}alt="" />{% endif %}
|
||||
@ -53,19 +54,30 @@
|
||||
|
||||
{% if config.global_message %}<hr /><div class="blotter">{{ config.global_message }}</div>{% endif %}
|
||||
<hr />
|
||||
|
||||
<form name="postcontrols" action="{{ config.post_url }}" method="post">
|
||||
<input type="hidden" name="board" value="{{ board.uri }}" />
|
||||
{% if mod %}<input type="hidden" name="mod" value="1" />{% endif %}
|
||||
{{ body }}
|
||||
{% include 'report_delete.html' %}
|
||||
<input type="hidden" name="board" value="{{ board.uri }}" />
|
||||
{% if mod %}<input type="hidden" name="mod" value="1" />{% endif %}
|
||||
|
||||
{{ body }}
|
||||
|
||||
<div id="thread-interactions">
|
||||
<span id="thread-links">
|
||||
<a id="thread-return" href="{{ return }}">[{% trans %}Return{% endtrans %}]</a>
|
||||
<a id="thread-top" href="#top">[{% trans %}Go to top{% endtrans %}]</a>
|
||||
<a id="thread-catalog" href="{{ config.root }}{{ board.dir }}{{ config.catalog_link }}">[{% trans %}Catalog{% endtrans %}]</a>
|
||||
</span>
|
||||
|
||||
<span id="thread-quick-reply">
|
||||
<a id="link-quick-reply" href="#">[{% trans %}Post a Reply{% endtrans %}]</a>
|
||||
</span>
|
||||
|
||||
{% include 'report_delete.html' %}
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
</form>
|
||||
|
||||
<span id="thread-links">
|
||||
<a id="thread-return" href="{{ return }}">[{% trans %}Return{% endtrans %}]</a>
|
||||
<a id="thread-top" href="#" style="padding-left: 10px">[{% trans %}Go to top{% endtrans %}]</a>
|
||||
<a id="thread-catalog" style="padding-left: 10px" href="{{ config.root }}{{ board.dir }}{{ config.catalog_link }}">[{% trans %}Catalog{% endtrans %}]</a>
|
||||
</span>
|
||||
|
||||
{{ boardlist.bottom }}
|
||||
|
||||
{% if board.uri not in config.banned_ad_boards %}
|
||||
|
0
tmp/cache/.gitkeep
vendored
Normal file
0
tmp/cache/.gitkeep
vendored
Normal file
0
tmp/locks/.gitkeep
Normal file
0
tmp/locks/.gitkeep
Normal file
125
tools/migrate_board_stats.php
Normal file
125
tools/migrate_board_stats.php
Normal file
@ -0,0 +1,125 @@
|
||||
<?php
|
||||
require dirname(__FILE__) . '/inc/cli.php';
|
||||
|
||||
/* Convert AI value to colun value for ez access */
|
||||
// Add column `posts_total` to `boards`.
|
||||
// This can potentially error if ran multiple times.. but that shouldn't kill the script
|
||||
echo "Altering `boards` to add `posts_total`...\n";
|
||||
query( "ALTER TABLE `boards` ADD COLUMN `posts_total` INT(11) UNSIGNED NOT NULL DEFAULT 0" );
|
||||
|
||||
// Set the value for posts_total for each board.
|
||||
echo "Updating `boards` to include `posts_total` values...\n";
|
||||
$tablePrefix = "{$config['db']['prefix']}posts_";
|
||||
|
||||
$aiQuery = prepare("SELECT `TABLE_NAME`, `AUTO_INCREMENT` FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = \"{$config['db']['database']}\"");
|
||||
$aiQuery->execute() or error(db_error($aiQuery));
|
||||
$aiResult = $aiQuery->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
foreach ($aiResult as $aiRow) {
|
||||
$uri = str_replace( $tablePrefix, "", $aiRow['TABLE_NAME'] );
|
||||
$posts = (int)($aiRow['AUTO_INCREMENT'] - 1); // Don't worry! The column is unsigned. -1 becomes 0.
|
||||
|
||||
echo " {$uri} has {$posts} post".($posts!=1?"s":"")."\n";
|
||||
query( "UPDATE `boards` SET `posts_total`={$posts} WHERE `uri`=\"{$uri}\";" );
|
||||
}
|
||||
|
||||
unset( $aiQuery, $aiResult, $uri, $posts );
|
||||
|
||||
/* Add statistics table and transmute post information to that */
|
||||
// Add `board_stats`
|
||||
echo "Adding `board_stats` ...\n";
|
||||
query(
|
||||
"CREATE TABLE IF NOT EXISTS ``board_stats`` (
|
||||
`stat_uri` VARCHAR(58) NOT NULL,
|
||||
`stat_hour` INT(11) UNSIGNED NOT NULL,
|
||||
`post_count` INT(11) UNSIGNED NULL,
|
||||
`post_id_array` TEXT NULL,
|
||||
`author_ip_count` INT(11) UNSIGNED NULL,
|
||||
`author_ip_array` TEXT NULL,
|
||||
PRIMARY KEY (`stat_uri`, `stat_hour`)
|
||||
);"
|
||||
);
|
||||
|
||||
$boards = listBoards();
|
||||
|
||||
echo "Translating posts to stats ...\n";
|
||||
foreach ($boards as $board) {
|
||||
$postQuery = prepare("SELECT `id`, `time`, `ip` FROM ``posts_{$board['uri']}``");
|
||||
$postQuery->execute() or error(db_error($postQuery));
|
||||
$postResult = $postQuery->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Determine the number of posts for each hour.
|
||||
$postHour = array();
|
||||
|
||||
foreach ($postResult as $post) {
|
||||
// Winds back timestamp to last hour. (1428947438 -> 1428944400)
|
||||
$postHourTime = (int)($post['time'] / 3600) * 3600;
|
||||
|
||||
if (!isset($postHour[ $postHourTime ])) {
|
||||
$postHour[ $postHourTime ] = array();
|
||||
}
|
||||
|
||||
$postDatum = &$postHour[ $postHourTime ];
|
||||
|
||||
// Add to post count.
|
||||
if (!isset($postDatum['post_count'])) {
|
||||
$postDatum['post_count'] = 1;
|
||||
}
|
||||
else {
|
||||
++$postDatum['post_count'];
|
||||
}
|
||||
|
||||
// Add to post id array.
|
||||
if (!isset($postDatum['post_id_array'])) {
|
||||
$postDatum['post_id_array'] = array( (int)$post['id'] );
|
||||
}
|
||||
else {
|
||||
$postDatum['post_id_array'][] = (int)$post['id'];
|
||||
}
|
||||
|
||||
// Add to ip array.
|
||||
if (!isset($postDatum['author_ip_array'])) {
|
||||
$postDatum['author_ip_array'] = array();
|
||||
}
|
||||
|
||||
$postDatum['author_ip_array'][ less_ip( $post['ip'] ) ] = 1;
|
||||
|
||||
unset( $postHourTime );
|
||||
}
|
||||
|
||||
// Prep data for insert.
|
||||
foreach ($postHour as $postHourTime => &$postHourData) {
|
||||
$postDatum = &$postHour[ $postHourTime ];
|
||||
|
||||
// Serialize arrays for TEXT insert.
|
||||
$postDatum['post_id_array'] = str_replace( "\"", "\\\"", serialize( $postDatum['post_id_array'] ) );
|
||||
$postDatum['author_ip_count'] = count( array_keys( $postDatum['author_ip_array'] ) );
|
||||
$postDatum['author_ip_array'] = str_replace( "\"", "\\\"", serialize( array_keys( $postDatum['author_ip_array'] ) ) );
|
||||
}
|
||||
|
||||
// Bash this shit together into a set of insert statements.
|
||||
$statsInserts = array();
|
||||
|
||||
foreach ($postHour as $postHourTime => $postHourData) {
|
||||
$statsInserts[] = "(\"{$board['uri']}\", \"{$postHourTime}\", \"{$postHourData['post_count']}\", \"{$postHourData['post_id_array']}\", \"{$postHourData['author_ip_count']}\", \"{$postHourData['author_ip_array']}\" )";
|
||||
}
|
||||
|
||||
if (count($statsInserts) > 0) {
|
||||
$statsInsert = "VALUES" . implode( ", ", $statsInserts );
|
||||
echo " {$board['uri']} is building " . count($statsInserts) . " stat rows.\n";
|
||||
|
||||
// Insert this data into our statistics table.
|
||||
$postStatQuery = prepare(
|
||||
"REPLACE INTO ``board_stats`` (stat_uri, stat_hour, post_count, post_id_array, author_ip_count, author_ip_array) {$statsInsert}"
|
||||
);
|
||||
$postStatQuery->execute() or error(db_error($postStatQuery));
|
||||
}
|
||||
else {
|
||||
echo " {$board['uri']} has no posts!\n";
|
||||
}
|
||||
|
||||
unset( $postQuery, $postResult, $postStatQuery, $postHour, $statsInserts, $statsInsert );
|
||||
}
|
||||
|
||||
|
||||
echo "Done! ^^;";
|
Loading…
x
Reference in New Issue
Block a user