Cruge en

De Yii Framework en Español
Saltar a: navegación, buscar

Contenido

QUÉ ES CRUGE ?

Cruge es un Framework para gestión de Usuarios y RBAC para Yii Framework. Te permite administrar y controlar de forma muy eficiente y segura a tus usuarios y los roles que ellos deban tener en tu Aplicación Web usando tanto un API Visual prefabricada y poniendo a tu disposción un API para controlar usuarios, login, sesiones a nivel de código evitando que tengas que acceder a estas partes empezando de cero.

¿ Por qué se llama Cruge ?

Control (de) Roles, Usuarios y Grupos (Extensión). El nombre Cruge se lo debemos a Esteban Perez (ver contribuciones al inicio).

¿ Y que pasó con la G de Grupos ?

Paciencia. A futuro, se incorporará un mecanismo de organización de usuarios en Grupos. Si bien los roles cumplen ese papel también es cierto que los Grupos ayudan a organizar usuarios para aplicar sobre ellos Roles u otras cosas. Por tanto..podriamos llamarlo: Cru_e (sin la g, pero suena feo). :-)


Conceptos Básicos de un sistema basado en Roles - formal

Los niveles de protección básicos de un sistema RBAC como Cruge son: ROLES, TAREAS y OPERACIONES, las cuales no son el gran descubrimiento del equipo que diseño a Yii Framework, en cambio son una teoría formal bien conocida que ellos implementaron para Yii Framework de una forma básica pero útil y bien hecha.

Un sistema RBAC como lo es Cruge, esta organizado de modo que un administrador principal pueda controlar a sus usuarios. La teoría formal (y es bueno que la comprendas) es que esto no es un invento de la rueda, un sistema RBAC tiene sus fundamentos y fuentes teóricas formales duras, aquí puedes ver una de esas fuentes formales acerca de los sistemas RBAC.

Es bueno comprender por qué esta organizado así (roles<-tareas<-operaciones) y lo explico con un ejemplo práctico:

La empresa JAMBOREE tiene un sistema administrativo, el cual tiene varios procesos delicados, concentremonos en el proceso de "emisión de cheques". Un administrador puede asignar a Juan al ROL "EMISIONCHEQUES", pero a su vez este ROL puede requerir de varias TAREAS que quiza esten en uso en otros ROLES también, y a su vez, cada TAREA esta compuesta de OPERACIONES, las cuales son indivisibles (o atómicas, una operacion -no debe- estar compuesta de operaciones por razones de buena práctica) además cada tarea de éstas pudiera estar comprometida en otras tareas de otros roles. Así de complejo puede ser, por eso es necesario ser ordenado y objetivo al momento de diseñar el nivel de permisos o roles de un sistema RBAC. A esto lo llamo "Anidación". Sigue leyendo.

Siguiendo este ejemplo (párrafo de arriba), para emitir un cheque se necesita que un usuario invitado pueda hacer login, pueda "acceder_al_menu_principal" y pueda "listar_los_cheques_emitidos", para finalmente ejecutar el formulario "crearcheque". Pero a la vez, podríamos tener a otro ROL, aquel del usuario supervisor de cheques emitidos, el cual puede hacer lo mismo a excepción de "crearcheque". Aquí verás a que me refiero con el concepto de "Anidación".

Por tanto, cómo diseñamos ese complejo sistema de permisos ? Con un montón de operaciones todas asignadas a un solo rol ? lo haz hecho así ? pues esta mal hecho, y es grave desordenado y erróneo. Se hace así:

ROL_EMISIONCHEQUES

  • compuesto de:
    • TAREA_acceder_al_menu_principal
    • TAREA_listar_los_cheques_emitidos
    • TAREA_crearcheque

ROL_SUPERVISORCHEQUES

  • compuesto de:
    • TAREA_acceder_al_menu_principal
    • TAREA_listar_los_cheques_emitidos

Como ves, hay tareas en común, además de que la tarea "crearcheque" no esta asignada al rol del supervisor.

¿ Y dónde están las OPERACIONES ? Te habrás preguntado (si me vas siguiendo con atención)...pues las operaciones son el detalle menor, son todos aquellos pasos menores que tu puedes poner en tu sistema regados como arroz por todas partes, las cuales pueden ser en el caso de yii framework los "actions" que comprenden a tu aplicación. Tu asignas a tus usuarios ROLES y TAREAS...y las operaciones déjalas en los detalles propios de cada tarea.

Así continuando, las operaciones de TAREA_acceder_al_menu_principal pudieran ser: action_site_index, action_site_menuprincipal y quizá una que otra operación flotante que tu verificas manualmente mediante el api de Cruge: Yii::app()->user->checkAccess('solo_usuarios_registrados');

Ahora, Yii (o mejor dicho la funcion checkAccess creada por Maurizio Domba de Yii Core) es lo bastante bien hecha como para comprobar la "anidación", supón que en algún lado se hace esta verificación:

 if(Yii::app()->user->checkAccess("action_site_index")) {...}

Ahora supón además que el usuario activo SOLO tiene asignado el rol "ROL_EMISIONCHEQUES" y nada mas. Pues bien, el API y la función checkAccess harán una busqueda con anidación (recursiva) para ver si el usuario activo tiene ese item solicitado: "action_site_index", así sea directamente o mediante la asignacion de esta a una tarea que a su vez esta incluida en un ROL.

En Cruge (y en Yii en general) los items: ROLES, TAREAS y OPERACIONES son internamente lo mismo, ojo sin confusiones aquí, internamente son lo mismo pero para el sistema, no son lo mismo conceptualmente. Son niveles con jerarquía, las tareas no se asignan a operaciones (no se debe, aunque yii lo permita).


Arquitectura Interna de Cruge

Cruge tiene una alta Arquitectura OOP, basada en interfaces, lo que ayuda enormemente a usarla sin modificar en lo absoluto su propio core. Si necesitas cambiar de ORDBM, cruge lo permite. Si necesitas extender el funcionamiento de autenticacion para admitir nuevos metodos tambien lo permite mediante la implantacion de filtros de autenticacion, incluso dispones ademas de filtros insertables para controlar el otorgamiento de una sesion a un usuario y finalmente para controlar los registros y actualizaciones de perfil de tus usuarios. Todo eso sin tocar en lo absoluto el core de Cruge.

Cruge es un API, que incluye una interfaz de usuario predeterminada con el objeto que puedas usar el sistema como viene ahorrandote mucho tiempo. Esta interfaz hace uso del API de forma estricta, es decir, no hay "dependencias espaguetti" las cuales son las primeras destructoras de todo software.

Listado de API's disponibles en Cruge:

  Yii::app()->user
   Para acceder a funciones muy básicas del control de usuarios. Compatible al 100% con Yii Estándar.
  Yii::app()->user->ui
   Provee enlaces a funciones de la interfaz
  Yii::app()->user->um
   Provee acceso al control de los usuarios
  Yii::app()->user->rbac
   Provee acceso a las funciones de RBAC.

Organización interna de Cruge - Sin dependencias Espaguetti!!

  [Tu Aplicacion]--->[Cruge]--->[API]---->[Factory]---->[modelos]

Esto significa que aún dentro de Cruge las dependencias son estrictamente organizadas, es decir, no verás interdependencias, si eso fuera asi estariamos hablando de otra "extension" espaguetti, como aquellas que solo le funcionan a su creador, o que en el mejor de los casos, funcionan...pero manipulandoles el core!!

Explayando mas este punto, este pequeño diagrama (arriba) te muestra que, por ejemplo, el nivel API (supongamos Yii::app()->user->um o Yii::app()->user->ui o cualquiera de las de arriba mencionadas), no accede a otros niveles inferiores sin pasar por el nivel subyacente, dicho de otro modo, cada nivel depende del nivel inferior, esto ayuda a controlar las dependencias, ayudando a futuros colaboradores a entender mejor el código.

¿ Por qué hay un Factory ?

Por una sencilla razón: Un Factory es un punto intermedio al cual un API pregunta o pide algo y el Factory responde, evitando que el API acceda directamente a los modelos específicos, nuevamente ayudando a la organización del código.

Pero bueno...esto ya es parte de un tema acerca de patrones de diseño y demás asuntos que se salen del foco: El uso de Cruge. Ya vamos para allá.


Un ejemplo de uso de roles

Supongamos que tienes un sistema de una libreria, en donde se te ofrece en tu cuenta listar "tus libros favoritos", por tanto podrás borrar, listar, crear, editar cada entrada de esa lista de "Tus Favoritos". Ese mismo sistema también tiene una gestión administrativa a la cual tu no debes entrar porque como habrás adivinado son funciones del dueño de la librería a la cual tu no tendrás acceso.

Basado en ese ejemplo, supongamos que tu te la das de jaker..y quieres entrar con tu cuenta, capturar la URL del enlace:

 "<a href='index.php?r=tucuenta/eliminarFavorito&id=123'>Eliminar tu Favorito Seleccionado</a>" 

y con eso pues...haces un desastre, cambiando el ID 123 "de tu favorito", por el ID 99128 el cual pudiera ser perfectamente el ID de un favorito de otro usuario....grave.

Pues no te sorprendas, hay sistemas que permiten eso, y luego le echan la culpa al jaker...no señor, eso es culpa de quien hizo el sistema: Por qué razón tu puedes ejecutar ese action con un ID de una entrada de favoritos que no es tuya ??!!.

En este caso de ejemplo es donde entran en juego dos cosas: El control de acceso RBAC, y el control de acceso de reglas de negocio. En ese bobo ejemplo de arriba solo se controla la eliminación de favoritos usando solo RBAC, pero debido a la ausencia de validación de reglas de negocio, pues bien te han "jakeado".

Cómo lo resuelves.

En primer lugar, ese action "eliminarFavorito" que pertenece al controller "tucuenta" quien debe tener control de acceso por RBAC, sencillamente para que no venga un invitado a copiar la URL y a pegarla en su browser para tratar de hacerte un desastre en tu aplicación. Un caso mas elegante sería evitar que un usuario tenga acceso a los actions del dueño de la librería de ejemplo de este caso.

Por tanto, si el usuario jperez el cual es un usuario "raso" de la librería trata de acceder a otro lado, digamos a "index.php?r=admin/registralibro", pues recibirá una excepción de "access denied". Si por el caso "happy day" ese usuario raso jperez quiere acceder a su lista de favoritos podrá hacerlo.

Entonces, al programar -solo- el RBAC lograriamos evitar que alguna persona entre a alguna parte del sistema.

