<?php
/*
 * e107 website system
 *
 * Copyright (C) 2008-2010 e107 Inc (e107.org)
 * Released under the terms and conditions of the
 * GNU General Public License (http://www.gnu.org/licenses/gpl.txt)
 *
 * Online handler
 *
*/


/**
 *	@package    e107
 *	@subpackage	e107_handlers
 *
 *	Handler to keep track of online users
 */

/*
  online_timestamp int(10) unsigned NOT NULL default '0',		Start of time period over which accesses counted
  online_flag tinyint(3) unsigned NOT NULL default '0',			Not used? (displayed in admin log only)
  online_user_id varchar(100) NOT NULL default '',
  online_ip varchar(45) NOT NULL default '',
  online_location text NOT NULL,        						Current page being accessed
  online_pagecount tinyint(3) unsigned NOT NULL default '0',	Number of page accesses (within most recent timeout interval?)
  online_active int(10) unsigned NOT NULL default '0',			Not used? Actually added in the update routines, version 0.7.6. (Also displayed in admin log)
  online_agent varchar(255) NOT NULL default ''					User agent - use for bot identification

  Current count() queries:
	a) Total
	b) $sql->db_Count('online', '(*)', "WHERE `online_location` = '{$page}' "));

  Also forum_viewforum.php:
  	$member_users = $sql->db_Count('online', '(*)', "WHERE online_location REGEXP('viewforum.php.id=$forumId\$') AND online_user_id != 0");
	$guest_users = $sql->db_Count('online', '(*)', "WHERE online_location REGEXP('viewforum.php.id=$forumId\$') AND online_user_id = 0");


Following single query gives two rows of data - one for members (online_user_id != 0) and one for guests (online_user_id == 0)
SELECT COUNT(`online_user_id`) AS ol_count, `online_user_id` FROM `#online` GROUP BY (`online_user_id` = 0)
*/

/**
 *	@todo	Don't create list of online members by default - $listuserson used in forum, online_menu.php, online.php
 *	@todo	$member_list defined as MEMBER_LIST - used in online_shortcodes.php
 *	@todo	$members_online, defined as MEMBERS_ONLINE - used in online_menu.php, online_shortcodes.php, online_template.php, online.php, forum.php
 *	@todo	$total_online defined (indirectly) as GUESTS_ONLINE + MEMBERS_ONLINE - used in online_menu.php, online_shortcodes.php, online_template.php, online.php
 *	@todo	Possibly online_pagecount should be bigger than tinyint(3)
 *	@todo	Possibly shouldn't log access to index.php - its usually a redirect - but not always!
 *	@todo	Can we distinguish between different users at same IP? Browser sig, maybe?
 *	@todo	Change queries to array access
 *	@todo 	Eliminate other globals
 *	@todo	Can we simplify counts into one query?
*/
class e_online
{
	
	public $users = array();	
	public $guests = array();	
	
	
	function __construct()
	{
		
		
	}
	
	
	
