<?php
/**
 * @brief		Popular things
 *
 * @copyright	(c) 2001 - 2016 Invision Power Services, Inc.
 *
 * @package		IPS Community Suite
 * @since		21 Oct 2016
 * @version		SVN_VERSION_NUMBER
 */

namespace IPS\core\modules\front\discover;

/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
	header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
	exit;
}

/**
 * Most popular things
 */
class _popular extends \IPS\Dispatcher\Controller
{
	/**
	 * Execute
	 *
	 * @return	void
	 */
	public function execute()
	{
		/* Ensure that the rep system is enabled and the leaderboard is enabled */
		if ( ! \IPS\Settings::i()->reputation_enabled or ! \IPS\Settings::i()->reputation_leaderboard_on )
		{
			\IPS\Output::i()->error( 'module_no_permission', '2C343/1', 403, '' );
		}
		
		\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'styles/search.css' ) );
		\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'styles/leaderboard.css' ) );
		
		if ( \IPS\Theme::i()->settings['responsive'] )
  		{
			\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'styles/search_responsive.css' ) );
			\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'styles/leaderboard_responsive.css' ) );
		}
		
		/* Make sure default tab is first */
		$tabs = array( \IPS\Settings::i()->reputation_leaderboard_default_tab );
		
		foreach( array( 'leaderboard', 'history', 'members' ) as $addTab )
		{
			if ( $addTab != \IPS\Settings::i()->reputation_leaderboard_default_tab )
			{
				$tabs[] = $addTab;
			}
		}
		
		$activeTab = ( isset( \IPS\Request::i()->tab ) and in_array(\IPS\Request::i()->tab, $tabs ) ) ? \IPS\Request::i()->tab : \IPS\Settings::i()->reputation_leaderboard_default_tab;
		
		/* Initiate the breadcrumb */
		\IPS\Output::i()->breadcrumb = array( array( \IPS\Http\Url::internal( "app=core&module=discover&controller=popular&tab=" . $activeTab, 'front', 'leaderboard_' . $activeTab ), \IPS\Member::loggedIn()->language()->addToStack('leaderboard_title') ) );
		
		\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'leaderboard_tabs_' . $activeTab );
		
		$content = $this->$activeTab();
		
		if ( \IPS\Request::i()->isAjax() )
		{
			\IPS\Output::i()->sendOutput( \IPS\Theme::i()->getTemplate( 'global', 'core' )->blankTemplate( $content ), 200, 'text/html', \IPS\Output::i()->httpHeaders );
		}
		else
		{
			/* Display */
			\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('popular')->tabs( $tabs, $activeTab, $content );
		}
	}
	
	/**
	 * View top members
	 *
	 * @return	void
	 */
	protected function members()
	{
		$filters = array(
			'reputation' => array( 'pp_reputation_points', \IPS\Member::loggedIn()->language()->addToStack('leaderboard_tab_reputation') ),
			'posts' => array( 'member_posts', \IPS\Member::loggedIn()->language()->addToStack('leaderboard_tab_posts') )
		);
		
		$classes = array();
		foreach ( \IPS\Application::allExtensions( 'core', 'ContentRouter', TRUE ) as $object )
		{
			$classes = array_merge( $object->classes, $classes );
		}
		
		foreach( $classes as $item )
		{
			$commentClass = NULL;
			
			if ( isset( $item::$commentClass ) )
			{
				$commentClass = $item::$commentClass;
			}
			
			if ( $item::$firstCommentRequired and isset( $commentClass::$databaseColumnMap['author'] ) )
			{
				$filters[ 'app_' . $item::$application . '_' . $item::$module ] = array(
					$commentClass,
					\IPS\Member::loggedIn()->language()->addToStack('leaderboard_tab_x', NULL, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack("{$commentClass::$title}_pl_lc") ) ) ),
					\IPS\Member::loggedIn()->language()->addToStack("{$commentClass::$title}_pl")
				);
			}
			else if ( isset( $item::$databaseColumnMap['author'] ) )
			{
				$filters[ 'app_' . $item::$application . '_' . $item::$module ] = array(
					$item,
					\IPS\Member::loggedIn()->language()->addToStack('leaderboard_tab_x', NULL, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack("{$item::$title}_pl_lc") ) ) ),
					\IPS\Member::loggedIn()->language()->addToStack("{$item::$title}_pl")
				 );
			}
		}
		
		$activeFilter = ( isset( \IPS\Request::i()->filter ) and isset( $filters[ \IPS\Request::i()->filter ] ) ) ? \IPS\Request::i()->filter : 'reputation';
		$members = array();
		
		/* Get the members */
		if ( mb_substr( $activeFilter, 0, 4 ) == 'app_' )
		{
			$storeKey = 'store_' . $activeFilter;
			$recache  = true;
			$memberIds = NULL;
			
			if ( isset( \IPS\Data\Store::i()->$storeKey ) )
			{
				$cache = \IPS\Data\Store::i()->$storeKey;
				
				if ( isset( $cache['time'] ) and isset( $cache['memberIds'] ) and ( time() - $cache['time'] < 300 ) )
				{
					$memberIds = $cache['memberIds'];
				}
			}
			
			/* Nothing returned from the cache, so fetch and store */
			if ( $memberIds === NULL )
			{
				$class = $filters[ $activeFilter ][0];
				$authorField = $class::$databasePrefix . $class::$databaseColumnMap['author'];
				$memberIds = array();
				$where = NULL;
				
				if ( isset( $class::$databaseColumnMap['hidden'] ) )
				{
					$where = array( $class::$databasePrefix . $class::$databaseColumnMap['hidden'] . '=0' );
				}
				else if ( isset( $class::$databaseColumnMap['approved'] ) )
				{
					$where = array( $class::$databasePrefix . $class::$databaseColumnMap['approved'] . '=1' );
				}
	
				/* Get member IDS */
				foreach( \IPS\Db::i()->select( 'COUNT(*) as count, ' . $class::$databaseTable . '.' . $authorField, $class::$databaseTable, $where, 'count DESC', array( 0, \IPS\Settings::i()->reputation_max_members ), $authorField ) as $row )
				{
					$memberIds[ $row[ $authorField ] ] = array( $row['count'] . '.' . $row[ $authorField ], $row['count'] );
				}
				
				\IPS\Data\Store::i()->$storeKey = array( 'time' => time(), 'memberIds' => $memberIds );
			}
			
			if ( count( $memberIds ) )
			{
				foreach( \IPS\Db::i()->select( '*', 'core_members', array( \IPS\Db::i()->in( 'member_id', array_keys( $memberIds ) ) ) ) as $member )
				{
					$members[ $memberIds[ $member['member_id'] ][0] ] = \IPS\Member::constructFromData( array_merge( $member, array( 'custom_count' => $memberIds[ $member['member_id'] ][1] ) ) );
				}
				
				krsort( $members );
			}
		}
		else
		{
			$members = iterator_to_array( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'core_members', array( "name IS NOT NULL AND name != '' AND temp_ban != -1 AND email != '' AND " . $filters[ $activeFilter ][0] . " > 0" ), $filters[ $activeFilter ][0] . ' DESC', array( 0, \IPS\Settings::i()->reputation_max_members ) ), '\IPS\Member' ) );
		}
		
		$contentLabel = 'members_member_posts';
		$contentCount = 'member_posts';
		if ( isset( $filters[ $activeFilter ][2] ) )
		{
			$contentLabel = $filters[ $activeFilter ][2];
			$contentCount = 'custom_count';
		}
		
		if ( \IPS\Request::i()->isAjax() and \IPS\Request::i()->topMembers )
		{
			\IPS\Output::i()->json( array( 'rows' => \IPS\Theme::i()->getTemplate('popular')->membersWrapper( $members, $contentLabel, $contentCount ), 'extraHtml' => $filters[ $activeFilter ][1] ) );
		}
		else
		{
			return \IPS\Theme::i()->getTemplate('popular')->members( $members, $filters, $activeFilter, \IPS\Http\Url::internal( "app=core&module=discover&controller=popular&tab=members", 'front', 'leaderboard_members' ), $contentLabel, $contentCount );
		}
	}
	
	/**
	 * View past leaders
	 *
	 * @return	void
	 */
	protected function history()
	{
		$table = new \IPS\Helpers\Table\Db( 'core_reputation_leaderboard_history', \IPS\Http\Url::internal( "app=core&module=discover&controller=popular&tab=history", 'front', 'leaderboard_history' ) );
		$table->where = array( 'leader_position <= 3' );
		$table->limit = 21;
		$table->selects = array( 'leader_member_id, leader_position, leader_rep_total', 'ABS(leader_date - leader_position) as leader_date' );
		$table->joins = array( array( 'select' => 'core_members.*', 'from' => 'core_members', 'where' => 'core_reputation_leaderboard_history.leader_member_id=core_members.member_id' ) );
		$table->include = array( 'leader_member_id', 'leader_date', 'leader_position', 'leader_rep_total' );
		$table->noSort = array( 'leader_member_id', 'leader_position', 'leader_rep_total' );
		$table->tableTemplate = array( \IPS\Theme::i()->getTemplate( 'popular', 'core', 'front' ), 'popularTable' );
		$table->rowsTemplate  = array( \IPS\Theme::i()->getTemplate( 'popular', 'core', 'front' ), 'popularRows' );
		$table->title = 'leaderboard_history';
		$table->mainColumn = 'leader_date';
		$table->sortBy = $table->sortBy ?: 'leader_date';
		$table->sortDirection = $table->sortDirection ?: 'DESC';
		
		/* Like or what? */
		if ( \IPS\Settings::i()->reputation_point_types == 'like' )
		{
			\IPS\Member::loggedIn()->language()->words['leader_rep_total'] = \IPS\Member::loggedIn()->language()->addToStack( 'leader_rep_total_likes' );
			\IPS\Member::loggedIn()->language()->words['leaderboard_history__desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'leaderboard_history_desc', NULL, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack( 'leaderboard_history_likes') ) ) );
		}
		else
		{
			\IPS\Member::loggedIn()->language()->words['leader_rep_total'] = \IPS\Member::loggedIn()->language()->addToStack( 'leader_rep_total_rep' );
			\IPS\Member::loggedIn()->language()->words['leaderboard_history__desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'leaderboard_history_desc', NULL, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack( 'leaderboard_history_rep') ) ) );
		}
		
		/* Parsers */
		$table->parsers = array(
			'leader_member_id' => function( $val, $row )
			{
				return \IPS\Member::constructFromData( $row );
			},
			'leader_date' => function( $val )
			{
				return \IPS\DateTime::ts( $val )->setTimezone( new \DateTimeZone( \IPS\Settings::i()->reputation_timezone ) );
			}
		);
		
		if ( \IPS\Request::i()->isAjax() )
		{
			\IPS\Output::i()->sendOutput( \IPS\Theme::i()->getTemplate( 'global', 'core' )->blankTemplate( $table ), 200, 'text/html', \IPS\Output::i()->httpHeaders );
		}
		else
		{
			if ( $table->page > 1 )
			{
				\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'title_with_page_number', FALSE, array( 'sprintf' => array( \IPS\Output::i()->title, $table->page ) ) );
			}
			
			return (string) $table;
		}
	}
	
	/**
	 * View Popular list
	 *
	 * It's slightly cumbersome because we need to group, but we can only select columns in the group list, so we have to:
	 * 1) Fetch the lookup_hash and total_rep
	 * 2) Fetch the rep data so we can...
	 * 3) Fetch the search engine data and then...
	 * 4) Manually sort in PHP
	 *
	 * @return	void
	 */
	protected function leaderboard()
	{
		/* Figure out dates */
		$dates = array();
		$timezone = new \DateTimeZone( \IPS\Settings::i()->reputation_timezone );
		$endDate = \IPS\DateTime::ts( time() )->setTimezone( $timezone );
		
		$firstRepDate = \IPS\Db::i()->select( 'MIN(rep_date)', 'core_reputation_index' )->first();
		$firstIndexDate = \IPS\Db::i()->select( 'MIN(index_date_updated)', 'core_search_index' )->first();
		$appWhere = null;
		$descApp  = \IPS\Member::loggedIn()->language()->addToStack('leaderboard_in_all_apps');
		
		$dates['oldest'] = \IPS\DateTime::ts( ( $firstRepDate > $firstIndexDate ) ? $firstRepDate : $firstIndexDate );
		$oldestStamp = $dates['oldest']->getTimeStamp();
		$date = $dates['oldest'];
		
		$aYearAgo = \IPS\DateTime::create()->setTimezone( $timezone )->sub( new \DateInterval( 'P1Y' ) );
		$month = \IPS\DateTime::create()->setTimezone( $timezone )->sub( new \DateInterval( 'P1M' ) )->setTime( 0, 0 );
		$week = \IPS\DateTime::create()->setTimezone( $timezone )->sub( new \DateInterval( 'P7D' ) )->setTime( 0, 0 );
		$today = \IPS\DateTime::create()->setTimezone( $timezone )->setTime( 0, 0 );
		
		if ( $aYearAgo->getTimeStamp() > $oldestStamp )
		{
			$dates['year'] = $aYearAgo;
		}
		
		if ( $month->getTimeStamp() > $oldestStamp )
		{
			$dates['month'] = $month;
		}
		
		if ( $week->getTimeStamp() > $oldestStamp )
		{
			$dates['week'] = $week;
		}
		
		if ( $today->getTimeStamp() > $oldestStamp )
		{
			$dates['today'] = $today;
		}
		
		/* Got a date? */
		if ( isset( \IPS\Request::i()->time ) and isset( $dates[ \IPS\Request::i()->time ] ) )
		{
			$date = $dates[ \IPS\Request::i()->time ];
		}
		else if ( isset( $dates['month'] ) )
		{
			/* Set the default to month */
			\IPS\Request::i()->time = 'month';
			$date = $dates['month'];
		}
		
		/* Applications */
		$classes = array();
		foreach ( \IPS\Application::allExtensions( 'core', 'ContentRouter', TRUE ) as $object )
		{
			$classes = array_merge( $object->classes, $classes );
		}
		
		$areas = array();
		foreach( $classes as $item )
		{
			$commentClass = NULL;
			$reviewClass = NULL;
			
			if ( in_array( 'IPS\Content\Reputation', class_implements( $item ) ) and isset( $item::$reputationType ) )
			{
				$areas[ $item::$application . '-' . $item::$reputationType ] = array( $item, \IPS\Member::loggedIn()->language()->addToStack("{$item::$title}_pl") );
			}

			if ( isset( $item::$commentClass ) )
			{
				$commentClass = $item::$commentClass;
				if ( in_array( 'IPS\Content\Reputation', class_implements( $commentClass ) ) and isset( $commentClass::$reputationType ) )
				{
					$areas[ $item::$application . '-' . $commentClass::$reputationType ] = array( $commentClass, \IPS\Member::loggedIn()->language()->addToStack("{$commentClass::$title}_pl") );
				}
			}
			
			if ( isset( $item::$reviewClass ) )
			{
				$reviewClass = $item::$reviewClass;
				if ( in_array( 'IPS\Content\Reputation', class_implements( $reviewClass ) ) and isset( $reviewClass::$reputationType ) )
				{
					$areas[ $item::$application . '-' . $reviewClass::$reputationType ] = array( $reviewClass, \IPS\Member::loggedIn()->language()->addToStack("{$reviewClass::$title}_pl") );
				}
			}
		}

		$form = new \IPS\Helpers\Form('popular_date', 'continue');
		$form->class = 'ipsForm_vertical';
		$customStart = isset( \IPS\Request::i()->custom_date_start ) ? \IPS\Request::i()->custom_date_start : NULL;
		$customEnd = isset( \IPS\Request::i()->custom_date_end ) ? \IPS\Request::i()->custom_date_end : NULL;

		$form->add( new \IPS\Helpers\Form\DateRange( 'custom_date', array( 'start' => $customStart, 'end' => $customEnd ), FALSE, array( 'start' => array( 'min' => $dates['oldest'], 'time' => false ) ) ) );
		
		if ( $values = $form->values() )
		{
			$url = \IPS\Request::i()->url()->stripQueryString('time');
			
			if ( isset( $values['custom_date']['start'] ) and $values['custom_date']['start'] instanceof \IPS\DateTime )
			{
				$url = $url->setQueryString( 'custom_date_start', $values['custom_date']['start']->getTimeStamp() );
			}
			
			if ( isset( $values['custom_date']['end'] ) and $values['custom_date']['end'] instanceof \IPS\DateTime )
			{
				$url = $url->setQueryString( 'custom_date_end', $values['custom_date']['end']->getTimeStamp() );
			}
			
			\IPS\Output::i()->redirect( $url );
		}
		else
		{
			if ( $customStart )
			{
				$date = \IPS\DateTime::ts( $customStart )->setTimezone( $timezone )->setTime( 0, 0, 1 );
			}
			
			if ( $customEnd )
			{
				$endDate = \IPS\DateTime::ts( $customEnd )->setTimezone( $timezone )->setTime( 23, 59, 59 );
			}
		}
		
		/* Got an app? */
		$appWhere = '';

		if ( isset( \IPS\Request::i()->in ) and isset( $areas[ \IPS\Request::i()->in ] ) )
		{
			$appWhere = " AND rep_class='" . \IPS\Db::i()->escape_string( $areas[ \IPS\Request::i()->in ][0] ) . "'";
			$descApp = \IPS\Member::loggedIn()->language()->addToStack('leaderboard_in_app', NULL, array( 'sprintf' => array( $areas[ \IPS\Request::i()->in ][1] ) ) );
		}
		
		$storeKey = NULL;
		$hashes   = NULL;
		if ( ! $customStart and ! $customEnd AND ! $appWhere )
		{
			$storeKey = 'leaderHashes_' . \IPS\Request::i()->time . '-' . md5( implode( ',', \IPS\Member::loggedIn()->groups ) );
		
			if ( isset( \IPS\Data\Store::i()->$storeKey ) )
			{
				$stored = \IPS\Data\Store::i()->$storeKey;
				
				if ( isset( $stored['hashes'] ) and isset( $stored['time'] ) and $stored['time'] > ( time() - 900 ) )
				{
					$hashes = $stored['hashes'];
				}
			}
		}

		/* Get hashes and total rep */
		if ( $hashes === NULL )
		{
			/* Prevent race condition */
			if ( $storeKey )
			{
				\IPS\Data\Store::i()->$storeKey = array( 'time' => time(), 'hashes' => array() );
			}
			
			$testFirst = iterator_to_array( \IPS\Db::i()->select( 'class_type_id_hash', 'core_reputation_index', array( 'rep_date BETWEEN ' . $date->getTimeStamp() . ' AND ' . $endDate->getTimeStamp() . $appWhere ), NULL, NULL, 'class_type_id_hash' ) );
			$searchWhere = array( array( "( si.index_permissions = '*' OR " . \IPS\Db::i()->findInSet( 'si.index_permissions', \IPS\Member::loggedIn()->groups ) . ' AND si.index_hidden=0 )' ) );
			
			if ( count( $testFirst ) < 501 )
			{
				/* We can use an IN() to make this a bit more efficient */
				$repQuery = \IPS\Db::i()->select( 'class_type_id_hash, SUM(rep_rating) as total_rep', 'core_reputation_index', \IPS\Db::i()->in( 'class_type_id_hash', array_values( $testFirst ) ), NULL, NULL, 'class_type_id_hash' );

				$searchWhere[] = array( \IPS\Db::i()->in( 'si.index_class_type_id_hash', array_values( $testFirst ) ) );
			}
			else
			{
				$repQuery = \IPS\Db::i()->select( 'class_type_id_hash, SUM(rep_rating) as total_rep', 'core_reputation_index', array( 'rep_date BETWEEN ' . $date->getTimeStamp() . ' AND ' . $endDate->getTimeStamp() . $appWhere ), NULL, NULL, 'class_type_id_hash' );
			}
			
			$searchQuery = \IPS\Db::i()->select( 'index_class_type_id_hash', array( 'core_search_index', 'si' ),  $searchWhere );
									
			$hashes = iterator_to_array(
				\IPS\Db::i()->select( 'class_type_id_hash, total_rep', array( $repQuery, 'ri' ), array( 'class_type_id_hash IN(?)', $searchQuery ), 'total_rep DESC', array( 0, 50 ) )->setKeyField('class_type_id_hash')
			);
						
			if ( $storeKey )
			{
				\IPS\Data\Store::i()->$storeKey = array( 'time' => time(), 'hashes' => $hashes );
			}
		}
		
		$classes = array();
		$repData = array();
		$or   = array();
		$results = array();
		$preLoadMembers = array();
		
		if ( count( $hashes ) )
		{
			/* Now get the reputation data */
			foreach( \IPS\Db::i()->select( '*', 'core_reputation_index', array( \IPS\Db::i()->in( 'class_type_id_hash', array_keys( $hashes ) ) ) ) as $data )
			{
				$data['total_rep'] = $hashes[ $data['class_type_id_hash'] ]['total_rep'];
				$repData[ $data['rep_class'] . '-' . $data['type_id'] ] = $data;
				$classes[ $data['rep_class'] ][] = $data['type_id'];
				
				if ( ! empty( $data['member_id'] ) )
				{
					$preLoadMembers[] = $data['member_id'];
				}
			}
			
			foreach( $classes as $class => $ids )
			{
				$or[] = '( index_class=\'' . \IPS\Db::i()->escape_string( $class ) . '\' AND ' . \IPS\Db::i()->in( 'index_object_id', $ids ) . ')';
			}
			
			/* Construct the query */
			$query = \IPS\Db::i()->select( '*', array( 'core_search_index', 'main' ), array( implode( ' OR ', $or ) ), NULL, NULL, NULL, NULL );
			
			$array = iterator_to_array( $query );
			$sorted = array();
			
			/* Manually sort */
			foreach( $array as $index => $data )
			{
				if ( isset( $repData[ $data['index_class'] . '-' . $data['index_object_id'] ] ) )
				{
					$data['rep_data'] = $repData[ $data['index_class'] . '-' . $data['index_object_id'] ];
					$sorted[ $data['rep_data']['total_rep'] . '.' . $data['index_date_updated'] . '.'. $index ] = $data;
				}
			}
			unset( $array );
			krsort( $sorted, SORT_NUMERIC );
			
			$results = new \IPS\Content\Search\Results( $sorted, count( $sorted ) );
			
			/* Load data we need like the authors, etc */
			$results->init();
		}
		
		/* Get top rated contributors */
		$topContributors = array();
		
		$innerQuery = \IPS\Db::i()->select( 'core_reputation_index.member_received as member, SUM(rep_rating) as rep', 'core_reputation_index', array( 'member_received>0 ' . $appWhere . ' and rep_date BETWEEN ' . intval( $date->getTimeStamp() ) . ' AND ' . intval( $endDate->getTimeStamp() ) ), NULL, NULL, 'member' );
		foreach( \IPS\Db::i()->select( 'member, rep', array( $innerQuery, 'in' ), NULL, 'rep DESC', 4 )->setKeyField('member')->setValueField('rep') as $member => $rep )
		{
			$topContributors[ $member ] = $rep;
		}
		
		if ( count( $topContributors ) )
		{
			$preLoadMembers = array_merge( $preLoadMembers, array_keys( $topContributors ) );
		}
		
		/* Load their data */
		if ( count( $preLoadMembers ) )
		{
			foreach ( \IPS\Db::i()->select( '*', 'core_members', \IPS\Db::i()->in( 'member_id', array_unique( $preLoadMembers ) ) ) as $member )
			{
				\IPS\Member::constructFromData( $member );
			}
		}
		
		/* Work out the description for popular content */
		if ( \IPS\Settings::i()->reputation_point_types == 'like' )
		{
			$popularResultsSingle = 'popular_results_single_desc';
			$popularResultsMany = 'popular_results_desc';
		}
		else
		{
			$popularResultsSingle = 'popular_results_single_desc_rep';
			$popularResultsMany = 'popular_results_desc_rep';
		}
		
		$description = \IPS\Member::loggedIn()->language()->addToStack( ( $date->localeDate() == $endDate->localeDate() ) ? $popularResultsSingle : $popularResultsMany, NULL, array( 'sprintf' => array( $date->localeDate(), $descApp ) ) );
		
		/* Are our offsets different? */
		$tzOffsetDifference = NULL;
		try
		{
			if ( \IPS\DateTime::ts( time() )->getOffset() != \IPS\DateTime::ts( time() )->setTimezone( $timezone )->getOffset() )
			{
				$tzOffsetDifference = \IPS\DateTime::ts( time() )->setTimezone( $timezone )->format('P');
			}
		}
		catch( \Exception $ex ) { }

		/* Display */
		return \IPS\Theme::i()->getTemplate('popular')->popularWrapper( $results, $areas, $topContributors, $dates, $description, $form->customTemplate( array( call_user_func_array( array( \IPS\Theme::i(), 'getTemplate' ), array( 'forms', 'core' ) ), 'popupTemplate' ) ), $tzOffsetDifference );
	}
}