Pero ahora, que sucede con ese caso especial en donde un usuario con acceso a favoritos quiere eliminar los favoritos de otro usuario ? El sistema RBAC no ha fallado..le dio acceso al action "eliminarFavorito", porque en la programación de RBAC se le dijo que el usuario raso puede usarla, pero, para eliminar "SU" favorito, no el del vecino. Cómo logramos esto ?

Pues bien, incorporando lógica de negocio en ese action.

Cuidado. Yii y el mecanismo por defecto de AuthManager provee una manera para pasarle al sistema RBAC un argumento para que sea evaluado junto con el RBAC, esto para garantizar de alguna manera que solo el usuario dueño del favorito pueda eliminar solo su favorito. Esta funcion de AuthManager es útil, pero termina hipercomplicando los sistemas, por tanto -a mi- no me gusta, prefiero aplicar una de las reglas de negocio de Python que bien dice: "...mejor explícito, que implícito..".

Cómo protegemos ese action. Simplemente con algo como:

 
  public function actionEliminarFavorito($idfavorito){
    $model = Favorito::model()->findByPk($idfavorito);
    if($model->idUsuarioCreador == Yii::app()->user->id){
       $model->delete();
    }else{
      throw new CHttpException(500,"Disculpe usted no puede eliminar el favorito de otro usuario.");
    }
  }

No te sorprenderá saber o redescubrir que éste es el mecanismo que el generador de Yii (Gii) crea para el metodo actionDelete al momento de hacer un CRUD.

INSTALACIÓN:

Principalmente necesitas obtener cruge, puedes hacerlo por dos vías:

  • Via descarga directa desde Repositorio oficial de Cruge, busca el enlace "download" en la ventana de la derecha junto a "Size (1.5MB)...(Download)". Simplemente descomprimirás el paquete cruge.zip obtenido dentro de tu proyecto asi:
 /home/tuapp/protected/modules/cruge

si tu directorio modules no existe, deberás crearlo a mano anteriormente (obviamente).

  • Vía GIT. Asumo que has configurado tu cliente GIT y que funciona bien. Personalmente recomiendo esta vía, es mas seria y permite que tu proyecto se mantenga al día con el repositorio oficial. Si obtienes cruge por via GIT, haces lo siguiente:
 
cd /home/tuusuario/tuapp/protected
mkdir modules
cd modules
git clone https://christiansalazarh@bitbucket.org/christiansalazarh/cruge.git
# si usas git con ssh puedes usar:
# git clone git@bitbucket.org:christiansalazarh/cruge.git
  


NOTA, SI USAS GIT: Si no tienes una clave SSH creada, deberás usar HTTP en vez de SSH, mira este issue para mayor información: issue#24

Ahora, a configurar Cruge, editarás el siguiente archivo:

 /home/tuusuario/tuapp/protected/config/main.php
 
	1.	dentro de 'import' agregar:
			'application.modules.cruge.components.*',
			'application.modules.cruge.extensions.crugemailer.*',

	2.	dentro de 'modules' agregar:
			'cruge'=>array(
				'tableprefix'=>'cruge_',

				// para que utilice a protected.modules.cruge.models.auth.CrugeAuthDefault.php
				//
				// en vez de 'default' pon 'authdemo' para que utilice el demo de autenticacion alterna
				// para saber mas lee documentacion de la clase modules/cruge/models/auth/AlternateAuthDemo.php
				//
				'availableAuthMethods'=>array('default'),

				'availableAuthModes'=>array('username','email'),
				'baseUrl'=>'http://coco.com/',

				 // NO OLVIDES PONER EN FALSE TRAS INSTALAR
				 'debug'=>true,
				 'rbacSetupEnabled'=>true,
				 'allowUserAlways'=>true,

				// MIENTRAS INSTALAS..PONLO EN: false
				// lee mas abajo respecto a 'Encriptando las claves'
				//
				'useEncryptedPassword' => false,

				// Algoritmo de la función hash que deseas usar
				// Los valores admitidos están en: http://www.php.net/manual/en/function.hash-algos.php
				'hash' => 'md5',

				// a donde enviar al usuario tras iniciar sesion, cerrar sesion o al expirar la sesion.
				//
				// esto va a forzar a Yii::app()->user->returnUrl cambiando el comportamiento estandar de Yii
				// en los casos en que se usa CAccessControl como controlador
				//
				// ejemplo:
				//		'afterLoginUrl'=>array('/site/welcome'),  ( !!! no olvidar el slash inicial / )
				//		'afterLogoutUrl'=>array('/site/page','view'=>'about'),
				//
				'afterLoginUrl'=>null,
				'afterLogoutUrl'=>null,
				'afterSessionExpiredUrl'=>null,

				// manejo del layout con cruge.
				//
				'loginLayout'=>'//layouts/column2',
				'registrationLayout'=>'//layouts/column2',
				'activateAccountLayout'=>'//layouts/column2',
				'editProfileLayout'=>'//layouts/column2',
				// en la siguiente puedes especificar el valor "ui" o "column2" para que use el layout
				// de fabrica, es basico pero funcional.  si pones otro valor considera que cruge
				// requerirá de un portlet para desplegar un menu con las opciones de administrador.
				//
				'generalUserManagementLayout'=>'ui',
			),

	3.	dentro de 'components' agregar:
	        //
			//  IMPORTANTE:  asegurate de que la entrada 'user' (y format) que por defecto trae Yii
			//               sea sustituida por estas a continuación:
			//
			'user'=>array(
				'allowAutoLogin'=>true,
				'class' => 'application.modules.cruge.components.CrugeWebUser',
				'loginUrl' => array('/cruge/ui/login'),
			),
			'authManager' => array(
				'class' => 'application.modules.cruge.components.CrugeAuthManager',
			),
			'crugemailer'=>array(
				'class' => 'application.modules.cruge.components.CrugeMailer',
				'mailfrom' => 'email-desde-donde-quieres-enviar-los-mensajes@xxxx.com',
				'subjectprefix' => 'Tu Encabezado del asunto - ',
				'debug' => true,
			),
			'format' => array(
				'datetimeFormat'=>"d M, Y h:m:s a",
			),
  

Ahora, debes preparar la base de datos, crearás las tablas requeridas en la base de datos de tu aplicacion, usa el script de acuerdo a tu motor de datos: mysql o postgre. Por favor anota que tu usuario por defecto será admin y su clave admin.

Usa el script SQL que cruge provee, copia y pégalo en tu mysqladmin o simplemente importalo, usa la vía que mejor te funcione, a veces importar no funciona en cambio copiar y pegar si.

 /home/tuusuario/tuapp/protected/modules/cruge/data/cruge-data-model.sql   (script de mysql)

Finalmente, y como algo opcional pero útil para que conozcas Cruge, configura el menu de tu aplicación para que incopore un acceso al item de "administración de usuarios" de Cruge, para ello editas tu archivo:

 /home/tuusuario/tuapp/protected/views/layouts/main.php

en el sustituyes el componente CMenu por el que te doy a continuacion.

 
		<?php $this->widget('zii.widgets.CMenu',array(
			'items'=>array(
				array('label'=>'Home', 'url'=>array('/site/index')),
				array('label'=>'About', 'url'=>array('/site/page', 'view'=>'about')),
				array('label'=>'Contact', 'url'=>array('/site/contact')),
				array('label'=>'Administrar Usuarios'
					, 'url'=>Yii::app()->user->ui->userManagementAdminUrl
					, 'visible'=>!Yii::app()->user->isGuest),
				array('label'=>'Login'
					, 'url'=>Yii::app()->user->ui->loginUrl
					, 'visible'=>Yii::app()->user->isGuest),
				array('label'=>'Logout ('.Yii::app()->user->name.')'
					, 'url'=>Yii::app()->user->ui->logoutUrl
					, 'visible'=>!Yii::app()->user->isGuest),
			),
		)); ?>

  

Nota aqui: Es posible que puedas usar el formulario por defecto de Yii (views/login.php), pero para hacerlo funcionar con Cruge debes cambiar en models/FormLogin.php las dos lineas que dicen UserIdentity por CrugeUser. Ver mas detalles

Felicidades. Ya con eso Cruge debería funcionar. Si no lo hace verifica que:

  • Tienes una versión de YII y PHP acorde a los requisitos.
  • Tu instalación de YII no esta defectuosa, a veces se daña al subirla por FTP a un servidor remoto.
  • Verifica la lista de errores frecuentes en este documentos.

Si todo eso ha sido revisado, y has comentado en el foro y aún el problema persiste entonces:

IMPORTANTE: Ahora debes configurar al usuario invitado.

CÓMO ACTUALIZAR TU ACTUAL INSTALACIÓN DE CRUGE

Bueno, en primer lugar si yo tuviese una manera de "prohibir" que cruge se instalara haciendo copy-paste del zip download, lo haría, la razón es la existencia de GIT:

  • Con dos comandos de GIT puedes actualizar toda tu instalacion de Cruge.
  • Cuando no usas GIT, te toca actualizar a mano.

CON GIT:

 1- abres una consola.
 2- cd /blabla/miaplicacionweb/protected/modules/cruge
 3- git fetch
 4- git pull

LISTO. con eso tu instalación de Cruge habrá recibido los cambios desde el repositorio oficial.

ESO SI: Si le metiste mano al core de cruge (a cualquiera de sus archivos)..tendrás problemas. Por esta razon y otras pido que no hagan cambios al core de Cruge, ya que Cruge de por si no necesita de esos cambios, puedes usarlo y adaptarlo -sin tocarlo-. (y si hay algo que Cruge no tiene y necesito arreglarlo ?, pues bien, primero seria bueno que envies ese requisito acá, para ver como te ayudamos, y segundo si no hay manera pues bien trata en lo posible que el cambio pueda hacerse de forma externa, por ejemplo creando una clase extendida, si aun no hay manera bueno deberás saber que al actualizarte esos cambios tendrán conflicto.)

EJEMPLOS DEL USO DEL API DE CRUGE:

Primero, para usar el API de usuarios de Cruge debes acceder por:

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

a diferencia de solo "Yii::app()->user" la cual es el API estándar de funciones de Yii, Yii::app()->user->um te provee funciones de negocio mas altas.


BUSCAR UN USUARIO POR SU USERNAME o EMAIL

  $usuario = Yii::app()->user->um->loadUser('admin@gmail.com',true);
  echo $usuario->username;
  true: es para indicar que cargue los valores de los campos personalizados. por defecto es : false. 


BUSCAR UN USUARIO POR SU ID

  $usuario = Yii::app()->user->um->loadUserById(123,true);
  echo $usuario->username;

BUSCAR UN USUARIO POR UN VALOR DE SU CAMPO PERSONALIZADO

  Yii::app()->user->um->loadUserByCustomField('cedula', $cedula);