	/**
	 * Go online
	 * @param boolean $online_tracking
	 * @param boolean $flood_control
	 * @return void
	 */
	public function goOnline($online_tracking = false, $flood_control = false)
	{
		// global $pref, $e_event; // Not needed as globals
		//global $online_timeout, $online_warncount, $online_bancount;	// Not needed as globals
		//global $members_online, $total_online;						// Not needed as globals
		global $listuserson; // FIXME - remove it, make it property, call e_online signleton - e107::getOnline()

		if($online_tracking == false || $flood_control == false)
		{
			define('e_TRACKING_DISABLED', true);		// Used in forum, online menu
			define('TOTAL_ONLINE', '');
			define('MEMBERS_ONLINE', '');
			define('GUESTS_ONLINE', '');
			define('ON_PAGE', '');
			define('MEMBER_LIST', '');

			return null;
		}

		$sql = e107::getDb();
		$user = e107::getUser();
		$dbg = e107::getDebug();

		$online_timeout = 300;

		list($ban_access_guest,$ban_access_member) = explode(',',e107::getPref('ban_max_online_access', '100,200'));
		$online_bancount = max($ban_access_guest,50);					// Safety net for incorrect values
		if ($user->isUser())
		{
			$online_bancount = max($online_bancount,$ban_access_member);
		}

		$online_warncount = $online_bancount * 0.9;		// Set warning threshold at 90% of ban threshold
			//TODO Add support for all queries.
			// $page = (strpos(e_SELF, 'forum_') !== FALSE) ? e_SELF.'.'.e_QUERY : e_SELF;
			// $page = (strpos(e_SELF, 'comment') !== FALSE) ? e_SELF.'.'.e_QUERY : $page;
			// $page = (strpos(e_SELF, 'content') !== FALSE) ? e_SELF.'.'.e_QUERY : $page;
			$page = e_REQUEST_URI; // mod rewrite & single entry support
			// FIXME parse url, trigger registered e_online callbacks
		//	$page = e107::getParser()->toDB($page, true);								/// @todo - try not to use toDB() - triggers prefilter

			$page = filter_var($page,FILTER_SANITIZE_URL);
			$ip = e107::getIPHandler()->getIP(FALSE);

			$udata = ($user->isUser() && USER ? $user->getId().'.'.$user->getName() : '0'); // USER check required to make sure they logged in without an error.
			$agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';

			// XXX - more exceptions, e.g. hide online location for admins/users (pref), e_jlsib.php, etc
			// XXX - more advanced flod timing when  e_AJAX_REQUEST, e.g. $ban_access_ajax = 300
			$update_page = deftrue('e_AJAX_REQUEST') ? '' : ", online_location='{$page}'";

			$insert_query = array(
				'online_timestamp'	=> time(),
				'online_flag'		=> 0,
				'online_user_id'	=> $udata,
				'online_ip'			=> $ip,
				'online_location'	=> $page,
				'online_pagecount'	=> 1,
				'online_active'		=> 0,
				'online_agent'		=> $agent,
				'online_language'   => e_LAN
			);

			// !deftrue('e_AJAX_REQUEST')
			// TODO add option to hide users from online list? boolean online_hide field?
			// don't do anything if main admin logged in as another user
			if ($user->isUser()  && !$user->getParentId())
			{
				$dbg->logTime('Go online (isUser)');
				// Find record that matches IP or visitor, or matches user info
				$dbg->logTime('Go online (db select)');
				if ($sql->select('online', '*', "(`online_ip` = '{$ip}' AND `online_user_id` = '0') OR `online_user_id` = '{$udata}' LIMIT 1"))
				{
					$dbg->logTime('Go online (db fetch)');
					$row = $sql->fetch();
					$dbg->logTime('Go online (db end)');

					if ($row['online_user_id'] == $udata)
					{
						//Matching user record
						if ($row['online_timestamp'] < (time() - $online_timeout))
						{
							//It has been at least 'online_timeout' seconds since this user's info last logged
							//Update user record with timestamp, current IP, current page and set pagecount to 1
						//	$query = "online_timestamp='".time()."', online_ip='{$ip}'{$update_page}, online_pagecount=1, `online_active` = 1 WHERE online_user_id='{$row['online_user_id']}'";

							$query = array(
								'online_timestamp' => time(),
								'online_ip'         => $ip,
								'online_pagecount'  => 1,
								'online_active'     => 1,
								'WHERE'             => "online_user_id=".intval($row['online_user_id'])." LIMIT 1"
							);


						}
						else
						{
							if (!$user->isAdmin())
							{
								$row['online_pagecount'] ++;
							}
							// Update user record with current IP, current page and increment pagecount
						//	$query = "online_ip='{$ip}'{$update_page}, `online_pagecount` = '".intval($row['online_pagecount'])."', `online_active` = 1 WHERE `online_user_id` = '{$row['online_user_id']}'";


							$query = array(

								'online_ip'         => $ip,
								'online_pagecount'  => intval($row['online_pagecount']),
								'online_active'     => 1,
								'WHERE'             => "online_user_id=".intval($row['online_user_id'])." LIMIT 1"
							);

						}
					}
					else
					{
						//Found matching visitor record (ip only) for this user
						if ($row['online_timestamp'] < (time() - $online_timeout))
						{
							// It has been at least 'timeout' seconds since this user has connected
							// Update record with timestamp, current IP, current page and set pagecount to 1
						//	$query = "`online_timestamp` = '".time()."', `online_user_id` = '{$udata}'{$update_page}, `online_pagecount` = 1,  `online_active` = 1 WHERE `online_ip` = '{$ip}' AND `online_user_id` = '0'";

							$query = array(
								'online_timestamp' => time(),
								'online_user_id'    => $udata,
								'online_pagecount'  => 1,
								'online_active'     => 1,
								'WHERE'             => "online_ip = '".$ip."' AND online_user_id = '0' LIMIT 1"
							);


						}
						else
						{	// Another visit within the timeout period
							if (!$user->isAdmin())
							{
								$row['online_pagecount'] ++;
							}
							//Update record with current IP, current page and increment pagecount
					//		$query = "`online_user_id` = '{$udata}'{$update_page}, `online_pagecount` = ".intval($row['online_pagecount']).", `online_active` =1  WHERE `online_ip` = '{$ip}' AND `online_user_id` = '0'";

							$query = array(
							//	'online_timestamp' => time(),
								'online_user_id'    => $udata,
								'online_pagecount'  => intval($row['online_pagecount']),
								'online_active'     => 1,
								'WHERE'             => "online_ip = '".$ip."' AND online_user_id = '0' LIMIT 1"
							);

						}
					}


					if(!empty($update_page))
					{
						$query['online_location'] = $page;
					}

					$dbg->logTime('Go online (update) Line:'.__LINE__);
					$sql->update('online', $query);
					$dbg->logTime('Go online (after update) Line:'.__LINE__);

				}
				else
				{
					$dbg->logTime('Go online (insert) Line: '.__LINE__);
					$sql->insert('online',$insert_query);
					$dbg->logTime('Go online (after insert) Line: '.__LINE__);
				}

				$dbg->logTime('Go online (after isUser)');
			}
			// don't do anything if main admin logged in as another user
			elseif(!$user->getParentId())
			{
				//Current page request is from a guest
				if ($sql->select('online', '*', "`online_ip` = '{$ip}' AND `online_user_id` = '0'"))
				{	// Recent visitor
					$row = $sql->fetch();

					if ($row['online_timestamp'] < (time() - $online_timeout)) //It has been at least 'timeout' seconds since this ip has connected
					{
						//Update record with timestamp, current page, and set pagecount to 1
						$query = "`online_timestamp` = '".time()."'{$update_page}, `online_pagecount` = 1 WHERE `online_ip` = '{$ip}' AND `online_user_id` = '0'";
					}
					else
					{
						//Update record with current page and increment pagecount
						$row['online_pagecount'] ++;
						//   echo "here {$online_pagecount}";
						$query="`online_pagecount` = {$row['online_pagecount']}{$update_page} WHERE `online_ip` = '{$ip}' AND `online_user_id` = '0'";
					}
					$dbg->logTime('Go online (update) Line:'.__LINE__);
					$sql->update('online', $query);
					$dbg->logTime('Go online (after update) Line:'.__LINE__);
				}
				else
				{	// New visitor
					$sql->insert('online',$insert_query);
				}
			}

			if ($user->isAdmin() || (e107::getPref('autoban') != 1 && e107::getPref('autoban') != 2) || (!isset($row['online_pagecount']))) // Auto-Ban is switched off. (0 or 3)
			{
				$row['online_pagecount'] = 1;
			}

			// Always allow localhost - any problems are usually semi-intentional!
			$onlineIP = varset($row['online_ip']);
			if (($onlineIP !== '127.0.0.1') && ($onlineIP !== e107::LOCALHOST_IP)  && ($onlineIP != e107::LOCALHOST_IP2))
			{
				// Check for excessive access
				if ($row['online_pagecount'] > $online_bancount)
				{
					e107::lan('core','banlist',true);//e_LANGUAGEDIR.e_LANGUAGE.'/admin/lan_banlist.php'
					$reason = e107::getParser()->lanVars(BANLAN_78,$row['online_pagecount']); //  str_replace('--HITS--',$row['online_pagecount'], BANLAN_78)

					if (true === e107::getIPHandler()->add_ban(2, $reason, $ip,0))
					{
						e107::getEvent()->trigger('flood', $ip); //BC
						e107::getEvent()->trigger('user_ban_flood', $ip);
						exit;
					}
				}
				elseif ($row['online_pagecount'] >= $online_warncount)
				{
					echo "<div style='text-align:center; font: 11px verdana, tahoma, arial, helvetica, sans-serif;'><b>".LAN_WARNING."</b><br /><br />".CORE_LAN6."<br /></div>";
					exit;
				}
			}

			// Delete records for users (and guests) not seen for a while
			// FIXME - DB optimization - mark records as deleted (online_deleted=1), delete once per hour (could be pref) via e_cron
			// FIXME - Additional prefs for this (it does 2-3 more queries no matter someone need them), could be also separate method
			// Speed up ajax requests
			if(!deftrue('e_AJAX_REQUEST'))
			{
				$dbg->logTime('Go online (delete) Line:'.__LINE__);
				$sql->delete('online', '`online_timestamp` < '.(time() - $online_timeout));

				// FIXME - don't use constants below, save data in class vars, call e_online signleton - e107::getOnline()
			//	$total_online = $sql->db_Count('online'); // 1 less query! :-)
				$dbg->logTime('Go online (total_online) Line:'.__LINE__);
				if ($total_online = $sql->gen('SELECT o.*,u.user_image FROM `#online` AS o LEFT JOIN `#user` AS u ON o.online_user_id = u.user_id WHERE o.online_pagecount > 0 ORDER BY o.online_timestamp DESC'))
			//	if ($total_online = $sql->gen('SELECT o  FROM `#online`  WHERE o.online_pagecount > 0 ORDER BY o.online_timestamp DESC'))
				{
					$member_list = '';
					$members_online = 0;
					$listuserson = array();

					$dbg->logTime('Go online (db fetch) Line:'.__LINE__);
					while ($row = $sql->fetch())
					{

						$row['online_bot'] = $this->isBot($row['online_agent']);
				
						// Sort into usable format and add bot field. 
						$user = array(
							'user_location'		=> $row['online_location'],
							'user_bot'			=> $this->isBot($row['online_agent']),
							'user_agent'		=> $row['online_agent'],
							'user_ip'			=> $row['online_ip'],
							'user_currentvisit'	=> $row['online_timestamp'],
							'user_online'		=> $row['online_flag'],
							'user_pagecount'	=> $row['online_pagecount'],
							'user_active'		=> $row['online_active'],
							'user_image'		=> vartrue($row['user_image'],false),
							'online_user_id'	=> $row['online_user_id'],
							'user_language'     => $row['online_language']
						);	
		
						if($row['online_user_id'] != 0 )
						{
							$vals = explode('.', $row['online_user_id'], 2);
							$user['user_id'] = $vals[0];
							$user['user_name'] = $vals[1];
							$member_list .= "<a href='".SITEURL."user.php?id.{$vals[0]}'>{$vals[1]}</a> ";
							$listuserson[$row['online_user_id']] = $row['online_location'];

							$this->users[] = $user;
							$members_online++;

						}
						else 
						{
							$user['user_id'] = 0;
							$user['user_name'] = 'guest';		// Maybe should just be an empty string?
							$this->guests[] = $user;	
						}
						
						
					}
				}
				if(!defined('TOTAL_ONLINE'))
				{
					define('TOTAL_ONLINE', $total_online);
					define('MEMBERS_ONLINE', $members_online);
					define('GUESTS_ONLINE', $total_online - $members_online);
					$dbg->logTime('Go online (db count) Line:'.__LINE__);
					define('ON_PAGE', $sql->count('online', '(*)', "WHERE `online_location` = '{$page}' "));
					define('MEMBER_LIST', $member_list);
				}
				//update most ever online
				$olCountPrefs = e107::getConfig('history');			// Get historic counts of members on line
				$olCountPrefs->setParam('nologs', true);

				if ($total_online > ($olCountPrefs->get('most_members_online') + $olCountPrefs->get('most_guests_online')))
				{
					$olCountPrefs->set('most_members_online', MEMBERS_ONLINE);
					$olCountPrefs->set('most_guests_online', GUESTS_ONLINE);
					$olCountPrefs->set('most_online_datestamp', time());
					$olCountPrefs->save(false, true, false);
				}
			}
		/*}
		else
		{
			define('e_TRACKING_DISABLED', true);		// Used in forum, online menu
			define('TOTAL_ONLINE', '');
			define('MEMBERS_ONLINE', '');
			define('GUESTS_ONLINE', '');
			define('ON_PAGE', '');
			define('MEMBER_LIST', '');
		}*/
	}


	/**
	 * @param $debug
	 * @return array|string
	 */
	function userList($debug=false)
	{

		if($debug === true)
		{
			//print_a($this->users);
			return e107::getDb()->retrieve('user', 'user_id,user_name,user_image, 1 as user_active, CONCAT_WS(".",user_id,user_name) as online_user_id', "LIMIT 7", true);
		}


		return $this->users;
	}

	/**
	 * @return array
	 */
	function guestList()
	{
		return $this->guests;
	}


	/**
	 * @param $userAgent
	 * @return bool
	 */
	function isBot($userAgent='')
	{
		return e107::getUser()->isBot($userAgent);
	}
	

}