<?php defined('SYSPATH') or die('No direct access allowed.');

/**
 * OSB Auth driver.
 *
 * @package    OSB
 * @subpackage Account
 * @category   Auth
 * @author     Deon George
 * @copyright  (c) 2010 Deon George
 * @license    http://dev.leenooks.net/license.html
 */
class Auth_OSB extends Auth_ORM {
	/**
	 * OSB authentication is controlled via database queries.
	 *
	 * This method can be used to test two situations:
	 * 1) Is the user logged in? ($role == FALSE)
	 * 2) Can the user run the current controller->action ($role == TRUE)
	 *
	 * @param   boolean If authentication should be done for this module:method (ie: controller:action).
	 * @return  boolean
	 */
	public function logged_in($role = NULL, $debug = NULL) {
		$status = FALSE;

		// Get the user from the session
		$user = $this->get_user(FALSE);

		// If we are not a valid user object, then we are not logged in
		if (is_object($user) AND $user instanceof Model_Account AND $user->loaded()) {

			if (Config::sitemode() == Kohana::DEVELOPMENT && Kohana::config('config.site_debug'))
				SystemMessage::add(array('title'=>'Debug','type'=>'debug','body'=>Kohana::debug(array('user'=>$user->username,'r'=>$role))));

			if (! empty($role)) {
				// Get the module details
				$module = ORM::factory('module',array('name'=>Request::current()->controller()));
				if (! $module->loaded() OR ! $module->status) {
					SystemMessage::add(array(
						'title'=>'Module is not defined or active in the Database',
						'type'=>'warning',
						'body'=>sprintf('Module not defined: %s',Request::current()->controller()),
					));

				} else {
					if (Request::current()->directory())
						$method_name = sprintf('%s_%s',Request::current()->directory(),Request::current()->action());
					else
						$method_name = Request::current()->action();

					// Get the method number
					$method = ORM::factory('module_method',array('module_id'=>$module->id,'name'=>$method_name));
					if (! $method->loaded()) {
						SystemMessage::add(array(
							'title'=>'Method is not defined or active in the Database',
							'type'=>'warning',
							'body'=>sprintf('Method not defined: %s for %s',Request::current()->action(),$module->name),
						));

					} else {
						// If the role has the authorisation to run the method
						$group_method = ORM::factory('group_method')
							->where('method_id','=',$method->id);

						$roles = '';
						foreach ($group_method->find_all() as $gm) {
							$roles .= ($roles ? '|' : '').$gm->group->name;

							$ro = ORM::factory('group', array('name' => $gm->group->name));

							// $ro->id == 0 means all users.
							if ($ro->id == 0 OR $user->has('group', $ro)) {
								$status = TRUE;
								$roles = '';
								break;
							}
						}

						if (! $status) {
							if (Config::sitemode() == Kohana::DEVELOPMENT)
								SystemMessage::add(array(
									'title'=>'User is not authorised in Database',
									'type'=>'debug',
									'body'=>sprintf('Role(s) checked: %s<br/>User: %s</br>Module: %s<br/>Method: %s',$roles,$user->username,$module->name,$method->name),
								));
						}
					}
				}

				if (Config::sitemode() == Kohana::DEVELOPMENT)
					SystemMessage::add(array(
						'title'=>'Debug',
						'type'=>'debug',
						'body'=>sprintf('A-User: <b>%s</b>, Module: <b>%s</b>, Method: <b>%s</b>, Role: <b>%s</b>, Status: <b>%s</b>, Data: <b>%s</b>',
							$user->username,Request::current()->controller(),Request::current()->action(),$role,$status,$debug)));

			// There is no role, so the method should be allowed to run as anonymous
			} else {
				if (Config::sitemode() == Kohana::DEVELOPMENT)
					SystemMessage::add(array(
						'title'=>'Debug',
						'type'=>'debug',
						'body'=>sprintf('B-User: <b>%s</b>, Module: <b>%s</b>, Method: <b>%s</b>, Status: <b>%s</b>, Data: <b>%s</b>',
							$user->username,Request::current()->controller(),Request::current()->action(),'No Role Default Access',$debug)));

				$status = TRUE;
			}

		// Check and see if we have a token to login and run the method
		} elseif ((! empty($_REQUEST['token']) AND $token = $_REQUEST['token']) OR $token=Session::instance()->get('token')) {

			if ($user=$this->_get_token_user($token) AND $user !== FALSE)
				$status = TRUE;

		} else {
			if (Config::sitemode() == Kohana::DEVELOPMENT)
				SystemMessage::add(array('title'=>'Debug','type'=>'debug','body'=>'No user logged in'));
		}

		return $status;
	}

