<?php
/*========================================================================*\
|| ###################################################################### ||
|| # vBulletin 5.5.2
|| # ------------------------------------------------------------------ # ||
|| # Copyright 2000-2019 MH Sub I, LLC dba vBulletin. All Rights Reserved.  # ||
|| # This file may not be redistributed in whole or significant part.   # ||

|| # With great thanks to the contribution provided by Andreas          # ||
|| # for the development of this script.                                # ||
|| # ----------------- VBULLETIN IS NOT FREE SOFTWARE ----------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html   # ||
|| ###################################################################### ||
\*========================================================================*/

// ######################## SET PHP ENVIRONMENT ###########################
// for some reason adding the @ to the beginning of this line makes it not work
error_reporting(E_ALL & ~E_NOTICE);

if (!function_exists('readline'))
{
	function readline( $prompt = '' )
	{
		echo $prompt;
		return rtrim( fgets( STDIN ), "\n" );
	}
}

function fetch_postindex_exec_time($seconds)
{
	$phraseApi = vB_Api::instanceInternal('phrase');

	$d['h'] = floor($seconds/3600);
	$d['m'] = floor( ($seconds - ($d['h']*3600)) / 60 );
	$d['s'] = $seconds % 60;

	$phrase = 'x_seconds';
	$params = array($d['s']);

	if (!empty($d['h']) OR !empty($d['m']))
	{
		$phrase = 'x_minutes_and_y_seconds';
		array_unshift($params, $d['m']);
	}

	if (!empty($d['h']))
	{
		$phrase = 'x_hours_y_minutes_and_z_seconds';
		array_unshift($params, $d['h']);
	}

	//if something goes wrong with the phrases, let's not terminate.  Just do something and keep going
	try
	{
		array_unshift($params, $phrase);
		$result = $phraseApi->renderPhrases(array('time' => $params));
		return $result['phrases']['time'];
	}
	catch(Exception $e)
	{
		return implode(':', $d);
	}

	return vB_Phrase::parsePhrase($phrase, $params);
}

function guessVBCorePath()
{
	$corepath = realpath('core');
	if($corepath)
	{
		return $corepath;
	}

	$sanity = 0;
	$corepath = realpath(dirname(__FILE__));
	do
	{
		if (is_readable("$corepath/global.php"))
		{
			return $corepath;
		}

		$oldcorepath = $corepath;
		$corepath = realpath($corepath . '/../');

		if(($oldcorepath == $corepath) OR $sanity++ >= 10)
		{
			break;
		}

	} while($corepath);

	return false;
}

function prompt_for_path($def_core)
{
	$prompt = "Please enter the path to your vBulletin core directory";
	if ($def_core)
	{
		$prompt .= " (default $def_core)";
	}
	$prompt .= ": ";

	$forumspath = trim(readline($prompt));

	if (empty($forumspath))
	{
		$forumspath = $def_core;
	}

	return $forumspath;
}

function handle_full_rebuild($vbphrase, $searchLib)
{
	echo($vbphrase['reindexing_all'] . "\n");
	$startbatch = time();
	// sphinx has it's own implementation of full indexing
	try
	{
		if ($searchLib->reIndexAll())
		{
			echo($vbphrase['building_search_index'] . ': ' . fetch_postindex_exec_time(time()-$startbatch) . "\n");
			// indexing is done
			return true;
		}
	}
	catch (Exception $e)
	{
		$phrase = vB_Api::instanceInternal('phrase')->fetch(array('rebuilt_search_index_not_implemented'));
		echo($phrase['rebuilt_search_index_not_implemented']);
	}

	echo($vbphrase['empty_the_index'] . '...');
	$searchLib->emptyIndex();
	$endbatch = time();
	echo(fetch_postindex_exec_time($endbatch-$startbatch) . "\n");
	return false;
}


// ##################### DEFINE IMPORTANT CONSTANTS #######################
define('THIS_SCRIPT', 'searchindex');
define('VB_AREA', 'Maintenance');
define('SKIP_SESSIONCREATE', 1);
define('VB_ENTRY', true);
define('NOCOOKIES', 1);
define('BYPASS_FORUM_DISABLED', 1);


// ########################################################################
// ######################### START MAIN SCRIPT ############################
// ########################################################################

// this file will most likely be run from the forum root
$def_core = guessVBCorePath();
$forumspath = prompt_for_path($def_core);
while (!is_dir($forumspath) AND !is_readable("$forumspath/global.php"))
{
	print ("\n'$forumspath' is not a valid directory, please try again\n");
	$forumspath = prompt_for_path($def_core);
}

chdir($forumspath);

require_once('./global.php');
@set_time_limit(0);

//get command line options
$options = getopt('', array('startnode::', 'batch::', 'memory::'));
$startnode = (isset($options['startnode']) ? $options['startnode'] : 0);
$perpage = (isset($options['batch']) ? $options['batch'] : 1000);
$memory = (isset($options['memory']) ? $options['memory'] : '256M');

