<?php
/**
CrugeWebUser

es un gestor que permite manejar al usuario que ha iniciado sesion o que pretende iniciarla.
consume a : CrugeUser

esta clase necesita ser instalada en config, mediante:

'components'=>array(
'user'=>array(
'allowAutoLogin'=>true,
'class' => 'application.modules.cruge.components.CrugeWebUser',
),

una vez instalada puede ser accedida mediante:

Yii::app()->user
Yii::app()->user->isGuest()

Todos los dem�s miembros de CWebUser se proveen, a excepcion de algunos que
son sobreescritos.

IMPORTANTE:

No usar CHttpSession porque interfiere con el uso interno que se le da a $_SESSION dentro de CWebUser en los metodos getState setState.

para almacenar valores usar $this->setState('nombreVariable','valor') y
$this->getState('nombreVariable','defaultValue');


@author: Christian Salazar H. <christiansalazarh@gmail.com> @salazarchris74
@license protected/modules/cruge/LICENSE
 */
class CrugeWebUser extends CWebUser implements IWebUser
{

    private $_lastError = "";
    private $_access = array(); // cache para rbac por checkaccess
    private $_ui = null;
    private $_um = null;

    public function init()
    {
        parent::init();
    }

    /**
    nuevo metodo, para saber el error qe ocurrio, con traduccion incorporada
     */
    public function getLastError()
    {
        return $this->_lastError;
    }

    /*	nuevo metodo que se accede via: Yii::app()->user->getUser();
        @returns instancia de ICrugeStoredUser o null
    */
    public function getUser()
    {
        return $this->getum()->getUserFromSession($this->getICrugeSession());
    }

    /**
    da acceso directo al valor (solo lectura) de un campo personalizado.

    si el nombre del campo no existe o el usuario no ha inciado sesion se retorna ""

    ejemplo

    echo Yii::app()->user->getField('email');
    echo Yii::app()->user->getField('firstname');
     */
    public function getField($fieldname)
    {
        $user = $this->getUser();
        if ($user != null) {
            return $this->getum()->getFieldValue($user, $fieldname);
        } else {
            return "";
        }
    }

    /*
        helper para ayudar a controlar rapidamente el acceso a funciones que requieren
        de un usuario, si no hay sesion se emite una excepcion.
    */
    public function noGuestAllowed()
    {
        if ($this->isGuest) {
            throw new CrugeException("debe iniciar sesion");
        }
    }

    /*
        extension que permite consultar si este usuario (aun siendo invitado) tiene
        acceso o no a un determinado token de autenticacion identificado por su itemName.

        ejemplo:

        if(Yii::app()->user->checkAccess('createPostOperation')){
            ..create post..
        }else{
            echo "access denied";
        }

        @itemName: nombre del item a ser comprobado para el usuario autenticado
        @params: los argumentos pasados al businessRule
        @descripcion: opcional, si rbacSetupEnabled es true, entonces se usara esta descripcion para crear el CAuthItem requerido cuando este no exista en la lista de operaciones.

        @returns true o false.  (si usuario activo es superadmin retorna true incondicionalmente)
    */
    public function checkAccess($itemName, $descripcion = "", $params = array())
    {

        // si esta habilitada la bandera de configuracion creara el CAuthItem si es requerido
        // y este no existe.
        //
        if (CrugeUtil::config()->rbacSetupEnabled == true) {
            // esto no es eficiente en ambientes de produccion ya configurados plenamente
            // por tanto cuando se hayan establecido todos los permisos entonces
            // habra que deshabilitar este flag (rbacSetupEnabled) en la configuracion mayor
            if (!$this->getrbac()->getAuthItem($itemName)) {
                $this->getrbac()->createAuthItem(
                    $itemName,
                    CAuthItem::TYPE_OPERATION
                    ,
                    $descripcion
                );
            }
        }

        if ($this->isSuperAdmin) {
            return true;
        } else {
            $ok = $this->getrbac()->checkAccess($itemName, $this->getId(), $params);
            if ($ok == false) {
                // no tiene el permiso asignado.
                // reportara el error para ser visualizado luego
                if (CrugeUtil::config()->rbacSetupEnabled == true) {
                    $ai = $this->getrbac()->getAuthItem($itemName);
                    if ($ai !== null) {
                        $this->getui()->addError(
                            $itemName
                            ,
                            $this->getrbac()->getAuthItemTypeName($ai->type)
                            ,
                            $descripcion
                        );
                    }
                }
            }
            return $ok;
        }
    }

