<?php
/*
* @package LabCMS
* @copyright (c) 2006 Alexander Levin aka Loki (loki_angel@mail.ru)
* @support http://labcms.ru
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
*/

class User
{
	private static $instance;
	private $user;
	private $auth;
	private static $parent_groups=array();
	private static $onliners=null;
	private $request_time=0;
	
	public $token;
	public $ip;
	public $user_agent;
	
	/**
	 * конструктор. при инициализации класса если данные уже имеются в сессии, то берет оттуда
	 * если же нет, то пытается авторизовать через куки. если авторизация неудачна, либо сессия украдена
	 * то считает посетителя гостем
	 * @return none
	 * @author Loki
	 */
	function __construct()
	{
		$this->_setSecureVars();
		$this->initAuthObject();
		//проверяем сессию и берем данные из сессии, если они там есть
		$this->_checkSessionValid();
		//производим проверку принудительного сброса сессии
		$this->_autologin();
		$this->_setUserData();
		$this->_updateLastRequest();
	}
	
	/**
	 * Алиасдля instanse
	 * 
	 * @return object   экземпляр текущего класса
	 */
	public static function i()
	{
		return self::instance();
	}

	/**
	 * Возвращает, а при необходимости создает экземпляр текущего класса
	 * 
	 * @return object   экземпляр текущего класса
	 */
	public static function instance()
	{
		if (self::$instance === null) {
			self::$instance = new self;
		}
		return self::$instance;
	}
	
	/**
	 * Создание объектов авторизации и текущего пользователя
	 * 
	 * @return none    
	 */
	private function initAuthObject()
	{
		if (!config::get('core.auth_module')) {
			config::set('core.auth_module', 'login');
		}
		
		$classname=config::get('core.auth_module').'_user';
		if (!Factory::get('auth'));
			Factory::set(
				'auth',
				config::get('core.auth_module').'_auth',
				LAB_PATH_MODULES
				.'/'.config::get('core.auth_module')
				.'/class.auth.php'
			);
		if(!Factory::get('user'));
			Factory::set(
				'user',
				config::get('core.auth_module').'_user',
				LAB_PATH_MODULES
				.'/'.config::get('core.auth_module')
				.'/class.user.php'
			);
		
		$this->auth=Factory::create('auth');
	}
	
	public function setCurrentUser($user)
	{
		$this->auth->setCurrentUser($user);
	}
	
	public function __set($key, $value)
	{
		$this->auth->$key=$value;
	}
	
	public function __get($key)
	{
		return $this->auth->$key;
	}
	
	public function __call($name, $args)
	{
		return call_user_func_array(array($this->auth->user, $name), $args);
	}
	
	private function _autologin()
	{
		return $this->auth->autologin();
	}
	
	/**
	 * Проверяет совпадает ли хэш сессии с расчетным. Если не совпадает - разлогинивает
	 * 
	 * @return none    
	 */
	private function _checkSessionValid()
	{
		//если не совпадает ip - разлогиниваем пользователя
		if (('ip+user_agent'==config::get('core.session_key')
			|| 'ip'==config::get('core.session_key'))
			&& $this->ip!=self::getIP()) {
					return $this->auth->setGuest();
		}
		
		//если не совпадает user_agent и не указан токен
		//или токен передан но не совпадает
		//разлогиниваем пользователя
		//проверка токена нужна для корректного общения flash  с сервером
		if ('ip+user_agent'==config::get('core.session_key')
			&& $this->user_agent!=$_SERVER['HTTP_USER_AGENT']
			&& (!$this->token
			|| $this->token!=$_REQUEST['token'])) {
					return $this->auth->setGuest();
		}
	}
	
	public function logout()
	{
		return $this->auth->logout();
	}
	
	/**
	 * Возвращает количество найденных пользователй с похожим именем
	 * 
	 * @param string $login
	 * 
	 * @return int
	 */
	public function IsLoginExists($login)
	{
		return $this->auth->IsLoginExists($login);
	}
	
	/**
	 * Возвращает экземпляр объекта пользователя
	 * 
	 * @return object 
	 */
	public static function getUser($id=null)
	{
		return Factory::create('user', $id);
	}
	
	/**
	 * Устанавливает допонительную информацию о пользователе, если таковая нужна
	 * 
	 */
	private function _setUserData()
	{
		$this->auth->setUserData();
	}
	