en donde "cedula" es el nombre de un campo personalizado, la busqueda se hace de forma optima filtrando desde dentro en la organizacion de clases mucho mas optimo para volumenes grandes.

CREAR UN USUARIO NUEVO USANDO EL API mas abajo existe otra funcion formal para crear un usuario pasandolo incluso valores de campos personalizados, ver tema: "CREAR NUEVO USUARIO". (mas abajo).

  public function actionAjaxCrearUsuario(){
     // asi se crea un usuario (una nueva instancia en memoria volatil)
     $usuarioNuevo = Yii::app()->user->um->createBlankUser();
     $usuarioNuevo->username = 'username1';
     $usuarioNuevo->email = 'username1@gmail.com';
     // la establece como "Activada"
     Yii::app()->user->um->activateAccount($usuarioNuevo);
     // verifica para no duplicar
     if(Yii::app()->user->um->loadUser($usuarioNuevo->username) != null)
     {
        echo "El usuario {$usuarioNuevo->username} ya ha sido creado.";
        return;
     }
     // ahora a ponerle una clave
     Yii::app()->user->um->changePassword($usuarioNuevo,"123456");
     // guarda usando el API, la cual hace pasar al usuario por el sistema de filtros.
     if(Yii::app()->user->um->save($usuarioNuevo)){
           echo "Usuario creado: id=".$usuarioNuevo->primaryKey;
     }
     else{
           $errores = CHtml::errorSummary($usuarioNuevo);
           echo "no se pudo crear el usuario: ".$errores;
        }
     }

LEYENDO UN CAMPO PERSONALIZADO DE UN USUARIO

  Yii::app()->user->um->getFieldValue(Yi::app()->user->id,'nombre');
  para el usuario activo se puede usar directamente asi:
  $nombre = Yii::app()->user->getField('nombre');

OBTENIENDO EL EMAIL DEL USUARIO ACTIVO

  $email = Yii::app()->user->email;

RECORRIENDO VARIOS USUARIOS Y LEER SU EMAIL

  foreach(Yii::app()->user->um->listUsers() as $user)
     echo "email=".$user->email."
";

LEER TODOS LOS CAMPOS PERSONALIZADOS DE UN USUARIO

  foreach($usuario->fields as $campo)
      echo "<p>campo: ".$campo->longname." es: ".$campo->fieldvalue;"</p>";
   

BUSCAR VARIOS USUARIOS QUE TENGAN UN CAMPO ESPECIFICO Supongamos que queremos buscar todos los usuarios que viven en un determinado pais, supongamos además que este campo (pais) fue previamente agregado a los "campos personalizados". La siguiente solución es óptima desde el punto de vista de OOP, funcionará bien y rapido para muchos casos pero si se necesitan hacer listados optimos en volúmenes muy grandes de usuarios entonces obviamente la via será otra: directo sobre las tablas, especificamente buscando primero en la tabla de cruge_fieldvalue.

 (podrás hallar mas abajo una funcion relacionada con este tema, busca por: 

"BUSCAR UN USUARIO POR UN VALOR DE SU CAMPO PERSONALIZADO", es una funcion mas optima aunque solo entrega un usuario)

  
public function listarUsuariosPorPais($paisABuscar){
   $listaUsuarios = array();
   // listas cada usuario
   foreach(Yii::app()->user->um->listUsers() as $user){
      //
      $paisUsuario = Yii::app()->user->um->getFieldValue($user,'pais'); 
       if($paisUsuario == $paisABuscar)
         $listaUsuarios[] = $user;
   }
 return $listaUsuarios;
}
   

Como bien pongo en la nota, si la solucion es requerida a ser aplicada en volúmenes muy grandes de usuarios, mas de 10.000 por ejemplo (o donde se vea que el código de ejemplo es lento) entonces lo que hay que hacer es usar las vistas, algo como: select * from cruge_fieldvalue where idfield = bla; luego, de ahi, filtrar por los registros que tengan determinado valor, para finalmente obtener una lista de usuarios. Repito: esta última vía es solo útil cuando hay demasiados usuarios en la base de datos o cuando el código anterior no es optimo o tarda mucho.

ACCEDER A UN CAMPO PERSONALIZADO DE UN USUARIO:

  $cedula = Yii::app()->user->um->getFieldValue($usuario,'cedula');

ARMANDO UN COMBOBOX CON LA LISTA DE USUARIOS REGISTRADOS MOSTRANDO LOS CAMPOS PERSONALIZADOS

  $comboList = array();
  foreach(Yii::app()->user->um->listUsers() as $user){
     // evitando al invitado
     if($user->primaryKey == CrugeUtil::config()->guestUserId)
        break;
     // en este caso 'firstname' y 'lastname' son campos personalizados
     $firstName = Yii::app()->user->um->getFieldValue($user,'firstname');
     $lastName = Yii::app()->user->um->getFieldValue($user,'lastname');
     $comboList[$user->primaryKey] = $lastName.", ".$firstName;
  }
  echo "Users: ".CHTML::dropDownList('userlist','',$comboList)."<hr/>";

VERIFICAR SI UN USUARIO TIENE ACCESO A UN ROL, TAREA u OPERACION:

  if(Yii::app()->user->checkAccess('admin')){ ...hacer algo...}

MANEJAR EL RBAC: ROLES, TAREAS y OPERACIONES DESDE EL API

En la documentación de la clase protected/modules/cruge/components/CrugeAuthManager.php puedes hallar mayor información con ejemplos. ir


Nuevas funciones del api

(usadas para conectar a CrugeConnector)

BUSCAR UN USUARIO POR SU USERNAME ESPECIFICAMENTE: https://bitbucket.org/christiansalazarh/cruge/changeset/a224a7f80dea6fe1c55f2892e619dee3e5772855#Lcomponents/CrugeUserManager.phpT202

  
 $usuario = Yii::app()->user->um->loadUserByUsername('jperez' /*, true (para que cargue sus campos)*/);

BUSCAR UN USUARIO POR UN VALOR DE SU CAMPO PERSONALIZADO

  Yii::app()->user->um->loadUserByCustomField('cedula', $cedula);

en donde "cedula" es el nombre de un campo personalizado, la busqueda se hace de forma optima filtrando desde dentro en la organizacion de clases mucho mas optimo para volumenes grandes.

EDITAR/ACCEDER UN CAMPO PERSONALIZADO https://bitbucket.org/christiansalazarh/cruge/changeset/a224a7f80dea6fe1c55f2892e619dee3e5772855#Lcomponents/CrugeUserManager.phpT459

 $usuario = Yii::app()->user->um->loadUserByUsername('jperez');
 $campoNombre = Yii::app()->user->um->getFieldValueInstance($usuario,'nombre');
 $campoNombre->setFieldValue("Juanito");
 $campoNombre->save();
 echo "cambiado a: ".$campoNombre->getFieldValue();
 // tambien es valido, pero solo dara el valor, en cambio getFieldValueInstance devuelve el objeto.
 echo Yii::app()->user->um->getFieldValue($usuario,'nombre');


GENERAR UN USERNAME AUTOMATICAMENTE BASADO EN EL EMAIL https://bitbucket.org/christiansalazarh/cruge/changeset/a224a7f80dea6fe1c55f2892e619dee3e5772855#Lcomponents/CrugeUserManager.phpT641

  echo "su usuario será: ".Yii::app()->user->um->generateNewUsername("pepito@gmail.com");
  // si ya existía "pepito" en la lista de usuarios (tomado de "pepito"@gmail.com)
  // cruge generará: "pepito.1" y asi sucesivamente hasta dar con un "pepito.N" que no exista


CREAR NUEVO USUARIO mas arriba hay otro ejemplo parecido, pero esta funcion es mas fuerte porque permite incluir en un array indexado los valores incluso de campos personalizados. https://bitbucket.org/christiansalazarh/cruge/changeset/a224a7f80dea6fe1c55f2892e619dee3e5772855#Lcomponents/CrugeUserManager.phpT674

 
$values = array(
  'username' => 'juanito',
  'email' => 'juanitoperez@gmail.com',
  'nombre' => 'Juanito',    // VALOR DE CAMPO PERSONALIZADO
  'apellido' => 'Perez',  // VALOR DE CAMPO PERSONALIZADO
  'cedula' => '1298912891',  // VALOR DE CAMPO PERSONALIZADO
);
$usuario = Yii::app()->user->um->createNewUser($values);

LOGIN USER https://bitbucket.org/christiansalazarh/cruge/changeset/fefa510a6eced169a9223ac12c8a093c334c13b1#Lcomponents/CrugeUserManager.phpT747

 
  $usuario = Yii::app()->user->um->loginUser('juanito'); // su username o su email..
  if($usuario != null){
    // se le ha iniciado una sesión a juanito, tal cual hubiese iniciado sesion manualmente via forms.
  }

REMOTE LOGIN https://bitbucket.org/christiansalazarh/cruge/changeset/cbca2c9820527e005994a9d4f6c9dbea8251115a#Lcomponents/CrugeUserManager.phpT789

  • A diferencia de Yii::app()->user->um->loginUser, esta funcion remoteLoginInterface puede incluso registrar al usuario e iniciarle la sesión de inmediato (según el argumento "modality" que se le pasa).
  • Puede incluso pasarle valores de inicialización mapeados tal cual los hubiese entregado un sistema remoto como facebook o google.
  • La funcion devuelve la URL a la cual deberíamos redirigir al usuario (si el usuario ya existia o se registro automaticamente se le iniciará la sesión de forma automática pudiendo usar tu aplicación web en el acto), esta url puede ser NULL (si hubo fallo) o la url de registration (si modality es "manual") o la url de login (si modality es "auto", la cual asegura que el usuario se ha creado aunque no haya existido antes).
 
$fieldMap = array(
   // username podria estar aqui, pero si no está se autogenerará en base al email
   // aunque podria ponerse asi:  'username'=>'id' (supon que facebook o google envian un campo 'id' )
   'email'=>'contact/email'   // se nos envia 'contact/email', pero internamente se llama 'email'
 , 'nombre'=>'first_name'     // se nos envia 'first_name', pues lo mapeamos al campo personalizado 'nombre'
 , 'apellido'=>'last_name'    // 
);
// los datos que nos mandan con variables propias de facebook o google:
$values = array(
   'email'=>'juanito@gmail.com'
   'first_name'=>'Juanito',
   'last_name'=>'Perez',
);
$mod = 'auto'; // podria ser auto, manual o none.  auto=registra al usuario si no existe.
$resultado = Yii::app()->user->um->remoteLoginInterface($fieldMap,$values,$mod,$info);
if(!$resultado){
  echo "algo ha fallado: ".$info;
}else{
  // si modality es 'auto' la variable $info nos informara con el valor 'registration'
  // en el caso de que haya tocado registrar al usuario porque no existía.
  // en tal caso al usuario ya se le ha iniciado la sesion automaticamente 
  // con Yii::app()->user->um->loginUser 
  $this->redirect($resultado);
}