    /*
        redirige al usuario a la pagina indicada por loginUrl en caso de que
        se detecte que es un invitado, luego del login es redirigido a la pagina a donde queria
        ir originalmente.

        este metodo es mejor usado en ambientes en donde no se esta usando a CAccessControlFilter, quien internamente invoca a Yii::app()->user->loginRequired cuando detecta que una regla ha fallado.
    */
    public function checkLoginRequired()
    {
        if ($this->isGuest) {
            $this->loginRequired();
        }
    }

    public function getIsSuperAdmin()
    {
        return ($this->name == CrugeUtil::config()->superuserName);
    }

    /*
        entrega un componente (CrugeUi) listo para ser usado, que se encarga de dar
        datos para la interfaz de usuario

        ejemplo:

        Yii::app()->user->ui
    */
    public function getui()
    {
        if ($this->_ui == null) {
            $this->_ui = new CrugeUi();
        }
        return $this->_ui;
    }

    public function getum()
    {
        if ($this->_um == null) {
            $this->_um = new CrugeUserManager();
        }
        return $this->_um;
    }

    /*
        permite llamar al authManager directamente usando:
        Yii::app()->user->rbac

        previamente se debio declarar a CrugeAuthManager como la clase que administra a
        authmanager, eso se hace en components.
    */
    public function getRbac()
    {
        return Yii::app()->getAuthManager();
    }

    /*
        permite conocer el sistema del usuario, si es un guest, el sistema sera nulo,
        si no es guest, dara la lista de sistemas del usuario a las que el pertenece
    */
    /*
        TODO:


    */

    /*  retorna el numero de usuario, tomado de la sesion iniciada
        si se quiere obtener acceso al usuario completo:
            Yii::app()->user->getUser()->getPrimaryKey()
    */
    public function getId()
    {

        $_crugesesion = $this->getICrugeSession();
        if ($_crugesesion == null) {
            return CrugeUtil::config()->guestUserId;
        }
        $userModel = $this->getum()->getUserFromSession($_crugesesion);
        if ($userModel != null) {
            return $userModel->getPrimaryKey();
        }
        return CrugeUtil::config()->guestUserId;
    }

    public function getIsGuest()
    {
        return ($this->getId() == CrugeUtil::config()->guestUserId);
    }

    public function getName()
    {
        $model = $this->getICrugeSession();
        if ($model != null) {
            return $model->getSessionName();
        } else {
            return CrugeTranslator::t("invitado");
        }
    }

    public function getEmail()
    {
        $u = $this->getUser();
        if ($u != null) {
            return $u->email;
        }
        return "";
    }

	protected function restoreFromCookie()
	{
		// invocada cuando allowAutoLogin es true.

		// 1. Cuando se invoca a login() y allowAutoLogin es true y
		// ($duration > 0), se invoca a saveToCookie guardando
		// alli el ID del usuario.
		
		// 2. Se recuperara el ID del usuario, luego
		// se buscara el objeto CrugeSession que tenga asignado,
		// el mas nuevo, y se revalida a ver si no ha caducado,
		// para finalmente reasignarlo a la autenticacion.
		
		// 3. Si la sesion del usuario en CrugeSession ha caducado
		// o ha sido cerrada por el administrador entonces 
		// el usuario debera iniciar sesion manualmente nuevamente.

		$app=Yii::app();
		$request=$app->getRequest();
		$cookie=$request->getCookies()->itemAt($this->getStateKeyPrefix());
		if($cookie && !empty($cookie->value) && is_string($cookie->value) 
				&& ($data=$app->getSecurityManager()->validateData(
						$cookie->value))!==false)
		{
			// el valor de la cookie es seguro
			$data=@unserialize($data);
			if(is_array($data) && isset($data[0],$data[1],$data[2],$data[3]))
			{
				list($id,$name,$duration,$states)=$data;
				// echo "--data es: id={$id},name={$name}, duration={$duration}--";
				$factory = CrugeUtil::factory();
				$_crugeuser = $factory->getICrugeStoredUserLoadModel($id);
				// si _crugeuser es null hay exception y no continuara.	
				if($this->beforeLogin($id,$states,true))
				{
					$this->changeIdentity($id,$name,$states);
					// esto solo vuelve a darle vida a la cookie por mas tiempo
					if($this->autoRenewCookie)
					{
						$cookie->expire=time()+$duration;
						$request->getCookies()->add($cookie->name,$cookie);
					}
					// busca la ultima sesion cruge, reutilizandola, no crea
					// ninguna nueva, solo reutiliza.
					$_crugesession = $factory->getICrugeSessionFindLastByUser($id);
					if($_crugesession != null){
						$this->setSessionId($_crugesession->primarykey);	
						$this->afterLogin(true);
					}
					else{	
						//	las credenciales estan aun en cookie, validas, pero
						//	el usuario de cruge (al que hace referencia) ya no
						//  tiene una Sesion valida dentro del sistema.
						
					}
				}
			}
		}
		else{
			// las credenciales almacenadas han caducado
		}
	}

