mirror of
https://github.com/vichan-devel/vichan.git
synced 2025-01-19 01:24:05 +01:00
This commit is contained in:
commit
b59999a05d
319
board-search.php
Normal file
319
board-search.php
Normal file
@ -0,0 +1,319 @@
|
||||
<?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 = 4 - strlen( $boardActivity['average'][ $boardUri ] );
|
||||
|
||||
if( $precision < 0 ) {
|
||||
$precision = 0;
|
||||
}
|
||||
|
||||
$board['pph'] = round( $boardActivity['average'][ $boardUri ], 2 );
|
||||
$board['ppd'] = round( $boardActivity['today'][ $boardUri ], 2 );
|
||||
|
||||
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') {
|
||||
$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 );
|
||||
}
|
||||
else {
|
||||
$response['omitted'] = 0;
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
214
boards.php
214
boards.php
@ -1,152 +1,106 @@
|
||||
<?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 */
|
||||
$boardsHTML = Element("8chan/boards-table.html", array(
|
||||
"config" => $config,
|
||||
"boards" => $boards,
|
||||
"tag_query" => $tag_query,
|
||||
|
||||
)
|
||||
);
|
||||
|
||||
array_splice($boards, 48);
|
||||
$tagsHTML = Element("8chan/boards-tags.html", array(
|
||||
"config" => $config,
|
||||
"tags" => $tags,
|
||||
"tag_query" => $tag_query,
|
||||
|
||||
)
|
||||
);
|
||||
|
||||
$boards = array_values($boards);
|
||||
$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
|
||||
)
|
||||
);
|
||||
|
||||
file_write("boards-top20.json", json_encode($boards));
|
||||
file_write("boards.html", $html_top2k);
|
||||
file_write("boards_full.html", $html);
|
||||
echo 'Done';
|
||||
$pageHTML = Element("page.html", array(
|
||||
"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 (count($_GET) == 0) {
|
||||
// Preserves the JSON output format of [{board},{board}].
|
||||
$nonAssociativeBoardList = array_values($boards);
|
||||
|
||||
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;
|
@ -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))
|
||||
|
@ -516,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
|
||||
@ -628,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.
|
||||
*
|
||||
@ -780,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) {
|
||||
@ -798,6 +819,139 @@ 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) {
|
||||
$boardActivity['active'][$bsRow['stat_uri']] = unserialize( $bsRow['author_ip_array'] );
|
||||
}
|
||||
else {
|
||||
$boardActivity['active'][$bsRow['stat_uri']] = array();
|
||||
}
|
||||
|
||||
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['average'][$bsRow['stat_uri']] = $bsRow['post_count'];
|
||||
}
|
||||
else {
|
||||
if ($bsRow['stat_hour'] == $forHour) {
|
||||
$boardActivity['active'][$bsRow['stat_uri']] = array_merge( $boardActivity['active'][$bsRow['stat_uri']], unserialize( $bsRow['author_ip_array'] ) );
|
||||
}
|
||||
|
||||
if ($bsRow['stat_hour'] <= $forHour && $bsRow['stat_hour'] >= $yesterHour) {
|
||||
$boardActivity['today'][$bsRow['stat_uri']] += $bsRow['post_count'];
|
||||
}
|
||||
|
||||
$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){
|
||||
@ -1010,70 +1164,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']);
|
||||
@ -1083,12 +1237,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();
|
||||
}
|
||||
|
||||
@ -1382,6 +1536,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;
|
||||
|
||||
@ -2074,8 +2287,27 @@ function markup(&$body, $track_cites = false, $op = false) {
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
$bodyNew .= "<p class=\"body-line {$paragraphDirection}\">" . $paragraph . "</p>";
|
||||
}
|
||||
|
||||
$body = $bodyNew;
|
||||
}
|
||||
else {
|
||||
$body = preg_replace("/\n/", '<br/>', $body);
|
||||
}
|
||||
|
||||
if ($config['markup_repair_tidy']) {
|
||||
$tidy = new tidy();
|
||||
@ -2132,6 +2364,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 = '';
|
||||
|
@ -126,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';
|
||||
@ -140,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';
|
||||
@ -152,25 +154,28 @@
|
||||
$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(
|
||||
@ -180,6 +185,32 @@
|
||||
'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;
|
||||
@ -200,6 +231,10 @@ $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-config.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;
|
||||
}
|
||||
|
||||
|
@ -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']);
|
||||
|
||||
|
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 |
@ -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;
|
||||
}
|
||||
@ -297,12 +363,13 @@ p.intro a {
|
||||
color: maroon;
|
||||
}
|
||||
|
||||
div.delete {
|
||||
float: right;
|
||||
}
|
||||
|
||||
div.post.reply p {
|
||||
margin: 0.3em 0 0 0;
|
||||
display: block;
|
||||
margin: 0;
|
||||
|
||||
line-height: 1.16em;
|
||||
font-size: 13px;
|
||||
min-height: 1.16em;
|
||||
}
|
||||
|
||||
div.post.reply div.body {
|
||||
@ -347,12 +414,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;
|
||||
@ -536,50 +605,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 +632,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 +1024,6 @@ span.pln {
|
||||
color:grey;
|
||||
}
|
||||
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
p.intro {
|
||||
clear: none;
|
||||
@ -1021,8 +1034,169 @@ span.pln {
|
||||
}
|
||||
}
|
||||
|
||||
/* threadwatcher */
|
||||
/* === SITE-WIDE ASSETS === */
|
||||
#logo {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 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 +1239,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 +1267,6 @@ div.mix {
|
||||
}
|
||||
|
||||
/* Quick reply (why was most of this ever in the script?) */
|
||||
|
||||
#quick-reply {
|
||||
position: fixed;
|
||||
right: 5%;
|
||||
@ -1259,3 +1432,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;
|
||||
}
|
||||
}
|
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 */
|
||||
|
||||
* {
|
||||
|
@ -177,7 +177,7 @@ function highlightReply(id, event) {
|
||||
post.className += ' highlighted';
|
||||
|
||||
if (history.pushState) {
|
||||
history.pushState(null, null, window.document.location.origin + window.document.location.pathname + window.document.location.search + '#' + id);
|
||||
history.pushState(null, null, window.document.location.protocol + "//" + window.document.location.host + window.document.location.pathname + window.document.location.search + '#' + id);
|
||||
} else {
|
||||
window.location.hash = id;
|
||||
}
|
||||
|
@ -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>
|
@ -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 %}
|
||||
|
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