	/**
	 * метод выбирает все родительские группы для заданной  и помещает их в массив
	 * @param integer Идентификатор группы
	 * @return array Все группы, являющиеся родителями запрошенной
	 * @author Loki
	 */
	static function getParentGroups($group_id)
	{
		if ($group_id==IS_GUEST) return array();

		if (isset(self::$parent_groups[$group_id])) {
			return self::$parent_groups[$group_id];
		}
		
		$target_id=$group_id;
		$all_groups=self::getAllUserGroups();
		while ($all_groups[$group_id]) {
			$groups[]=$group_id=$all_groups[$group_id];
		}
		return self::$parent_groups[$target_id]=array_reverse($groups);
	}
	
	/**
	 * Возвращает полный перечень существующих групп
	 * 
	 * @return array    дерево групп пользователей
	 */
	public static function getAllUserGroups()
	{
		return Db::i()->selectCol(
			"-- CACHE: 24h
			SELECT parent_id, group_id AS ARRAY_KEY FROM ?_groups"
		);
	}
	
	/**
	 * С интервалом в минуту обновляем в таблице пользователей онлайн
	 * данные текущего пользователя
	 */
	private function _updateLastRequest()
	{
		if ($this->isNotGuest() && $this->request_time<(time()-60)) {
			$this->request_time=time();
			Db::i()->query(
				"INSERT INTO ?_users_online (user_id, request_time)
				VALUES(?d, UNIX_TIMESTAMP(NOW())) ON DUPLICATE KEY UPDATE request_time=UNIX_TIMESTAMP(NOW())",
				$this->id
			);
		}
		if (rand(0, 100)==50) $this->_cleanOnliners();
	}
	
	/**
	 * Проверяет числится ли пользователь на сайте
	 * 
	 * @param int $user_id идентификатор пользователя
	 * 
	 * @return int    время последнего обращения
	 */
	public static  function isOnline($user_id)
	{
		if (!is_array(self::$onliners)) self::$onliners=self::getOnliners();
		return (empty(self::$onliners[$user_id])?0:self::$onliners[$user_id]);
	}
	
	/**
	 * Получает список пользователей обращавшихся к серверу в последние online_time минут
	 * 
	 * @return array    Массив пользователей со временем обращения
	 */
	public static function getOnliners()
	{
		if (config::get('core.online_time')) {
			$maxlifetime=config::get('core.online_time')*60;
		} else {
			$maxlifetime=ini_get('session.gc_maxlifetime');
		}
		
		return Db::i()->selectCol(
			"-- CACHE:60s
			SELECT user_id AS ARRAY_KEY, request_time
			FROM ?_users_online
			WHERE request_time>=UNIX_TIMESTAMP(NOW())-?d", $maxlifetime
		);
	}
	
	/**
	 * Удаляет пользователей "онлайн", которые не обращались к сайту более чем maxlifetime секунд
	 * 
	 * @return none    
	 */
	private function _cleanOnliners()
	{
		if (config::get('core.online_time')) {
			$maxlifetime=config::get('core.online_time')*60;
		} else {
			$maxlifetime=ini_get('session.gc_maxlifetime');
		}
		
		$this->auth->saveLastRequests($maxlifetime);
		Db::i()->query(
			"DELETE FROM ?_users_online
			WHERE request_time<UNIX_TIMESTAMP(NOW())-?d", $maxlifetime
		);
	}
	
	/**
	 * Проверяет переданный токен с хранящимся в сесии
	 * 
	 * @param string $token переданный токен
	 * @param boolen $break прервать выполнение
	 * 
	 * @return boolean    Результат проверки
	 */
	public static function checkToken($token=null, $break=true)
	{
		if (!$token) $token=isset($_REQUEST['token'])?$_REQUEST['token']:null;
	
		if (self::i()->token && self::i()->token==$token) return true;
		if ($break) die('Ошибка сессии');
		return LabCMS::i()->error('Ошибка сессии');
	}
	
	//возвращает ip текущего пользователя
	public static function getIP()
	{
		return $_SERVER['REMOTE_ADDR'];
	}
	
	/**
	 * Установка системных переменных безопасности сессии
	 * 
	 * @return none    
	 */
	private function _setSecureVars()
	{
		$this->token=&$_SESSION['user_sec']['token'];
		if (!$this->token) $this->token=$this->_getToken();
		$this->ip=&$_SESSION['user_sec']['ip'];
		if (!$this->ip) $this->ip=self::getIP();
		$this->user_agent=&$_SESSION['user_sec']['user_agent'];
		if (!$this->user_agent) $this->user_agent=$_SERVER['HTTP_USER_AGENT'];
		$this->request_time=&$_SESSION['user_sec']['request_time'];
	}

	/**
	 * Генерирует токен
	 * 
	 * @return string    токен
	 */
	private function _getToken()
	{
		if (isset($this->id) && $this->id>0)
			return md5($this->pass.md5(SECURECODE.date("Ymd", time()-18000)));
		else
			return md5(rand().rand());
	}
}