VARIABLES DEL SISTEMA

5-cruge-system-options.png

Las variables controlan el comportamiento de Cruge, por ejemplo, la pantalla de Registro, detener el sistema, duración de la sesión etc. Las variables del sistema se pueden modificar mediante el menu de administración de usuarios en el sub item 'Variables del Sistema'.

Las variables del sistema estan contenidas en el modelo /cruge/models/data/CrugeSystem.php, también puedes acceder a las variables del sistema mediante API así:

  if(Yii::app()->user->um->getDefaultSystem()->getn('registerusingcaptcha')==1){...}

Puedes editar las variables en línea mediante la URL:

  <?php echo Yii::app()->user->ui->userManagementAdminLink; ?>

En este menu verás las siguientes opciones:

1. Detener Sistema. si esta activada no se permitira iniciar sesion o usar el sistema en general.


2. No Admitir Nuevas Sesiones. solo bloquea el login, es decir nuevas sesiones de usuario.


3. Minutos de Duración de la Sesión. el tiempo en minutos en que una sesion expira.


4. Registrarse usando captcha. si esta activada presenta un captcha al momento de registrar a un nuevo usuario.


5. Activación del usuario registrado. opciones para aplicar al nuevo usuario registrado, puedes activarlo inmediatamente, o mediante activacion manual por parte de un administrador, o mediante un correo de verificacion.


6. Asignar Rol a usuarios registrados. es el rol que tu quieres que se le asigne por defecto al usuario recien registrado.


7. Ofrecer la opción de Registrarse en la pantalla de Login. Esta opción habilita el link de "registrarse" en la pantalla de login. para que esta opcion funcione necesitas actualizar tu base de datos con el siguiente script sql:

    alter table cruge_system add registrationonlogin integer default 1;

8. Registrarse usando terminos. si lo activas, el usuario que se va a registrar debe aceptar los terminos que tu indiques.


9. Etiqueta. si la opcion anterior esta activa se le hara una pregunta, aqui puedes escribir esa pregunta, por ejemplo: "Por favor lea los terminos y condiciones y aceptelos para proceder con su registro"


10. Términos y Condiciones de Registro. El texto de las condiciones para registrarse, solo aplica si la opcion de "registrarse usando terminos" esta activada.

MANEJANDO ENLACES

Puedes invocar al API UI de Cruge para acceder a los enlaces. Esto es importante debido a la complejidad de anidamiento de Cruge debido a que es un módulo, por tanto para ayudarte y evitarte errores es mejor que accedas a las URL de Cruge usando el API.

 Yii::app()->user->ui   (cuidado: user->ui no es lo mismo que user->um, son API's distintas)


Lista de Enlaces listos para usar:

 
  <?php echo Yii::app()->user->ui->loginLink; ?>
  <?php echo Yii::app()->user->ui->logoutLink; ?>
  <?php echo Yii::app()->user->ui->passwordRecoveryLink; ?>
  <?php echo Yii::app()->user->ui->userManagementAdminLink; ?>
  <?php echo Yii::app()->user->ui->registrationLink; ?>
  

debido a características de Yii Framework, estas dos formas son equivalentes:

 <?php echo Yii::app()->user->ui->getLoginLink('iniciar sesion'); ?>
 <?php echo Yii::app()->user->ui->loginLink; ?>

para obtener solo la URL y no un objeto generado con CHtml::link (lo cual es lo que devuelven las funciones de Yii::app()->user->ui que terminan en 'Link'), se dispone además de la misma función pero en forma URL, por tanto puedes obtener la URL asi:

  <?php $miUrl = Yii::app()->user->ui->getLoginUrl(); ?>

la cual te retornará una Url lista (tal cual como aquellas devueltas por CHtml::normalizeUrl) para que las uses donde necesites.

Presentando el menú de administración de usuarios

Para facilitar el trabajo Cruge trae un menu item con todas las funciones listas en un menú, con solo pasarle este menu a un componente CMenu verás que automaticamente se crea una lista con todas las funciones necesarias para administrar a los usuarios:

 <?php
 // $items sera una array listo para insertar en CMenu, BootNavbar o similares.
 $items = Yii::app()->user->ui->adminItems;
 ?>

Usando Bootstrap:

Screenshots.gif
  
   <?php 
   $this->widget('bootstrap.widgets.BootNavbar', array(
      'fixed'=>false,
      'brand'=>"Tu App",
      'items'=>array(
      array(
         'class'=>'bootstrap.widgets.BootMenu',
         'items'=>array(Yii::app()->user->ui->adminItems
      ),
   )))); ?>
   


También dispones de esta función del API de Cruge:

  Yii::app()->user->ui->userManagementAdminUrl

La cual brinda toda la lista de actividades a realizarse, de hecho se utiliza como punto de ejemplo tras instalar Cruge:

  
<?php $this->widget('zii.widgets.CMenu',array(
'items'=>array(
array('label'=>'Home', 'url'=>array('/site/index')),
array('label'=>'About', 'url'=>array('/site/page', 'view'=>'about')),
array('label'=>'Contact', 'url'=>array('/site/contact')),
array('label'=>'Administrar Usuarios'
, 'url'=>Yii::app()->user->ui->userManagementAdminUrl   // <-- AQUI VA
, 'visible'=>!Yii::app()->user->isGuest),
array('label'=>'Login'
, 'url'=>Yii::app()->user->ui->loginUrl
, 'visible'=>Yii::app()->user->isGuest),
array('label'=>'Logout ('.Yii::app()->user->name.')'
, 'url'=>Yii::app()->user->ui->logoutUrl
, 'visible'=>!Yii::app()->user->isGuest),
),
)); ?>
   

USO DE LAYOUTS

Cruge te permite que su interfaz de usuario pueda ajustarse a tu sitio web usando lo que en Yii se conoce como Layouts. Por favor conoce mas acerca del uso de layouts en el sitio oficial de Yii Framework, es importante que lo conozcas.

Básicamente un "Layout" en Yii significa que es un código que va a contener a otro. En este caso, cuando tu usas tu aplicación Yii por defecto se te entrega un layout llamado "main" y otro llamado "column1". Cuando tu usas una vista en Yii su controlador va a poner un layout: $this->layout = '\\Layouts\main', pero a su vez main incorpora a "column1". Por tanto tu vista va a ser renderizada con "column1" y luego finalmente con "main". Usando este principio simple verás que a Cruge puedes decirle con qué layout quieres que renderize sus vistas, por esta única y básica razón te digo que: No es necesario en lo absoluto modificar las vistas internas de Cruge porque son controlables con layouts.

Hay personas que modifican estas vistas, es un error, porque si mas adelante salen actualizaciones de Cruge no las podrán aprovechar, y en el mejor de los casos podrán causar problemas a Cruge y a toda la aplicación.

Ejemplo:

Supón que quieres que el formulario de registro de nuevo usuario se presente en un esquema de diseño distinto al que yii trae por defecto, entonces tú podrías crear un nuevo layout que se ajuste a tus necesidades y luego indicarle a Cruge mediante la configuración del componente cuál sería ese layout a usar cuando un usuario quiera registrarse, se hace así (ejemplo con bootstrap):

  
<?php
'components'=> array(
  'loginLayout'=>'//layouts/bootstrap',
  'registrationLayout'=>'//layouts/bootstrap',
  'activateAccountLayout'=>'//layouts/bootstrap',
  'generalUserManagementLayout'=>'//layouts/bootstrap',
),
?>
   

Te cuidado especial con "generalUserManagementLayout": este es un layout especial que requiere que dentro de él haya un Portlet, éste último es para presentar las funciones de administracion de usuarios. Para ello por defecto Cruge apunta este valor a: "ui" el cual es el nombre de un layout prefabricado que ya trae un Portlet, practicamente idendico al que Yii trae por defecto llamado //layouts/column2. El Layout para UI de Cruge por defecto es:

 tuapp/protected/modules/cruge/views/layouts/ui.php

Como escribí mas atrás, en este layout (ui.php) hay un Portlet, que será llenado con los items de administración en linea de Cruge, estos items salen del modulo UI de Cruge, el cual es accesible usando:

  Yii::app()->user->ui->adminItems

CRUGE RBAC

8-cruge-visual-role-task-operation-manager.png
9-cruge-visual-role-assignment.png

El RBAC que Cruge implementa es el mismo de Yii Framework de fábrica, salvo algunos agregados que he notado que han sido incorporados en las nuevas versiones de Yii. RBAC Es un sistema de control de acceso basado en roles (por sus siglas en ingles). Todo el mecanismo RBAC puede ser manejado mediante la interfaz (UI) de Cruge, o mediante su API.

Las dos modalidades para usar en este mecanismo son:

  • Usar checkAccess para verificar si el usuario tiene permiso.
  • Controlando el Acceso a un "controller" o a un "action" usando CrugeAccessControlFilter

Usar checkAccess para verificar si el usuario tiene permiso

Es basicamente el mismo mecanismo que provee Yii, pero en Cruge se ha ampliado un poco mas. Para usar este mecanismo: en cualquier parte de tu codigo fuente puedes poner lo siguiente:

  
<?php
if(Yii::app()->user->checkAccess('puede_ver_menu_sistema')) { 
...mostar menu sistema... 
}
?>

(este mecanismo “directo” fue incorporado en las mas recientes versiones de Yii, para la fecha de salida de Cruge al aire no estaba disponible para Yii estándar, antes había que acceder mediante el componente AuthManager de Yii)

Controlando el Acceso a un "controller" o a un "action" usando CrugeAccessControlFilter

Este mecanismo es muy útil, porque permite controlar el acceso a tus controllers/actions de forma totalmente automatizada y controlada mediante la UI de Cruge. Para usar este mecanismo, necesitarás incluir en tu Controller (cualquiera que tú uses y que desees controlar a nivel de permisos) el siguiente código:

  
<?php
   ..cuerpo de tu controladora...
   public function filters()
   {
      return array(array('CrugeAccessControlFilter'));
   }
   ..cuerpo de tu controladora...
?>
   

Al usar CrugeAccessControlFilter estas permitiendo que Cruge controle el acceso tanto al “controller” en general como al “action” especifico. Ejemplo:

Un usuario cualquiera, incluso un invitado, intenta acceder a la siguiente URL:

  index.php?r=empleado/vernomina.

pues bien, Cruge verificara dos cosas:

  a) el acceso a la controladora: 'Empleado'.
  b) el acceso al action 'Vernomina'.