//attempt to extend memory
$memory = vB_Utilities::ini_size_to_bytes($memory);
vB_Utilities::extendMemoryLimitBytes($memory);

//prefetch phrases.  This should cache the phrases even if we later look
//them up via a seperate API call.
$phraseApi = vB_Api::instanceInternal('phrase');
try
{
	$vbphrase = $phraseApi->fetch(array(
		'note_reindexing_empty_indexes_x',
		'building_search_index',
		'default',
		'empty_the_index',
		'reindexing_all',
		'calculating_total',
		'indexing_x_nodes_y_percent_speed_z_eta',
		'rebuilt_search_index_not_implemented',
		'x_seconds',
		'x_minutes_and_y_seconds',
		'x_hours_y_minutes_and_z_seconds',
		'indexing_x_y_x',
		'last_node_processed_x',
	));
}
catch (Exception $e)
{
	echo "Failed to load phrases\n";
}

echo(strip_tags($vbphrase['note_reindexing_empty_indexes_x']) . "\n");
$emptyindex = intval(readline($vbphrase['empty_index'].' [0/1,'.$vbphrase['default'].'=0]: '));

echo("\n");
$searchLib = vB_Library::instance('search');
$start = microtime(true);

if ($emptyindex)
{
	//some implementations (such as sphinx) handle the rebuild directly.
	//others empty the index but require the rebuild step.
	if(handle_full_rebuild($vbphrase, $searchLib))
	{
		exit;
	}
}

/*
//	Init Search & get the enabled types to be re-indexed
$indextype = '';
 	//we might want to implement just certain content types, however the original
	//command line didn't handle that.  This is the preported admincp code.
	$filters = false;
	if (!empty($vbulletin->GPC['indextypes']))
	{
		$filters = array('channel' => $vbulletin->GPC['indextypes']);
		$channelTypes = vB_Channel::getChannelTypes();
		$indextype = $vbphrase[$channelTypes[$vbulletin->GPC['indextypes']]['label']];
	}
 */

//we exclude some types from the index, this should stay in sync with the
//query in the indexRangeForNode
echo $vbphrase['calculating_total'] . "...\n";
$total = $searchLib->getRangeFromNodeCount($startnode);

$startat = 0;
while($startnode !== false)
{

	//handle the fact that the last batch isn't a full one
	$processed = min($perpage, $total - $startat);

	$startbatch = microtime(true);
	$phrases = $phraseApi->renderPhrases(array(
		'indexing' => array('indexing_x_y_x', $startat, ($startat + $processed), $total)
	));

	echo $phrases['phrases']['indexing'];
	$startnode = $searchLib->indexRangeFromNode($startnode, $perpage);
	if (isset($startnode['errors']))
	{
		$phrases = $phraseApi->renderPhrases($startnode['errors']);
		$message = implode("\n\n", $phrases['phrases']);
		exit;
	}

	$endbatch = microtime(true);
	$spent = round($endbatch - $startbatch, 2);

	$startat += $processed;
	$percent = ($total == 0 ? 100 : round(100 * $startat / $total, 2));
	$speed = ($spent == 0 ? 0 : round($processed / $spent));
	$eta = ($speed == 0 ? 0 : ($total - $startat) / $speed);

	$phrases = array(
		'indexing' => array(
			'indexing_x_nodes_y_percent_speed_z_eta',
			$spent,
			$percent,
			$speed,
			fetch_postindex_exec_time($endbatch - $start),
			fetch_postindex_exec_time($eta),
		)
	);

	if($startnode)
	{
		$phrases['lastnode'] = array('last_node_processed_x', $startnode);
	}

	$phrases = $phraseApi->renderPhrases($phrases);

	echo(' ' . $phrases['phrases']['indexing']);
	if(isset($phrases['phrases']['lastnode']))
	{
		echo(' ' . $phrases['phrases']['lastnode']);
	}
	echo "\n";

/*
	$peakreal = memory_get_peak_usage(true) / (1024 * 1024);
	$peak = round(memory_get_peak_usage() / (1024 * 1024), 2);
	$currentreal = memory_get_usage(true) / (1024 * 1024);
	$current = round(memory_get_usage() / (1024 * 1024), 2);
	echo("memory usage. peak: {$peak}MB, peak real: {$peakreal}MB, current: {$current}MB, current real: {$currentreal}MB\n");
*/

	//need to avoid memory limit issues in the command line script.
	//should convert this to clean only the local memory rather than
	//a complete cache clear.
	//
	//Actually it doesn't appear that we need this, at least not any longer
	//leaving in for the time being as a placeholder in case something goes sideways.
	//
	//vB_Cache::resetCache();
}

$end = microtime(true);
echo($vbphrase['building_search_index'] . ': ' . fetch_postindex_exec_time($end-$start) . "\n");


/*======================================================================*\
|| ####################################################################
|| # NulleD By - prowebber.ru
|| # CVS: $RCSfile$ - $Revision: 101245 $
|| ####################################################################
\*======================================================================*/
