diff --git a/board-search.php b/board-search.php
index 5f172036..c156fffb 100644
--- a/board-search.php
+++ b/board-search.php
@@ -26,6 +26,7 @@ $search = array(
'lang' => false,
'nsfw' => true,
'tags' => false,
+ 'time' => ( (int)( time() / 3600 ) * 3600 ) - 3600,
'title' => false,
);
@@ -43,6 +44,12 @@ if (isset( $_GET['lang'] ) && $_GET['lang'] != "" && isset($languages[$search['l
if (isset( $_GET['tags'] ) && $_GET['tags'] != "") {
$search['tags'] = $_GET['tags'];
}
+
+// 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'];
@@ -80,6 +87,8 @@ foreach ($boards as $board) {
$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)
@@ -103,16 +112,33 @@ foreach ($response['boards'] as $boardUri => &$board) {
}
}
+unset( $boardTags );
+
/* Activity Fetching */
-$boardActivity = fetchBoardActivity( array_keys( $response['boards'] ) );
+$boardActivity = fetchBoardActivity( array_keys( $response['boards'] ), $search['time'], true );
$response['tags'] = array();
// 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'] = (int) $boardActivity['active'][ $boardUri ];
- $board['pph'] = (int) $boardActivity['average'][ $boardUri ];
+ $board['active'] = 0;
+ $board['pph'] = 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 );
+
+ unset( $precision );
+ }
if (isset($board['tags']) && count($board['tags']) > 0) {
foreach ($board['tags'] as $tag) {
@@ -126,6 +152,8 @@ foreach ($response['boards'] as $boardUri => &$board) {
}
}
+unset( $boardActivity );
+
// Sort boards by their popularity, then by their total posts.
$boardActivityValues = array();
$boardTotalPostsValues = array();
@@ -152,6 +180,9 @@ if (count($response['tags']) > 0) {
}
+/* Include our interpreted search terms. */
+$response['search'] = $search;
+
/* (Please) Respond */
if (!$Included) {
$json = json_encode( $response );
diff --git a/boards.php b/boards.php
index afd96841..06e18086 100644
--- a/boards.php
+++ b/boards.php
@@ -1,9 +1,10 @@
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); */
-
-$boardQuery = prepare("SELECT COUNT(1) AS 'boards_total', COUNT(indexed) AS 'boards_public' FROM ``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_total = $boardResult['boards_total'];
-$boards_public = $boardResult['boards_public'];
-$boards_hidden = $boardResult['boards_total'] - $boardResult['boards_public'];
+$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 );
-$posts_hour = 0;
-$posts_total = 0;
+$posts_hour = number_format( fetchBoardActivity(), 0 );
+$posts_total = number_format( $boardResult['posts_total'], 0 );
/* Create and distribute page */
$boardsHTML = Element("8chan/boards-table.html", array(
@@ -66,6 +58,7 @@ $searchHTML = Element("8chan/boards-search.html", array(
"posts_hour" => $posts_hour,
"posts_total" => $posts_total,
+ "founding_date" => $founding_date,
"page_updated" => date('r'),
"uptime" => shell_exec('uptime -p'),
diff --git a/inc/functions.php b/inc/functions.php
index 7b572dfc..98f07377 100755
--- a/inc/functions.php
+++ b/inc/functions.php
@@ -833,18 +833,65 @@ function loadBoardConfig( $uri ) {
return $config;
}
-function fetchBoardActivity( $uris ) {
+function fetchBoardActivity( array $uris = array(), $forTime = false, $detailed = false ) {
global $config;
- $boardActivity = array();
- //$uris = "\"" . implode( (array) $uris, "\",\"" ) . "\"";
+ // 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.
+ $forHour = ( (int)( $forTime / 3600 ) * 3600 ) - 3600;
- foreach ($uris as $uri) {
- $random = 0;//rand( -1000, 1000 );
- if( $random < 0 ) $random = 0;
+ $boardActivity = array(
+ 'active' => 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`, `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);
- $boardActivity['active'][ $uri ] = $random;
- $boardActivity['average'][ $uri ] = ($random * 72) / 72;
+
+ // Format the results.
+ foreach ($bsResult as $bsRow) {
+ if (!isset($boardActivity['active'][$bsRow['stat_uri']])) {
+ $boardActivity['active'][$bsRow['stat_uri']] = unserialize( $bsRow['author_ip_array'] );
+ $boardActivity['average'][$bsRow['stat_uri']] = $bsRow['post_count'];
+ }
+ else {
+ $boardActivity['active'][$bsRow['stat_uri']] = array_merge( $boardActivity['active'][$bsRow['stat_uri']], unserialize( $bsRow['author_ip_array'] ) );
+ $boardActivity['average'][$bsRow['stat_uri']] = $bsRow['post_count'];
+ }
+ }
+
+ foreach ($boardActivity['active'] as &$activity) {
+ $activity = count( array_unique( $activity ) );
+ }
+ foreach ($boardActivity['average'] as &$activity) {
+ $activity /= 72;
+ }
+ }
+ // Simple return.
+ else {
+ $bsQuery = prepare("SELECT SUM(`post_count`) AS `post_count` FROM ``board_stats`` WHERE {$uriSearch} ( `stat_hour` <= :hour 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);
+
+ $boardActivity = $bsResult[0]['post_count'];
}
return $boardActivity;
@@ -854,7 +901,7 @@ function fetchBoardTags( $uris ) {
global $config;
$boardTags = array();
- $uris = "\"{$config['db']['prefix']}" . implode( (array) $uris, "\",\"" ) . "\"";
+ $uris = "\"" . implode( (array) $uris, "\",\"" ) . "\"";
$tagQuery = prepare("SELECT * FROM ``board_tags`` WHERE `uri` IN ({$uris})");
$tagQuery->execute() or error(db_error($tagQuery));
@@ -1090,70 +1137,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']);
@@ -1163,12 +1210,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();
}
@@ -1462,6 +1509,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;
diff --git a/post.php b/post.php
index 177c2dba..38898e11 100644
--- a/post.php
+++ b/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,7 +233,7 @@ elseif (isset($_POST['post'])) {
$_POST['subject'] = '';
if (!isset($_POST['password']))
- $_POST['password'] = '';
+ $_POST['password'] = '';
if (isset($_POST['thread'])) {
$post['op'] = false;
@@ -708,7 +713,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
@@ -906,10 +911,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))
@@ -1005,17 +1015,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']);
diff --git a/stylesheets/style.css b/stylesheets/style.css
index 916067fc..ff29bb2a 100644
--- a/stylesheets/style.css
+++ b/stylesheets/style.css
@@ -1363,15 +1363,19 @@ table.board-list-table .board-title {
}
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 div.board-cell {
@@ -1435,4 +1439,14 @@ li.tag-item {
list-style: none;
margin: 0;
padding: 0 0.6em 0 0;
+}
+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;
}
\ No newline at end of file
diff --git a/templates/8chan/boards-search.html b/templates/8chan/boards-search.html
index e7519dd0..57f5834c 100644
--- a/templates/8chan/boards-search.html
+++ b/templates/8chan/boards-search.html
@@ -72,6 +72,8 @@
{{html_boards}}
+
+
diff --git a/templates/8chan/boards-table.html b/templates/8chan/boards-table.html
index a43f3d67..f3451ae1 100644
--- a/templates/8chan/boards-table.html
+++ b/templates/8chan/boards-table.html
@@ -6,6 +6,6 @@
{{board['pph']}} |
{{board['posts_total']}} |
{{board['active']}} |
- {% for tag in board.tags %}{{ tag }} {% endfor %} |
+ {% for tag in board.tags %} {{ tag }}{% endfor %} |
{% endfor %}
\ No newline at end of file
diff --git a/templates/8chan/boards.html b/templates/8chan/boards.html
index 4687a6dd..db6b2682 100644
--- a/templates/8chan/boards.html
+++ b/templates/8chan/boards.html
@@ -1,36 +1,80 @@
-
-
{% trans %}There are currently {{n_boards}} boards + {{hidden_boards_total}} unindexed boards = {{t_boards}} 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 %}
+
+
+ Global Statistics
+ {% trans %}There are currently {{boards_public}} public boards, {{boards_total}} 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 %}
+ {% if uptime %}{{uptime}} without interruption
{% endif %}
+ This page last updated {{page_updated}}.
+
-
-
-
- L |
- {% trans %}Board{% endtrans %} |
- {% trans %}Board title{% endtrans %} |
- {% trans %}Posts in last hour{% endtrans %} |
- {% trans %}Total posts{% endtrans %} |
- {% trans %}Unique IPs{% endtrans %} |
- {% trans %}Created{% endtrans %} |
-
-
-
-
-
-
- {% for board in boards %}
-
- {{ board.img|raw }} |
- /{{board['uri']}}/{{lock|raw}} |
- {{ board['title'] }} |
- {{board['pph']}} |
- {{board['max']}} |
- {{board['uniq_ip']}} |
- {{board['time']}} ({{board['ago']}} ago) |
-
- {% endfor %}
-
-
-
- Page last updated: {{last_update}}
- {{uptime_p}} without interruption
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ {% trans %}Board{% endtrans %} |
+ {% trans %}Title{% endtrans %} |
+ {% trans %}PPH{% endtrans %} |
+ {% trans %}Total posts{% endtrans %} |
+ {% trans %}Active users{% endtrans %} |
+ {% trans %}Tags{% endtrans %} |
+
+
+
+ {{html_boards}}
+
+
+
+
\ No newline at end of file
diff --git a/tools/migrate_board_stats.php b/tools/migrate_board_stats.php
index 8ef7b9b4..8184e037 100644
--- a/tools/migrate_board_stats.php
+++ b/tools/migrate_board_stats.php
@@ -79,11 +79,10 @@ foreach ($boards as $board) {
// Add to ip array.
if (!isset($postDatum['author_ip_array'])) {
- $postDatum['author_ip_array'][ less_ip( $post['ip'] ) ] = 1;
+ $postDatum['author_ip_array'] = array();
}
- // Count ip array.
- $postDatum['author_ip_count'] = count( $postDatum['post_id_array'] );
+ $postDatum['author_ip_array'][ less_ip( $post['ip'] ) ] = 1;
unset( $postHourTime );
}
@@ -93,7 +92,8 @@ foreach ($boards as $board) {
$postDatum = &$postHour[ $postHourTime ];
// Serialize arrays for TEXT insert.
- $postDatum['post_id_array' ] = str_replace( "\"", "\\\"", serialize( $postDatum['post_id_array'] ) );
+ $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'] ) ) );
}