lo hara de esta forma:

  a) verifica si el usuario (aun invitado) tiene asignada la operacion:  'controller_empleado'
  b) verifica si el usuario (aun invitado) tiene asignada la operacion: 'action_empleado_vernomina'

si ambas condiciones se cumplen (a y b) entonces tendrá acceso al action.

  • Si tu quieres denegar el total acceso a un controller simplemente no le asignas al usuario la operacion que tenga el nombre del controller antecedido de la palabra 'controller_'.


  • Si tu quieres denegar el acceso a un action de un controller simplemente no le asignas al usuario la operacion que tenga el nombre del action: 'action_nombrecontroller_nombreaction'.

Programacion del RBAC: diferencias con otras extensiones.

En el caso de Cruge, la programacion de RBAC no se hace "asumiendo que un usuario va pasar por ahi.." (modo Yii Rights). En cambio en Cruge, debes "tratar de hacer pasar al usuario por donde quieres", este ultimo metodo, a mi juicio, es mas seguro porque te obliga a verificar que realmente el usuario pudo acceder o no a tal o cual parte.

Modo de Programación del RBAC

Para activarlo, en la configuracion de tu aplicación web (config/main.php) debes poner a "true" estos dos argumentos que explico en detalle:

 'rbacSetupEnabled'=>true,

Permitirá que las operaciones se vayan creando (valor true) en la tabla de la base de datos a medida que vas probando el sistema, de lo contrario deberas crear las operaciones a mano. Las operaciones que se crearan automaticamente seran: 'controller_nombredetucontroller' y 'action_tucontroller_tuaction'.

¿ Qué sucede si rbacSetupEnabled esta en valor false ?

Explico con un ejemplo. Si el valor esta en false y creas una nueva controladora o un nuevo action en una existente, entonces el sistema RBAC dirá que -no tiene permiso- para ese action o controladora. Para solucionar esto tienes dos vias: creas la operacion a mano, o pones a true el valor de rbacSetupEnabled y tratas de usar ese controller o action para que sea Cruge quien registre esa operacion, posteriormente tu deberás asignar esa nueva operación al rol de los usuarios.

Lee acerca de CrugeAccessControlFilter para que sepas como pedirle a Cruge que controle tu controladora y action. [[1]]


 'allowUserAlways'=>true,

Permitirá el paso (valor true) al usuario aunque este no tenga permiso. Cuando estás en produccion ponlo en 'false' lo que causara que el usuario reciba una excepción si no dispone del permiso para usar cualquier rol, tarea u operación. Es bueno ponerlo en TRUE para el momento en que estas configurando los permisos.

Desplegando una lista de permisos requeridos al pie de tu página

Aunque allowUserAlways tenga valor TRUE (el cual hara que cruge siempre de paso con checkAccess) siempre podrás conocer que permisos "fallaron" (permisos que se requieren para que el usuario pueda usar una parte del sistema), puedes agregar a tu layout principal una llamada a displayErrorConsole, asi:

  <?php echo Yii::app()->user->ui->displayErrorConsole(); ?>

¿ y esto para qué sirve ?

Sirve cuando estas diseñando los permisos de tus usuarios. Pones allowUserAlways en true, luego vas haciendo pasar al usuario con el rol a programar por todo el sistema requerido e irás viendo al pie de la página la lista de permisos que se han requerido. En un browser paralelo abres el administrador de cruge con tu superusuario y vas aprobando los permisos que se requieran.

Es una herramienta de programación que no estará disponible cuando rbacSetupEnabled sea false, pero no tienes de que preocuparte, cuando este sea el caso simplemente no imprimira nada, por tanto puedes dejarla en tu layout con seguridad de que no va a molestar.

Usando el LOG

Adicionalmente todos los errores de permiso que se generen seran reportados en el log bajo el key 'rbac', para poder visualizar los errores en protected/runtime/application.log deberas configurar tu config/main.php para indicar el key del log:

  
<?php
'log'=>array(
  'class'=>'CLogRouter',
  'routes'=>array(
  array(
   'class'=>'CFileLogRoute',
   'levels'=>'error, info, rbac', // <--- agregar 'rbac'
  ),
)),
?>
   

eso causara que en el archivo de log (protected/runtime/application.log) se generen entradas como las que describo a continuación:

  
2012/07/27 15:24:25 [rbac] [application] PERMISO REQUERIDO:
invitado
iduser=2
tipo:operacion
'itemName:action_catalog_imageh'
   

Tips de Programacion del RBAC

  • Cuando quieras programar el RBAC con cruge, usa dos navegadores: uno abierto con un usuario administrador, para que puedas ir activando las operaciones para un rol especifico a medida que sea necesario, y abre otro navegador con el usuario que tenga el rol que quieres programar. No olvides tener activado el flag: rbacSetupEnabled.


  • Por ejemplo, quieres que el usuario 'juan' que tiene asignado el rol 'empleado_regular' tenga solo acceso solo a donde quieres, entonces en el segundo navegador inicia sesion con 'juan', y tratas de ir al menu u operacion requerida, cruge ira informando al pie de la pagina los permisos requieridos. Luego con el navegador que tiene abierto el usuario 'admin' entonces vas verificando los permisos y se los asignas al rol.


  • Considera que cuando rbacSetupEnabled esta habilitado, entonces, asumiendo ademas que no hay ninguna operacion creada iras viendo que cruge creara las operaciones automaticamente de acuerdo a donde el usuario 'juan' vaya pasando, solo las crea si previamente no existen.


  • Ejemplo: no hay ninguna operacion creada, entonces el usuario juan quiere entrar a 'site/index', por tanto, si la controladora 'siteController' esta manejada con el filtro: 'CrugeAccessControlFilter' (ver tema mas arriba) entonces veras que se creara una operacion llamada 'site_controller' y otra 'action_site_index', deberas entonces asignarle estas dos operaciones al rol al cual 'juan' pertenece.

Superusuario e Invitados

Seleccionando el modo de inicio de sesión: Por usuario, por Email, o ambos

Cruge permite que puedas seleccionar la modadalidad de inicio de sesión, se configura en config/main.php.

 'availableAuthModes'=>array('username','email'),

Esto causará que cruge etiquete el formulario de login con "Username o Email:", pudiendo introducirse tanto un username como un email. Internamente el filtro de autenticacion de Cruge probará primero con username, y luego con email.

 'availableAuthModes'=>array('username'),

Esto causará que cruge etiquete el formulario de login con "Username:", Solo aceptara un login válido con su username. Internamente el filtro de autenticacion de Cruge probará solo con username.

 'availableAuthModes'=>array('email'),

Esto causará que cruge etiquete el formulario de login con "Email:", Solo aceptara un login válido con su email. Internamente el filtro de autenticacion de Cruge probará solo con username. IMPORTANTE: si habilitas esta opción e inicias sesión como "admin" no funcionará, deberás poner: "admin@tucorreo.com", el cual obviamente podrás cambiar luego.

El superUsuario

Por defecto, cruge considera a un superusuario, para proposito de administracion, depuracion etc. Para que cruge considere a un usuario como un "superusuario" entonces éste deberá tener un "username" con el mismo valor configurado en CrugeModule::superuserName.

Por defecto este valor viene configurado como 'admin' (con password admin).

Para cambiar este valor, al igual que otros parametros de Cruge, no debes cambiar nada en CrugeModule, sino mediante edición de "tuapp/protected/config/main.php":

  
'cruge'=>array(
  'superuserName'=>'administrador', (suponiendo que no te gusta 'admin')
),
   

El superUsuario para Cruge tiene un caso especial en el método Yii::app()->user->checkAccess (cruge\components\CrugeWebUser.php): si el usuario activo es un superusuario entonces checkAccess devolverá true -siempre- sin importar ningún rol, tarea u operación.

Como saber si estamos ante un superusuario ?

Consultando a:

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

o mas corto:

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

El usuario Invitado

Cruge hace especial tratamiento al usuario invitado. Para esto CrugeModule contiene un atributo llamado 'guestUserId' el cual es usado para indicarle al sistema Cruge cual de sus usuarios existentes en la base de datos de usuarios es el invitado, dicho de otro modo es para reconocer quien es el usuario marcado como invitado. Solo debe haber un solo invitado en una aplicación web, no tiene sentido alguno tener mas de uno.

Por defecto cuando Cruge es instalado, su script de base de datos (protected/modules/cruge/data) creará dos usuarios:

  
insert into `cruge_user`(username, email, password, state) values
      ('admin', 'admin@tucorreo.com','admin',1),('invitado', 'invitado','nopassword',1);
   

Siendo 'admin' el usuario con ID 1, y siendo 'invitado' el usuario con ID 2. Por esto veras que por defecto en CrugeModule.php ya esta predefinido el atributo guestUserId en 2. No lo cambies a menos que cambies el ID del usuario invitado.

Cómo maneja Cruge al usuario invitado.

Por defecto en YII cuando tu llamas a Yii::app()->user->id esta devuelve 0 (cero) cuando un usuario es invitado. En Cruge esta misma llamada a Yii::app()->user->id devolverá al valor configurado en CrugeModule::guestUserId, el cual de fábrica es dos (2). Siempre puedes confiar en Yii::app()->user->isGuest, ya que ésta función considera todo esto para saber si el usuario es un invitado.

  Usa: Yii::app()->user->isGuest para saber si estamos operando ante un invitado.

TRAS INSTALAR, DEBES CONFIGURAR EL USUARIO INVITADO

Esto es importante: Si no asignas al usuario invitado a ningún rol (por defecto es así) entonces no tendrá acceso a ningún “controller” que esté siendo manejado por CrugeAccessControlFilter (mas arriba explico acerca de CrugeAccessControlFilter). Por tanto tras instalar tu sistema con Cruge debes hacer lo siguiente:

  • Crea un rol llamado 'invitados'.
  • Asignale a ese rol las operaciones necesarias, por ejemplo 'controller_site', 'action_site_index', 'action_site_contact', 'action_site_login' y otras que vayas viendo que se requieran (usa el LOG).
  • Asigna este rol creado al usuario invitado usando la página de administración de permisos de Cruge, ó, mediante las "variables del sistema": "Asignar este rol al usuario registrado".
 Tip: No necesariamente el rol del 'invitado' debe llamarse 'invitado'. Por conveniencia es sano que asi sea pero no es indispensable.

Manejo de Eventos con Cruge

EVENTOS DE INICIO, CIERRE Y EXPIRACIÓN DE SESIÓN

Puedes hacer que Cruge vaya a una URL específica cuando ocurra uno de estos eventos:

  • inicio de sesión
  • cierre de sesión
  • la sesión expira.