	/**
	 * Gets the currently logged in user from the session.
	 * Returns FALSE if no user is currently logged in.
	 *
	 * @param boolean Check token users too
	 * @return  mixed
	 */
	public function get_user($tokenuser=TRUE) {
		$user = parent::get_user();

		// If we are not logged in, see if there is token for the usre
		if ($tokenuser AND $user === FALSE AND $token=Session::instance()->get('token')) {
			$user = $this->_get_token_user($token);
		}

		return $user;
	}

	/**
	 * Get the user that a token applies to
	 *
	 * This will check that the token is valid (not expired and for the request)
	 *
	 * @param $token The token
	 * @return mixed The user
	 */
	private function _get_token_user($token) {
		$mmto = ORM::factory('module_method_token',array('token'=>$token));
		$request = Request::current();
		$user = FALSE;

		if ($mmto->loaded()) {
			if ($mmto->date_expire < time()) {
				SystemMessage::add(array(
					'title'=>_('Token Not Valid'),
					'type'=>'warning',
					'body'=>_('Token expired')));

				Session::instance()->delete('token');
				$mmto->delete();

			} else {
				// Check that the token is for this URI
				$mo = ORM::factory('module',array('name'=>$request->controller));
				$mmo = ORM::factory('module_method',
					array('name'=>$request->directory ? sprintf('%s_%s',$request->directory,$request->action) : $request->action));

				// Ignore the token if this is not the right method.
				if ($mmo->id == $mmto->method_id) {
					// @todo Implement single use tokens
					Session::instance()->set('token',$token);

					$user = ORM::factory('account',$mmto->account_id);
				}
			}
		}

		return $user;
	}

	/**
	 * Logs a user in.
	 *
	 * @param   string   username
	 * @param   string   password
	 * @param   boolean  enable autologin
	 * @return  boolean
	 */
	protected function _login($user, $password, $remember)
	{
		if ( ! is_object($user))
		{
			$username = $user;

			// Load the user
			$user = ORM::factory('account');
			$user->where($user->unique_key($username), '=', $username)->find();
		}

		// If the passwords match, perform a login
		if ($user->has('group', ORM::factory('group', array('name' => 'Registered Users'))) AND $user->password === $password)
		{
			if ($remember === TRUE)
			{
				// Create a new autologin token
				$token = ORM::factory('user_token');

				// Set token data
				$token->user_id = $user->id;
				$token->expires = time() + $this->_config['lifetime'];
				$token->save();

				// Set the autologin cookie
				Cookie::set('authautologin', $token->token, $this->_config['lifetime']);
			}

			// Record our session ID, we may need to update our DB when we get a new ID
			$oldsess = session_id();

			// Finish the login
			$this->complete_login($user);

			// Do we need to update databases with our new sesion ID
			// @todo figure out where this is best to go
			$session_change_trigger = array('cart'=>'session_id');

			if (count($session_change_trigger) AND (session_id() != $oldsess)) {
				foreach ($session_change_trigger as $t => $c) {
					$orm = ORM::factory($t)
						->where($c,'=',$oldsess);

					// @todo There must be a way that ORM can update multiple records with 1 SQL
					foreach ($orm->find_all() as $o)
						$o->set('session_id',session_id())
						->update();
				}
			}

			return TRUE;
		}

		// Login failed
		return FALSE;
	}

	/**
	 * Determine if a user is authorised to view an account
	 *
	 * @param integer Account ID
	 *
	 * @return boolean TRUE if authorised, FALSE if not.
	 */
	public function authorised($aid) {
		if (! $this->get_user())
			return FALSE;

		// @todo Consider caching this.
		$ao = ORM::factory('account',$this->get_user()->id);

		if (! $ao->loaded() OR ($aid != $ao->id AND ! $ao->admin()))
			return FALSE;

		return TRUE;
	}
}
?>