    /**
    se SUPONE que este metodo fue llamado tras un $identity->authenticate exitoso,
    por tanto estamos garantizando que identity->getId() tiene un identificador valido de
    un ICrugeStoredUser o un 0 si no se autentico.
     ***el argumento $duration es pedido solo por compatibilidad, no se usa aqui.***

    la duracion del identificador en memoria de sesion dependera de la duracion de
    configuracion de PHP CONFIG, pero no asi la duracion del objeto de sesion (CrugeSession)
    el cual durara y sera reutilizado hasta que caduque o sea cerrado.
     */
    public function login( /*IUserIdentity*/
        $identity,
        $duration = 0
    ) {

        if (!($identity instanceof CrugeUser)) {
            throw new CrugeException(
                "Por favor cambie las referencias a '" . get_class($identity) . "' por 'CrugeUser'"
            );
        }


        Yii::log(__CLASS__ . "\nlogin\n", "info");

        $this->_lastError = "";
        $this->clearSessionId();

        // carga el filtro de sesion habilitado para este modulo:
        $filtro = $this->getum()->getSessionFilter();

        // toma al usuario autenticado
        $user = $identity->getUser();
        if ($user == null) {
            // no hay un usuario identificado para iniciar una sesion
            Yii::log(__CLASS__ . "\ngetUser is null\n", "info");
            $this->_lastError = CrugeTranslator::t("debe autenticarse");
            return false;
        }

        $system = $this->getum()->getDefaultSystem();
        if ($system == null) {
            Yii::log(__CLASS__ . "::login. systemName:" . $_sname . " no hallado.", "error");
            throw new CrugeException("debe crear un registro en la tabla cruge_system");
        }

        // aplica credenciales sobre el sistema para obtener una sesion
        Yii::log(__CLASS__ . "\nfiltro->startSession\n", "info");
        if (($usersession = $filtro->startSession($user, $system)) != null) {
            Yii::log(__CLASS__ . "\nfiltro->startSession OK\n", "info");

            if ($filtro->onStore($usersession)) {
                // ahora si...guarda el identificador de sesion que getId devolvera
				$_SESSION['cruge_redirect_count']=0;
                $this->setSessionId($usersession->getPrimaryKey());
				if($this->allowAutoLogin && ($duration > 0))
					$this->saveToCookie($duration);
				$filtro->onLogin($usersession);
                return true;
            } else {
                Yii::log(CHtml::errorSummary($usersession, "error al guardar una sesion"), "error");
                $this->_lastError = CrugeTranslator::t("Error al almacenar sesion");
                return false;
            }
        } else {
            Yii::log(__CLASS__ . "\nfiltro->startSession error.\n" . $filtro->getLastErrorDescr(), "info");

            $this->_lastError = $filtro->getLastErrorDescr();
            return false;
        }
    }

    public function logout($destroySession = true)
    {
        $result = false;
        $usersession = $this->getICrugeSession();
        if ($usersession != null) {
            $filtro = $this->getum()->getSessionFilter();
			// para compatibilidad con anteriores versiones del filtro
			if(method_exists($filtro,'onBeforeLogout'))
				if($filtro->onBeforeLogout($usersession) == false)
					return false;
            $usersession->logout();
            if ($filtro->onStore($usersession)) {
                $filtro->onLogout($usersession);
                $result = true;
            } else {
                Yii::log(CHtml::errorSummary($usersession, "error al guardar una sesion"), "error");
                $this->_lastError = CrugeTranslator::t("Error al almacenar sesion");
            }
        }
        parent::logout($destroySession);
        return $result;
    }

    /**HASTA AQUI llegan los metodos de la interfaz IWebUser*/

    /**

    estas funciones de aqui para abajo no pertenecen a la interfaz: IWebUser

     */
    private function getSessionId()
    {
        return $this->getState('_sessionid_', '0');
    }

    private function setSessionId($newValue)
    {
        $this->setState('_sessionid_', $newValue);
    }

    private function clearSessionId()
    {
        $this->setState('_sessionid_', '0');
    }

    /*
        carga una sesion de acuerdo al id persistente en cookies,
        pero al buscarlo lo revalida a ver si esta vigente, sino deriva en un evento
        de onSessionExpired
    */
    private function getICrugeSession()
    {
        // idsession usado al momento del login almacenado en la memoria de sesion
        $model = $this->getum()->loadSession($this->getSessionId());
        if ($model != null) {
            if ($model->validateSession()) {
                return $model;
            } else {
                //if($model->isSessionExpired())
                $this->getum()->getSessionFilter()->onSessionExpired($model);
                // retorna null porque la sesion expiro.
                return null;
            }
        } else {
            return null;
        }
    }
}

;