Esto se configura en tuproyecto/protected/config/main.php, allí puedes colocar las URL en las siguientes variables del módulo Cruge:

 
<?php
'afterLoginUrl'=>array('/site/welcome'), 
'afterLogoutUrl'=>array('/site/page','view'=>'about'),
'afterSessionExpiredUrl'=>null,
?>
  

(IMPORTANTE: no olvidar el slash inicial "/" sino la url no funcionará)

LOGIN, LOGOUT, SESIONES. CÓMO FUNCIONA EN CRUGE.

Debes comprender que hay dos estados en un sistema RBAC estándar como lo es Cruge:

  • La autenticación

Es el estado del sistema RBAC en el cual se verifíca que un usuario es realmente quien dice ser, se verifica mediante un usuario y una clave, un código, un mecanismo de OPEN-ID (pronto será incorporado en Cruge, al igual que Facebook)

  • La sesión.

Tras autenticarse, a un usuario se le asigna un espacio y un tiempo para operar en el sistema. El tiempo de la sesión se configura en las “Variables del Sistema” (ver tema al inicio). Tras cumplirse el tiempo, Cruge abortará al usuario emitiendo el evento onSesionExpired (de la clase: cruge.models.filters.DefaultSessionFilter), el cual derivará en una llamada a la URL afterSessionExpiredUrl (ver arriba).Cruge maneja la sesión bajo una arquitectura delicada que será explicada a futuro con mayor detenimiento y con soporte de diagramas UML, por ahora no viene al caso detallar tanto.

Filtros:

Cruge tiene un 'filtro para otorgar sesiones' y un 'filtro de autenticacion' estos funcionan asi:

  • Primero se pasa por el filtro de autenticacion, el cual le da sentido a 'Yii::app()->user->getUser()', luego el filtro de sesión verifica si el sistema esta apto para recibir una nueva sesión, quizás está en mantenimiento o quizás no, por tanto es el filtro de sesión quien ahora entra en juego.
  • Una vez que el filtro de autenticación determina que se puede dar una sesion a 'juan perez', entonces se le crea una sesion y se llama a un evento llamado 'onLogin' de la clase 'cruge.models.filters.DefaultSessionFilter'.

Este evento de onLogin es quien establece el valor a Yii::app()->user->returnUrl el cual es procesado por UiController para redirigir el browser a una pagina que tu indicas.

Ahora, importante, cuando tu haces logOff manualmente, o si por alguna razón tú usando el api estándar de Yii haces una llamada a Yii::app()->user->logout() verás que también serás redirigido a la URL que hayas especificado en 'afterLogoutUrl'.

Conviviendo con CAccessControl filter.

Supongamos que tras iniciar sesion exitosamente quieres que el usuario sea redirigido al actionBienvenido de siteController (index.php?r=site/bienvenido), pero utilizando el filtro accessControl que Yii trae por defecto y no mediante los eventos de sesión que te he mostrado en la página anterior.

Pues bien el metodo que aquí describiré es algo estándar para Cruge o para Yii en general, no es nada nuevo.

  • 1) en siteController (en el controller de tu gusto) creas un action el cual desplegara la página que solo verá aquel usuario que haya iniciado sesion exitosamente.
  
<?php
public function actionBienvenido(){
  $this->render('bienvenido');
}
?>
   
  • 2) en siteController usas el filtro accessControl y los rules (que vienen de caja en Yii), asi:
  
<?php
public function filters()
{
  return array('accessControl',);
}
public function accessRules()
{
return array(
array('allow',
 'actions'=>array('index','contact','captcha'),
 'users'=>array('*'),),
array('allow',
 'actions'=>array('bienvenido'),
 'users'=>array('@'),
),
array('deny', // deny all users
 'users'=>array('*'),
),
);
}
?>

Con esta configuración de CAccessControl le estas diciendo a tu aplicacion que para el action "site/bienvenido" se requiere que el usuario deba haber iniciado sesion exitosamente ( con cruge o con yii tradicional). De este modo, si un usuario invitado presiona el enlace "login" entonces tu lo envias a site/bienvenido, si no ha iniciado sesion se le pediran credenciales y luego se le enviara a la vista site/bienvenido. por tanto el siguiente paso es requerido:

  • 3) finalmente sustituye tu enlace a login por un enlace a site/bienvenido.

