2015-04-13 06:24:55 +02:00
|
|
|
<?php
|
|
|
|
|
2015-04-13 17:40:45 +02:00
|
|
|
// 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";
|
|
|
|
}
|
2015-04-13 06:24:55 +02:00
|
|
|
|
|
|
|
$CanViewUnindexed = isset($mod["type"]) && $mod["type"] <= GlobalVolunteer;
|
|
|
|
|
|
|
|
|
|
|
|
/* The expected output of this page is JSON. */
|
|
|
|
$response = array();
|
|
|
|
|
|
|
|
|
|
|
|
/* Determine search parameters from $_GET */
|
|
|
|
$search = array(
|
2015-04-13 21:36:38 +02:00
|
|
|
'lang' => false,
|
|
|
|
'nsfw' => true,
|
2015-04-15 21:46:48 +02:00
|
|
|
'page' => 0,
|
2015-04-13 21:36:38 +02:00
|
|
|
'tags' => false,
|
2015-04-14 17:01:32 +02:00
|
|
|
'time' => ( (int)( time() / 3600 ) * 3600 ) - 3600,
|
2015-04-13 21:36:38 +02:00
|
|
|
'title' => false,
|
2015-04-15 14:02:11 +02:00
|
|
|
|
|
|
|
'index' => count( $_GET ) == 0,
|
2015-04-13 06:24:55 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
// Include NSFW boards?
|
2015-04-13 21:36:38 +02:00
|
|
|
if (isset( $_GET['sfw'] ) && $_GET['sfw'] != "") {
|
|
|
|
$search['nsfw'] = !$_GET['sfw'];
|
2015-04-13 06:24:55 +02:00
|
|
|
}
|
|
|
|
|
2015-04-15 21:46:48 +02:00
|
|
|
// Bringing up more results
|
|
|
|
if (isset( $_GET['page'] ) && $_GET['page'] != "") {
|
|
|
|
$search['page'] = (int) $_GET['page'];
|
|
|
|
|
|
|
|
if ($search['page'] < 0) {
|
|
|
|
$search['page'] = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-13 06:24:55 +02:00
|
|
|
// Include what language (if the language is not blank and we recognize it)?
|
2015-04-19 05:10:13 +02:00
|
|
|
if (isset( $_GET['lang'] ) && $_GET['lang'] != "" && isset($config['languages'][$_GET['lang']])) {
|
2015-04-13 06:24:55 +02:00
|
|
|
$search['lang'] = $_GET['lang'];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Include what tag?
|
2015-04-13 21:36:38 +02:00
|
|
|
if (isset( $_GET['tags'] ) && $_GET['tags'] != "") {
|
2015-04-15 21:46:48 +02:00
|
|
|
$search['tags'] = explode( " ", $_GET['tags'] );
|
|
|
|
$search['tags'] = array_splice( $search['tags'], 0, 5 );
|
2015-04-13 06:24:55 +02:00
|
|
|
}
|
2015-04-14 17:01:32 +02:00
|
|
|
|
|
|
|
// What time range?
|
|
|
|
if (isset( $_GET['time'] ) && is_numeric( $_GET['time'] ) ) {
|
|
|
|
$search['time'] = ( (int)( $_GET['time'] / 3600 ) * 3600 );
|
|
|
|
}
|
|
|
|
|
2015-04-13 21:36:38 +02:00
|
|
|
// Include what in the uri / title / subtitle?
|
|
|
|
if (isset( $_GET['title'] ) && $_GET['title'] != "") {
|
|
|
|
$search['title'] = $_GET['title'];
|
|
|
|
}
|
2015-04-13 06:24:55 +02:00
|
|
|
|
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
2015-04-15 21:46:48 +02:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2015-04-13 06:24:55 +02:00
|
|
|
// 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.
|
2015-04-19 05:10:13 +02:00
|
|
|
if ($search['lang'] !== false && $search['lang'] != $boardLang) {
|
2015-04-13 06:24:55 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2015-04-19 05:10:13 +02:00
|
|
|
if (isset($config['languages'][$boardLang])) {
|
|
|
|
$board['locale'] = $config['languages'][$boardLang];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$board['locale'] = $boardLang;
|
|
|
|
}
|
2015-04-13 06:24:55 +02:00
|
|
|
|
|
|
|
$response['boards'][ $board['uri'] ] = $board;
|
|
|
|
}
|
|
|
|
|
2015-04-14 17:01:32 +02:00
|
|
|
unset( $boards );
|
|
|
|
|
2015-04-13 06:24:55 +02:00
|
|
|
/* 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.
|
2015-04-15 21:46:48 +02:00
|
|
|
if ( $search['tags'] !== false && ( !isset( $boardTags[ $boardUri ] ) || count(array_intersect($search['tags'], $boardTags[ $boardUri ])) !== count($search['tags']) ) ) {
|
2015-04-13 06:24:55 +02:00
|
|
|
unset( $response['boards'][$boardUri] );
|
2015-04-19 14:11:56 +02:00
|
|
|
continue;
|
2015-04-13 06:24:55 +02:00
|
|
|
}
|
|
|
|
// 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();
|
|
|
|
}
|
2015-04-19 14:11:56 +02:00
|
|
|
|
|
|
|
// Legacy support for API readers.
|
|
|
|
$board['max'] = &$board['posts_total'];
|
2015-04-13 06:24:55 +02:00
|
|
|
}
|
|
|
|
|
2015-04-14 17:01:32 +02:00
|
|
|
unset( $boardTags );
|
|
|
|
|
2015-04-13 06:24:55 +02:00
|
|
|
|
|
|
|
/* Activity Fetching */
|
2015-04-14 17:01:32 +02:00
|
|
|
$boardActivity = fetchBoardActivity( array_keys( $response['boards'] ), $search['time'], true );
|
2015-04-13 06:24:55 +02:00
|
|
|
|
|
|
|
// 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) {
|
2015-04-14 17:01:32 +02:00
|
|
|
$board['active'] = 0;
|
|
|
|
$board['pph'] = 0;
|
2015-04-19 14:11:56 +02:00
|
|
|
$board['ppd'] = 0;
|
2015-04-14 17:01:32 +02:00
|
|
|
|
|
|
|
if (isset($boardActivity['active'][ $boardUri ])) {
|
|
|
|
$board['active'] = (int) $boardActivity['active'][ $boardUri ];
|
|
|
|
}
|
|
|
|
if (isset($boardActivity['average'][ $boardUri ])) {
|
2015-04-19 18:30:51 +02:00
|
|
|
$precision = 1;
|
2015-04-14 17:01:32 +02:00
|
|
|
|
2015-04-19 18:30:51 +02:00
|
|
|
$board['pph'] = round( $boardActivity['average'][ $boardUri ], $precision );
|
|
|
|
$board['ppd'] = round( $boardActivity['today'][ $boardUri ], $precision );
|
2015-04-14 17:01:32 +02:00
|
|
|
|
|
|
|
unset( $precision );
|
|
|
|
}
|
2015-04-13 06:24:55 +02:00
|
|
|
}
|
|
|
|
|
2015-04-13 17:40:45 +02:00
|
|
|
// Sort boards by their popularity, then by their total posts.
|
2015-04-13 21:36:38 +02:00
|
|
|
$boardActivityValues = array();
|
|
|
|
$boardTotalPostsValues = array();
|
2015-04-15 21:46:48 +02:00
|
|
|
$boardWeightValues = array();
|
2015-04-13 17:40:45 +02:00
|
|
|
|
2015-04-13 21:36:38 +02:00
|
|
|
foreach ($response['boards'] as $boardUri => &$board) {
|
|
|
|
$boardActivityValues[$boardUri] = (int) $board['active'];
|
|
|
|
$boardTotalPostsValues[$boardUri] = (int) $board['posts_total'];
|
2015-04-15 21:46:48 +02:00
|
|
|
$boardWeightValues[$boardUri] = (int) $board['weight'];
|
2015-04-13 17:40:45 +02:00
|
|
|
}
|
|
|
|
|
2015-04-13 21:36:38 +02:00
|
|
|
array_multisort(
|
2015-04-15 21:46:48 +02:00
|
|
|
$boardWeightValues, SORT_DESC, SORT_NUMERIC, // Sort by weight
|
2015-04-13 21:36:38 +02:00
|
|
|
$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']
|
|
|
|
);
|
2015-04-13 17:40:45 +02:00
|
|
|
|
2015-04-19 14:11:56 +02:00
|
|
|
if (php_sapi_name() == 'cli') {
|
2015-04-19 15:00:39 +02:00
|
|
|
$response['boardsFull'] = $response['boards'];
|
2015-04-19 14:11:56 +02:00
|
|
|
}
|
2015-04-15 14:02:11 +02:00
|
|
|
|
2015-04-19 15:00:39 +02:00
|
|
|
$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 );
|
2015-04-15 14:02:11 +02:00
|
|
|
$response['order'] = array_keys( $response['boards'] );
|
|
|
|
|
2015-04-15 16:35:36 +02:00
|
|
|
|
|
|
|
// 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'];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-13 06:24:55 +02:00
|
|
|
// Get the top most popular tags.
|
2015-04-13 17:40:45 +02:00
|
|
|
if (count($response['tags']) > 0) {
|
2015-04-15 16:35:36 +02:00
|
|
|
arsort( $tagUsage['boards'] );
|
|
|
|
arsort( $tagUsage['users'] );
|
|
|
|
|
|
|
|
array_multisort(
|
|
|
|
$tagUsage['boards'], SORT_DESC, SORT_NUMERIC,
|
|
|
|
$tagUsage['users'], SORT_DESC, SORT_NUMERIC,
|
|
|
|
$response['tags']
|
|
|
|
);
|
|
|
|
|
2015-04-13 06:24:55 +02:00
|
|
|
// Get the first n most active tags.
|
2015-04-15 14:02:11 +02:00
|
|
|
$response['tags'] = array_splice( $response['tags'], 0, 100 );
|
2015-04-15 16:35:36 +02:00
|
|
|
$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'] );
|
2015-04-13 06:24:55 +02:00
|
|
|
|
2015-04-15 16:35:36 +02:00
|
|
|
$weightDepartureFurthest = 0;
|
|
|
|
|
|
|
|
foreach ($tagUsage['users'] as $tagUsers) {
|
|
|
|
$weightDeparture = abs( $tagUsers - $tagsAvgUsers );
|
|
|
|
|
|
|
|
if( $weightDeparture > $weightDepartureFurthest ) {
|
|
|
|
$weightDepartureFurthest = $weightDeparture;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($tagUsage['users'] as $tagName => $tagUsers) {
|
2015-04-15 21:46:48 +02:00
|
|
|
if ($weightDepartureFurthest != 0) {
|
|
|
|
$weightDeparture = abs( $tagUsers - $tagsAvgUsers );
|
|
|
|
$response['tagWeight'][$tagName] = 75 + round( 100 * ( $weightDeparture / $weightDepartureFurthest ), 0);
|
|
|
|
}
|
|
|
|
else {
|
2015-04-16 11:41:52 +02:00
|
|
|
$response['tagWeight'][$tagName] = 100;
|
2015-04-15 21:46:48 +02:00
|
|
|
}
|
2015-04-15 16:35:36 +02:00
|
|
|
}
|
2015-04-13 06:24:55 +02:00
|
|
|
}
|
|
|
|
|
2015-04-14 17:01:32 +02:00
|
|
|
/* Include our interpreted search terms. */
|
|
|
|
$response['search'] = $search;
|
|
|
|
|
2015-04-13 06:24:55 +02:00
|
|
|
/* (Please) Respond */
|
2015-04-13 17:40:45 +02:00
|
|
|
if (!$Included) {
|
|
|
|
$json = json_encode( $response );
|
2015-04-13 21:36:38 +02:00
|
|
|
|
2015-04-13 17:40:45 +02:00
|
|
|
// 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;
|
|
|
|
}
|
2015-04-13 21:36:38 +02:00
|
|
|
|
2015-04-13 17:40:45 +02:00
|
|
|
if ($jsonError) {
|
|
|
|
$json = "{\"error\":\"{$jsonError}\"}";
|
|
|
|
}
|
2015-04-13 21:36:38 +02:00
|
|
|
|
2015-04-13 17:40:45 +02:00
|
|
|
// Successful output
|
|
|
|
echo $json;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return $response;
|
|
|
|
}
|