Que sucederá ?

 * Tu invitado visita tu website (no ha iniciado sesion aun por eso es un invitado) y sigue el enlace 'login' o 'iniciar sesion' que tu has provisto (y que apunta a site/bienvenido como dice el paso anterior.
 * Tu invitado sera redirigido automaticamente a "index.php?r=cruge/ui/login" (o a la url de login del sistema que este registrado para autenticar, en este caso Cruge), esto debido al "accessControl" que implementaste en el paso 2.
 * Luego tras iniciar sesion exitosamente sera redirigido automaticamente a "index.php?r=site/bienvenido", (funciona asi debido a que en el paso 2 al detectarse que no se ha iniciado sesion entonces se establecio el valor de returnUrl a "site/bienvenido", por tanto cuando Cruge o Yii estandar hacen un login correcto redirigen a tu usuario a la direccion que tenga almacenada en returnUrl, en este caso site/bienvenido)

Como has visto Cruge es un sistema orquestado para trabajar en conjunto con el actual sistema estandar de autenticacion de Yii, por eso como dije antes es una extension: porque extiende la funcionalidad basica de Yii a un nivel mas alto.

FILTROS

Cruge permite que se pueda extender más allá usando filtros. Existen varios tipos de filtros, todos se instalan en config/main y disponen de una interfaz (interface) que debes respetar.

La idea principal a cubrir con los filtros es que mediante config/main tu puedas decirle a Cruge cuales filtros va a usar, para que estos hagan el trabajo que tu necesitas pero sin tocar ni modificar el core de Cruge, en cambio usando el concepto de Override de la programación orientada a objetos (POO).

Filtros de autenticación: permite que amplies como se busca un usuario para ser autenticado. Se ubican en:

 protected\modules\cruge\models\auth\CrugeAuthDefault.php
 (pendiente documentacion para saber como usarlo)

Filtros de sesión: permite que puedas controlar como se entrega una sesion a un usuario, inclusive puedes denegarla. Se ubican en:

 protected\modules\cruge\models\filters\DefaultSessionFilter.php
 (para saber como usar este filtro lee: "CONTROL AVANZADO DE SESIONES Y EVENTOS DE AUTENTICACION Y SESION")


Filtro de actualización: permite saber si un usuario actualizo su perfil. Se ubican en:

 protected\modules\cruge\models\filters\DefaultUserFilter.php
 (pendiente documentacion para saber como usarlo)

Debido a lo extenso de Cruge no he tenido tiempo de documentar bien estos filtros, pero es bastante intuitivo, aunque pronto iré documentandolo en detalle.

CONTROL AVANZADO DE SESIONES Y EVENTOS DE AUTENTICACION Y SESION

El propósito: Poder controlar el otorgamiento de sesiones, y poder ser notificado cuando un usuario ha sido autenticado, sin necesidad de "echarle mano" al core de Cruge, sino en cambio usando la arquitectura OOP de Cruge. Eso es importante que lo tengas claro.

Para esto se hacen dos cosas:

  • Se crea una clase personalizada en tu ruta de componentes (de nuevo: no dentro del directorio de cruge..eso no se toca, sino en tu propio directorio /tuapp/protected/components/ )
  • Se edita tu archivo de configuración, para indicarle al Core de Cruge que use esta nueva clase en vez de la que usa por defecto.

Manos a la obra:

Edita tu archivo: /tuapp/protected/config/main.php y en la sección "cruge" (dentro de modules) agregas la siguiente linea:

 'defaultSessionFilter'=>'application.components.MiSesionCruge',
 

Crea el archivo MiSesionCruge.php en: /tuapp/protected/components/MiSesionCruge.php, y copiale el código que coloco aqui abajo.

<?php
/***
	Esta clase sirve para personalizar las acciones de inicio y cierre
	de sesión.

	requiere que la registres en config/main dentro de cruge setup:

		'defaultSessionFilter'=>'application.components.MiSesionCruge',

	@author Christian Salazar (christiansalazarh@gmail.com)
*/
class MiSesionCruge extends DefaultSessionFilter {


	/**
		este evento es invocado cuando un usuario ha hecho LOGIN EXITOSO.
		Puedes tomar tus propias acciones aqui, no se esperan valores de
		retorno.

		si quieres controlar a un usuario que ha sido autenticado entonces
		deberás trabajar en el método aqui provisto: startSession. mas abajo.
		Una cosa es la sesion otra la autenticacion, aqui solo se notifica que
		un usuario existe.
	*/
	public function onLogin(ICrugeSession $model){
		parent::onLogin($model);
		Yii::log("PASANDO POR ONLOGIN","info");
	}

	/**
		este evento es invocado cuando un usuario ha hecho logout, o cuando
		explicitamente se invoca a
			Yii::app()->user->logout.
		Puedes tomar tus propias acciones aqui, no se esperan valores de
		retorno.
	*/
	public function onLogout(ICrugeSession $model) {
		parent::onLogout($model);
		Yii::log("PASANDO POR ONLOGOUT","info");
	}

	/**
		este evento es invocado cuando una sesion ha expirado. no se esperan
		valores de retorno, solo puedes colocar aqui tus propias acciones.

	*/
	public function onSessionExpired(ICrugeSession $model) {
		parent::onSessionExpired($model);
		Yii::log("PASANDO POR ONSESSIONEXPIRED","info");
	}

	/**
		Este metodo es invocado por el core de Cruge cuando se requiere una
		nueva sesion para un usuario que ha iniciado sesión. El proposito aqui
		es que tu puedas tomar tus propias acciones y decisiones al momento de
		otorgar una sesion a un usuario, pudiendo revocarla si lo deseas
		usando a:
			CrugeSession::expiresession()

		Por defecto es altamente recomendado que retornes:

			return parent::startSession($user, $sys);

		Lo que aqui se espera es que se retorne una nueva instancia de un
		objeto que implemente la interfaz ICrugeSession (por defecto la clase
		CrugeSession lo hace y normalmente es la instancia que aqui se retorna)

		la implementacion base de startSession usara las siguientes funciones
		del API para hallar y crear una sesion según sea el caso:

			$sesion = Yii::app()->user->um->findSession($user);
		y
			$sesion = Yii::app()->user->um->createSession($user,$sys);

		para caducarla de inmediato usas:

			$sesion->expiresession()

		y luego invoca a :

			$this->onSessionExpired();

		para otorgar una sesion al usuario se hacen por defecto validaciones
		contra el estado sistema, la caducidad de la sesion y otras cosas de
		relevancia.
	*/
	public function startSession(ICrugeStoredUser $user,ICrugeSystem $sys) {
		Yii::log("PASANDO POR startSession","info");
		return parent::startSession($user, $sys);
	}

}
  

EL ENVIO DE CORREOS CON CRUGEMAILER

(puedes ver el tema ampliado haciendo click en: CrugeMailer)

CrugeMailer no solo esta hecho para Cruge, sino para proveer una plataforma de email a TODA TU APLICACION.

  • Permite que tu aplicación envie correos basados en vistas.
  • Centraliza el método de envío de email.
  • Convierte al correo en un componente de negocio.

Ejemplo:

 Yii::app()->crugemailer->enviarEmailNuevaClave($usuario); 

(es un ejemplo, la función 'enviarEmailNuevaClave' no existe en Cruge, pero si verás otras similares)

Primero es necesario que comprendas que no existe un método público de crugemailer al cual llamar para enviar un correo, me refiero a que -no verás- llamadas como esta:

 Yii::app()->crugemailer->sendEmail("hola","alguien@gmail.com","body");  // asi no se usa CrugeMailer

En cambio si verás llamadas muy específicas que tu mismo creas en una clase derivada de CrugeMailer, por ejemplo:

 Yii::app()->crugemailer->dimeHola("hola",$alguien);    // esta si es la manera de usar CrugeMailer

Lo que se haces es que internamente dentro de esa función de ejemplo 'dimeHola' se invocará a una vista que recibirá como argumento el texto "hola" (usando las funciones que CrugeMailer provee), se imprimirá el contenido del mensaje sobre una vista para posteriormente enviarsela por correo a $alguien.

Podrías preguntarte ¿ Y cómo selecciono que método será usado para enviar el correo ? mail() ? phpmailer() ? otro() ?

Es decisión tuya: en esa clase derivada de CrugeMailer que tu debes crear, sobreescribirás el método sendEmail para especificar (o implementar) el método de tu preferencia.

Siguiendo el ejemplo de arriba, es dentro del método 'dimeHola' de tu clase extendida de CrugeMailer donde se especifica cómo se enviará ese correo, si por mail() o por otro método de tu preferencia.

Comprende que a la aplicación entera no le compete este detalle de cómo se envía el email, a la aplicación le interesa "decirle hola a alguien". Este es concepto tras CrugeMailer, y es lo que Cruge usa. Esto es el concepto de Encapsulamiento de la OOP hecho práctica.

Configurar CrugeMailer

 
  'crugemailer'=>array(
  'class' => 'application.modules.cruge.components.CrugeMailer',
  'mailfrom' => 'email-desde-donde-quieres-enviar-los-mensajes@xxxx.com',
  'subjectprefix' => 'Tu Encabezado del asunto - ',
  'debug' => true,
),
  

El argumento class es quien cuál es la clase que implementará al componente crugemailer, por tanto si creas una nueva clase derivada de CrugeMailer (que extiende de..), deberás poner ahi la nueva ruta y toda la aplicación web incluso cruge se aprovecharán de este cambio.

Si en esta clase derivada sobreescribes el metodo sendMail verás que toda la aplicación enviará correos mediante ese metodo que tu seleccionas.

Personalizar CrugeMailer

Supongo que quieres crear un nuevo tipo de correo basado en una vista para tu aplicación, digamos que quieres algo como:

 Yii::app()->crugemailer->notificarStockMercancia($usuarioSeleccionado,"stock va a vencer producto xxx");

1. Creas una clase que extienda de "CrugeMailerBase", la llamarias por ejemplo: "MiClaseCrugeMailer" y supon que la guardas en tu directorio de 'protected/components/MiClaseCrugeMailer.php' (!!no dentro de cruge module!!)

 

<?php 
// esta clase va en: 'protected/components/MiClaseCrugeMailer.php' (no dentro de cruge!!)
class MiClaseCrugeMailer extends CrugeMailerBase {

  public function sendEmail($to,$subject,$body){
     // usa esto para que el correo se envíe por la via estandar mail()
     // si quieres usar otro método deberás comentar esta linea e implementar 
     // tu propio mecanismo.
     parent::sendEmail($to,$subject,$body);
  }
}

2. En el "config/main" del componente 'crugemailer' pones el argumento:

 'class' => 'application.components.MiClaseCrugeMailer'

3. Esta nueva clase contendría un nuevo método llamado "notificarStockMercancia": Notarás que uso una llamada a $this->render, es éste método el que renderiza tu correo dentro de una vista que has creado previamente.

 
<?php 
// esta clase va en: 'protected/components/MiClaseCrugeMailer.php' (no dentro de cruge!!)
class MiClaseCrugeMailer extends CrugeMailerBase {

  public function notificarStockMercancia($usuario,$asunto) {
     // IMPORTANTE: NO implementes aqui el metodo de envio de email 
     // por ejemplo mail(..) o phpmailer(..bla..) eso es
     // responsabilidad del metodo sendEmail que haces mas abajo
     // aqui solo dedicate a preparar el correo !!
     $this->sendEmail(
       $usuario->email,$asunto, 
       $this->render('vista_stockvence',array('data'=>$usuario))
     );
  }
  
  public function sendEmail($to,$subject,$body){
     // usa esto para que el correo se envíe por la via estandar mail()
     // si quieres usar otro método deberás comentar esta linea e implementar 
     // tu propio mecanismo.
     parent::sendEmail($to,$subject,$body);
  }
}

4. Para usar este nuevo método en tu aplicación:

 Yii::app()->crugemailer->notificarStockMercancia($usuarioSeleccionado,"asunto");

¿ Cómo cambiar el método de envío de correo ? (mail, phpmailer etc)

Tienes dos alternativas para cambiar el método de envío de correo, supongamos que no te gusta o no te sirve la función mail() y prefieres usar la librería PHPMailer:


  • Método 1: (preferido)

Editas tu propia clase 'MiClaseCrugeMailer' que has creado en tu propia ruta de 'applications.components' y en ella editas el método sendEmail para implementar la solución.

  • Método 2:

Editas CrugeMailerBase.php y en ella editas el método sendEmail para implementar la solución.

Personalmente recomiendo el "método 1" ya que evita la manipulación del core de CrugeMailer, delegando asi a tu aplicación web la "especialización", además ayuda a que recibas actualizaciones a crugemailer via git sin que haya conflicto. El "método 2" es referenciado en el README pero para el momento de escribirlo no se había tomado en cuenta la actualización via GIT entre otras cosas.

Beneficios obtenidos al usar CrugeMailer

  • Se logra que tú hagas envíos de correos orientados a tu modelo de negocio, sin hacer espaguetti con el código!!, centralizando y encapsulando el código... todos tus correos invocarán a sendEmail...y no tendrás que crear y duplicar o triplicar el esfuerzo de escribir las funciones de correo.
  • Se logra que puedas clarificar el codigo...envias un correo con un proposito muy definido:

Yii::app()->crugemailer->nuevoCorreoAlCliente($usuario, "hola")

  • Se logra que en cualquier momento puedas decidir por cual via se envia el correo, si por mail() o por phpmailer() o por cualquier otra que tu decidas..solo cambiando un metodo..sin hacer "código espaguetti" ("código espaguetti" = código con hiper-dependencias, necio y ofuscado que termina acabando con la vida del proyecto entero en menos de tres meses gracias a la flojera de no organizar ni leer, ahh lo olvidaba, además en algo menos de 6 meses solo dios y tu (si te acuerdas) entenderán que se hizo ahi).

¿Cómo hacer un CRON (BATCH) con CrugeMailer ?

Primero, qué es un "CRON". Es acrónimo para "CRON JOB" o "TAREA PROGRAMADA", que se encarga de despachar correos en forma de LOTES (BATCH).

Segundo, CrugeMailer no va a correr como una tarea programada por si solo porque en PHP no existen las tareas programadas dado que la arquitectura de PHP no admite hilos secundarios como lo haría Java. Por tanto para hacer una tarea programada en PHP se invoca un script (o una funcion) cada cierto tiempo usando un TRIGGER (disparador):

Tipos de Disparador (TRIGGER):

Hay dos metodos para ejecutar una tarea programada en Php:

1) Con un CRONJOB de tu "sistema operativo" mandas a ejecutar un script periódicamente. Pregunta si tu hosting dispone de CronJobs, algunos lo tienen, en la config del hosting se le indica el tiempo y la ruta de un script y sera el servidor quien corra el script cada cierto tiempo. Si es windows, puedes crear una tarea programada que corra un .BAT con una sentencia PHP que ejecute el script, o, simplemente que corra un script por su URL.

2) Pones un contador (en una tabla o en memoria de sesion) en tu aplicación, y cada vez que alguien invoque a tu archivo "index.php" incrementas ese valor, y si se llega al "umbral" ejecutas un método en tu aplicación.

Sea cual sea el método de lanzamiento o "TRIGGER", siempre invocarás un método en una clase, el cual va a ejecutar una lectura de mensajes almacenados y los enviará por correo. Este es el concepto principal.

¿ Cómo hacerlo con CrugeMailer ?

(para saber mas lee "Cómo Personalizar CrugeMailer" mas arriba).


1. Primero, no mandas el email cada vez que ocurre algo en tu negocio (como normlamente sucedería sin un proceso de batch). Es decir, en vez de enviar el email de una vez lo almacenarás.

 
<?php 
// esta clase va en: 'protected/components/MiClaseCrugeMailer.php' (no dentro de cruge!!)
class MiClaseCrugeMailer extends CrugeMailerBase {

  public function sendEmail($to,$subject,$body){
     // en vez de enviarlo:
     // parent::sendEmail($to,$subject,$body);

     // lo guardas en un modelo llamado protected/models/Correo.php
     $c = new Correo();
     $c->asunto = $subject;
     $c->body = $body; // es un LONGBLOB en tu modelo
     $c->to = $to;
     $c->enviado = false;
     $c->insert();
  }

}

2. Creas un metodo trigger() para que sea invocado mas adelante por el disparador del cronjob al momento que quieras enviar los correos realmente.

 
<?php 
// esta clase va en: 'protected/components/MiClaseCrugeMailer.php' (no dentro de cruge!!)
class MiClaseCrugeMailer extends CrugeMailerBase {

  // en config/main en el setup de crugemailer le pones:
  // 'limit'=>10,  para indicar cuantos emailas por vez quieres mandar.
  public $limit;

  // metodo usado por tu disparador:
  //   Yii::app()->crugemailer->trigger();
  //
  public function trigger() {
     $lista = Correo::model()->findAllByAttributes(array('enviado'=>false));
     $n=0;
     foreach($lista as $c){
         $this->sendEmailReal($c->to, $c->asunto,$c->body);
         if($n++ > $this->limit)
           break;
     }
  }

  public function sendEmailReal($to,$subject,$body){
     // AQUI PONES TU PHPMAILER...O..
     // simplemente por defecto a:
     // (este es el sendEmail de CrugeMailerBase)
     parent::sendEmail($to,$subject,$body); 
  }

  public function sendEmail($to,$subject,$body){
     // en vez de enviarlo:
     // parent::sendEmail($to,$subject,$body);

     // lo guardas en un modelo llamado protected/models/Correo.php
     $c = new Correo();
     $c->asunto = $subject;
     $c->body = $body; // es un LONGBLOB en tu modelo
     $c->to = $to;
     $c->enviado = false;
     $c->insert();
  }

}


3. Ahora creas un script disparador. (IMPORTANTE: considera protegerlo de ejecución desde IP's extrañas)

por ejemplo asumo el metodo1 de ejecución (usando un script, ver inicio del tema) tu aplicación web tendrá un CronJob de sistema operativo que lanzará este script periodicamente:

 http://tuapp.com/cronjob-email.php
 
<?php
// llamalo:  /tuapp/cronjob-email.php
//
$yii=dirname(__FILE__).'/../../yii/framework/yii.php';
$config=dirname(__FILE__).'/protected/config/main.php';

defined('YII_DEBUG') or define('YII_DEBUG',false);
defined('YII_TRACE_LEVEL') or define('YII_TRACE_LEVEL',3);

$_GET['r'] = '/site/disparador';

require_once($yii);
Yii::createWebApplication($config)->run();


4. En siteController.php, creas un action PROTEGIDO por un CAccessControl Rule de YII que prevenga que una IP desconocida lo ejecute: (mira la documentacion yii de CAccessControl)

 
public function actionDisparador(){
   $this->crugemailer->trigger();
}

LISTO. tienes envio de correos en procesos de lotes usando un cron-job.

CASO REAL: Crea tu propia clase MiCrugeMailer

Aqui tienes un caso real de uso de CrugeMailer. Ir al enlace

ENCRIPTADO DE CLAVES

Por defecto Cruge trae la encriptacion de claves desactivada. Esto es así para facilitar el trabajo mientras se instala.

Para activar o desactivar la encriptación se usa el atributo 'useEncryptedPassword' => true o false (en tuapp/protected/config/main.php).

IMPORTANTE:

Si quieres encriptar las claves y hacer que en la tabla de usuarios estas no sean visibles humanamente, entonces deberas tomar un paso extra:

a) encriptar tu clave de admin usando algún script PHP externo y guardar ese código o hash obtenido via base de datos en la tabla de usuarios (cruge_user) de forma manual, mediante un script sql directo.

b) luego activas la encriptación poniendo a true el atributo useEncryptedPassword y con eso podrás acceder y verás que los nuevos usuarios que se vayan creando tendrán su nueva clave encriptada.


CÓMO CAMBIAR EL MÉTODO DE ENCRIPTACIÓN POR DEFECTO

Por defecto Cruge encripta la clave en MD5. Sin embargo, este comportamiento es posible modificarlo desde el archivo "tuapp/protected/config/main.php", en la sección "modules":

  
'cruge'=>array(
...
'hash' => 'sha1',
...
),
   

Los valores aceptados por el parámetro hash son cualquiera aceptado por la función “hash” (PHP 5.1+), para mayor información debes visitar: http://www.php.net/manual/en/function.hash-algos.php hash_algos()). (Gracias a Ricardo Andrés Obregón).

MANEJO DE ERRORES (EXCEPCIONES) EN CRUGE

Cruge emite una excepción mediante el componente CrugeException, que es una extensión de CHttpException

Además de estas dos clases hay un tercer componente involucrado: el config/main en la sección de 'errorHandler' en el cual le das la ruta del action que va a responder ante una excepción.

Este diagrama muestra cómo interactuan las clases y partes del sistema ante una excepción. Cruge-exception-secuence-diagram.jpg

1) cruge emite una excepcion con CrugeExeption, ejemplo 501 access denied.
2) CrugeException deriva de CHttpException y le pasa sus argumentos en el constructor.
3) CHttpExpression se inicializa
4) CHttpExpression lee la config/main y sabe a que action debe redirigirse.
5) CHttpExpression redirige el browser al action indicado.
6) (fin) se ha renderizado la vista site/error en la aplicacion.

CASOS DE EJEMPLO

Este apartado es para presentar casos de ejemplo en donde pudieras pensar que Cruge requiere modificaciones, pero en realidad el problema puede solucionarse de otra manera.

igualmente puedes contribuir con tus propios casos enviandomelos al correo christiansalazarh@gmail.com, normalmente yo saco los casos de ejemplo de las preguntas enviadas por correo o de los issues.

Acceder a los campos Personalizados desde tu propia clase relacionada con CrugeUser

Supon que tienes una clase llamada Empleado, que esta asociada a un CrugeStoredUser (un iduser de cruge, aquel id que sacas de Yii::app()->user->id).

Ahora supon que te gustaria que este modelo empleado leyera los campos personalizados del usuario de cruge al cual esta asociado, algo como:

 
  $e = Empleado::model()->findByPk(123);  // levantas al empleado con idempleado = 123.
  echo "Cédula": $e->cedula;
  echo "Nombre": $e->nombre;
  echo "Apellido": $e->apellido;

El atributo "cedula", "nombre" y "apellido" son campos personalizados que hiciste con el gestor de campos personalizados de Cruge, y , mágicamente pueden ser vistos publicamente como si fuesen parte de la clase, gracias a los magic getters and setters de PHP (no de yii).


 
<?php
class Empleado extends CActiveRecord {
	public function __get($name){
	
		$field = Yii::app()->user->um->loadFieldByName($name);
		if($field != null)
			return Yii::app()->user->um->getFieldValue($this->iduser0,$field);
		return parent::__get($name);
	}
	public function __set($name,$val){
	
		$field = Yii::app()->user->um->loadFieldByName($name);
		if($field != null)
			return;
		return parent::__set($name,$val);
	}
	public function relations()
	{
		return array(
			'iduser0' => array(self::BELONGS_TO, 'CrugeStoredUser', 'iduser'),
		);
	}
	
}


El empleado debe pertenecer a una o varias empresas

Sigue este enlace para ampliar el tema con mas detalles e imagenes

Cruge-ejemplo-relacion-empleado-empresa.png

Pudieras pensar que la tabla de usuarios de cruge (cruge_user) requiere mas campos, por ejemplo: idempresa. Pues no. Puedes ampliar la imagen a la derecha para ver como insertar cruge en una relacion mas amplia.

En la imagen de la derecha se muestra como la "tabla" Empleado es una extensión de la tabla "cruge_user", es para que veas que no es necesario incorporarle campos a cruge_user, en cambio se crea una nueva tabla que asocia a Cruge con TuModelo, en este caso de ejemplo esa relacion es de "CrugeUser" con una "Empresa" lo cual se traduce en una tabla llamada "Empleado". Un Empleado es un usuario de cruge que labora en una empresa. Como verás en la imagen no fue necesario agregar un campo idempleado a crugeuser (como mucha gente piensa que se requiere, razón por la cual aclaro el tema aqui)

El caso real quedaría asi como muestro a continuación, este ejemplo contiene lo que quiero mostrar: relations(), pero aparte contiene también como se manejan campos personalizados en el modelo Empleado el cual es asociado con CrugeStoredUser.

 
<?php
/**
 * This is the model class for table "empleado".
 *
 * The followings are the available columns in table 'empleado':
 * @property integer $idempleado
 * @property integer $iduser
 * @property integer $idempresa
 * @property string $notas
 *
 */
class Empleado extends CActiveRecord 	
{
	public function getPresentacion(){
		// usa los magic getters abajo para leer campos personalizados
		return ucwords($this->nombre." ".$this->apellido);
	}
	/**
		devuelve la lista de empresas en la cual labora el usuario que ha iniciado sesion
	*/
	public static function misEmpresas(){
		$list = self::model()->findAllByAttributes(array('iduser'=>Yii::app()->user->id));
		$result = array();
		if(isset($list))
			foreach($list as $empleadoInst)
				$result[] = $empleadoInst->idempresa0;
		return $result;
	}
	public static function isEmpleadoEmpresa($idempresa){
		foreach(self::misEmpresas() as $emp)
			if($emp->primaryKey == $idempresa)
				return $emp;
		return null;
	}
	/**
		retorna la instancia de empleado del usuario indicado en la empresa seleccionada
	*/
	public static function getEmpleado($iduser,$idempresa){
		return Empleado::model()->findByAttributes(array('iduser'=>$iduser,'idempresa'=>$idempresa));
	}
	public function getEmpresa(){
		return $this->idempresa0;
	}
	/**
	 * getEmpleadoPorCedula 
	 *	busca a un empleado por su cedula, considerando que la cedula
	 *	es un campo personalizado del sub sistema Cruge.
	 * @param string $cedula 
	 * @param integer $idempresa 
	 * @static
	 * @access public
	 * @return instancia de Empleado o nul
	 */
	public static function getEmpleadoPorCedula($cedula,$idempresa){
		$user = Yii::app()->user->um->loadUserByCustomField(
			'cedula', $cedula);
		if($user != null){
			return self::getEmpleado($user->primaryKey, $idempresa);
		}
		else
			return null; // ningun usuario del sistema tiene esta cedula
	}
	/*
		este getter permite que sobre el modelo empleado se hagan consultas de atributo
		sobre los campos personalizados, ejemplo:
		
		echo $empleado->cedula; ,siendo cedula un campo personalizado del sistema Cruge
	*/
	public function __get($name){
	
		$field = Yii::app()->user->um->loadFieldByName($name);
		if($field != null)
			return Yii::app()->user->um->getFieldValue($this->iduser0,$field);
		return parent::__get($name);
	}
	public function __set($name,$val){
	
		$field = Yii::app()->user->um->loadFieldByName($name);
		if($field != null)
			return;
		return parent::__set($name,$val);
	}
	/**
	 * @return array relational rules.
	 */
	public function relations()
	{
		// NOTE: you may need to adjust the relation name and the related
		// class name for the relations automatically generated below.
		return array(
			'deptoemps' => array(self::HAS_MANY, 'Deptoemp', 'idempleado'),
			'iduser0' => array(self::BELONGS_TO, 'CrugeStoredUser', 'iduser'),
			'idempresa0' => array(self::BELONGS_TO, 'Empresa', 'idempresa'),
			'nominaemps' => array(self::HAS_MANY, 'Nominaemp', 'idempleado'),
			'puestoemps' => array(self::HAS_MANY, 'Puestoemp', 'idempleado'),
		);
	}
}