Commits

christiansalazar  committed 8b1c5eb

version 1.2 - rbac_menu

  • Participants
  • Parent commits e1d6c54

Comments (0)

Files changed (13)

File components/CrugeAccessControlFilter.php

 			//
 			// tiene permiso para la accion indicada ?
 			//
-			if(Yii::app()->user->checkAccess($controllerItemName))
-			{
+			//if(Yii::app()->user->checkAccess($controllerItemName))
+			//{
 				// si tiene permiso.
 				
 				// tiene permiso para la accion indicada ?
 					//
 					$this->reportError($actionItemName);
 				}
+			/*
 			}
 			else{
 				// no esta autorizado a la controladora en general.
 				//
 				$this->reportError($controllerItemName);
 			}
+			*/
 			
 			$filterChain->run();
 		}

File components/CrugeAuthManager.php

 		if(Yii::app()->user->checkAccess('createPost')){...}
 
 
-	funciones extendidas:
+	FUNCIONES EXTENDIDAS
+	====================
 	
 		permiten listar items. no estan declaradas como parte de la interfaz original de Yii
 	
 		Yii::app()->user->rbac->roles;
 		Yii::app()->user->rbac->tasks;
 		Yii::app()->user->rbac->operations;
-	
+
+	SINTAXIS PARA LA DESCRIPCION DEL AUTHITEM / MENU ITEMS 
+	======================================================
+
+		Para obtener menu items en base a RBAC se usa el metodo:
+
+		Yii::app()->user->rbac->getMenu([$args]);
+
+		Para que los menu items funcionen deben contener una SINTAXIS en
+		la DESCRIPCION como describo a continuacion.
+		
+		[$args] son argumentos en forma de array para adosarle a las URL
+		de los menu items aqui obtenidos, leer mas abajo en este mismo apartado.
+
+		SINTAXIS
+		
+		La descripcion del CAuthItem puede venir en dos formas:
+		
+		A) Estandar.   	"Mi Descripcion"
+
+		B) Extendia.	":2 Menu Usuario {menu_principal} {action_site_index}"
+
+		El caso (A) no tiene mayor explicación, la descripcion se usa como
+		un texto informativo y nada mas.
+
+		El caso (B) representa la "Sintaxis de la Descripcion"
+
+		SU FORMA ES:
+
+		[:]+[nn]+[Texto]+[{auth_item_padre}{acion_auth_item_name}]
+
+		en donde:
+
+		":" 		indica que el CAuthItem es un MENU o un SUB MENU
+
+		"nn"		indica la posicion ordinal, si es un MENU o u SUB MENU
+
+		"Texto"		indica el texto del menu o submenu
+
+		{parent}	el nombre del CAuthItem superior (al existir este argumento
+					se considera al CAuthItem como un SUBMENU de "parent"
+
+		{action}	el nombre del CAuthItem que servirá como ACTION para la
+					url del sub menu item.
+
+		FORMAS POSIBLES:
+
+		":texto"	
+		menu de primer nivel con etiqueta "Texto"
+
+		":N texto"	
+		menu de primer nivel con etiqueta "Texto" en posicion N
+		
+		":texto {parent} {action}"
+		menu de segundo nivel con etiqueta "Texto" relativo a "parent" quien
+		debe ser otro CAuthItem con sintaxis de descripcion, pero de tipo Menu.
+		siendo action el nombre del CAuthItem que va a lanzar la URL
 		
-	ejemplo:
+		":texto {parent}" este caso no tiene sentido. (Un submenu sin URL ?!)
+
+
+		MANEJO DE LA URL / MAPPINGS
+
+		La url se toma del nombre de un CAuthItem que ha sido indicada
+		como la seleccionada para el submenu item usando la sintaxis aqui
+		indicada.
+
+		ejemplo:
+
+			action item:			usada como:
+
+			action_site_index		array('site/index')
+
+			action_ui_editprofile	array('/cruge/ui/editprofile')
+
+		nota acerca de donde salio: "/cruge/ui/":
+
+		esta clase tiene un atributo llamado "mappings" el cual
+		va a convertir patrones de URL, 
+
+		por ejemplo:
+
+			"action_ui_XXX" sera convertida a "action_cruge_ui_XXX"
+
+
+			URL: CASO MODULOS
+
+			El uso de mapping tambien ayuda para cuando los actions
+			declarados como "actions de menu item" apuntan a aquellos 
+			que estan definidos en un modulo de usuario,
+
+			por ejemplo, tienes un action que realmente esta definido
+			dentro de un modulo llamado "tumodulox"
+
+				action_default_index
+
+			si no usas un mapping, este action se generara relativo a
+			la aplicacion sin modulo, es decir:
+
+				array('default/index')  Y DARA UN ERROR, porque
+				asume que lo que "para tu modulo" era el controller default
+				se le pedira a la aplicacion base y fallará porque no existe.
+
+			por tanto, se resolveria en config main pasandole a esta clase
+			un nuevo mapping:
+
+				'mapping' => array(
+						'action_ui_'=>'action_cruge_ui_',
+						'action_default_' => 'action_tumodulox_default_',
+					),
+
+			ahora, al invocar el menu se producira correctamente la URL
+			apuntando a tu modulo:
+
+				array('/tumodulox/default/index'),
+
+		
+		ARGUMENTOS DE LA URL ($args)
+
+		La url recibe argumentos cuando se invoca a Yii::app()->rbac->getMenu
+
+		Por ejemplo, queremos que todos los menu items tengas adosado un
+		parametro (o mas):
+
+		Yii::app()->user->rbac->getMenu(array('idempresa'=>123));
+
+		esto generara un array de menu items (para CMenu o MbMenu o EMenu etc)
+		cuya url será finalmente asi:
+
+		array('label'=>'cosa', 'url'=>'', 'items'=>
+				array('cosa menor','url'=>array('site/index','idempresa'=>123))
+				..
+				..
+		)
+
+
+	EJEMPLO USO DE ESTA CLASE
+	=========================
 	
 		$auth=Yii::app()->authManager;
 		 
 */
 class CrugeAuthManager extends CAuthManager implements IAuthManager {
 
+	// este mapping es usado para el caso de obtener una URL en base a
+	// un CAuthItem.getDescription() usando el mecanismo de  sintaxis
+	// descrito mas abajo.
+	//
+	//	resuelve el problema de 'action_ui_editprofile' el cual
+	//	realmente representa al action action_cruge_ui_editprofile
+	//  al usar getTaskUrl se obtendrá: array('/cruge/ui/editprofile')
+	//  en vez de: array('/ui/editprofile')
+	//
+	//	Importante:
+	//	  este es un mapping de patrones, no de indices directos.
+	//
+	public $mapping = array(
+				'action_ui_' => 'action_cruge_ui_',
+			);
+	private $_enumcontrollers;
+	private $_enumactions;
+
 	/**
 	 * @var string the ID of the {@link CDbConnection} application component. Defaults to 'db'.
 	 * The database must have the tables as declared in "framework/web/auth/*.sql".
 			), 'name=:whereName', array(
 				':whereName'=>$oldName===null?$item->getName():$oldName,
 			));
+
+		// extra: manejo de sintaxis.	
+		//
+		//	cuando un CAuthItem se guarda, se asegura que en caso de ser
+		//  un item tipo TASK que usa sintaxis de descripcion entonces
+		//	vigile el menuitem superior, asignando o desasignando automatica-
+		//	-mente a este TASK con las tareas superior.
+
+
+		if($item->getType() == CAuthItem::TYPE_TASK)
+			if($this->isTaskSubMenuItem($item)){
+				// es un submenu de otra tarea $parent
+				$parent = $this->getParentMenuAuthItem($item);
+				if($parent != null){
+					// no es huerfana
+					// a inserta a $item como hija de $parent
+					if(!$this->hasItemChild($parent->name, $item->name))
+						$this->addItemChild($parent->name, $item->name);
+				}
+			}
+
 	}
 
 	/**
 	public function getOperations($userId = NULL){
 		return $this->getAuthItems(CAuthItem::TYPE_OPERATION);
 	}
+
+		private function _filtroCruge($ops){
+			$crugeKey = "action_cruge_ui_";
+			$ar = array();
+			foreach($ops as $op)
+			{
+				$actionFull = $this->_mapAction($op->name, $this->mapping);
+				// ejemplo: convirtio action_ui_editprofile
+				//			a : 	  action_cruge_ui_editprofile
+				if(substr($actionFull,0,strlen($crugeKey))==$crugeKey)
+						$ar[] = $op;
+			}
+			return $ar;
+		}
+		private function _filtroNoCruge($ops){
+			$crugeKey = "action_cruge_ui_";
+			$ar = array();
+			foreach($ops as $op)
+			{
+				$actionFull = $this->_mapAction($op->name, $this->mapping);
+				// ejemplo: convirtio action_ui_editprofile
+				//			a : 	  action_cruge_ui_editprofile
+				if(substr($actionFull,0,strlen($crugeKey))!=$crugeKey)
+						$ar[] = $op;
+			}
+			return $ar;
+		}
+
+		private function _filtroNoController($ops){
+			$ar = array();
+			$arcontrollers = array();
+			foreach($this->enumControllers() as $controllerName)
+				$arcontrollers[]  = strtolower("action_".$controllerName."_");
+
+			foreach($ops as $op){
+				$found=false;
+				// busca a ver si esta operacion esta en la lista
+				// de controllers definidos.
+				foreach($arcontrollers as $cn)
+					if(substr($op->name,0, strlen($cn)) == $cn){
+						$found=true;
+						break;
+					}
+				// es una operacion no asociada a un controller
+				// por tanto le damos paso en este filtro
+				if(!$found)
+					$ar[] = $op;
+			}
+			return $ar;
+		}
+
+		// solo aquellas operaciones: "controller_site"
+		// (acceso maestro a controllers)
+		private function _filtroControllerMaestro($ops){
+			$ar = array();
+			$arcontrollers = array();
+			foreach($this->enumControllers() as $controllerName)
+				$arcontrollers[]  = strtolower("controller_".$controllerName);
+			$arcontrollers[] = "controller_ui"; // cruge
+
+			foreach($ops as $op){
+				$found=false;
+				// busca a ver si esta operacion esta en la lista
+				// de controllers definidos.
+				foreach($arcontrollers as $cn)
+					if(substr($op->name,0, strlen($cn)) == $cn)
+						$found=true;
+				// es una operacion no asociada a un controller
+				// por tanto le damos paso en este filtro
+				if($found == true)
+					$ar[] = $op;
+			}
+			return $ar;
+		}
+		private function _filtroNotControllerMaestro($ops){
+			$ar = array();
+			$arcontrollers = array();
+			foreach($this->enumControllers() as $controllerName)
+				$arcontrollers[]  = strtolower("controller_".$controllerName);
+			$arcontrollers[] = "controller_ui"; // cruge
+			foreach($ops as $op){
+				$found=false;
+				// busca a ver si esta operacion esta en la lista
+				// de controllers definidos.
+				foreach($arcontrollers as $cn)
+					if(substr($op->name,0, strlen($cn)) == $cn)
+						$found=true;
+				// es una operacion no asociada a un controller
+				// por tanto le damos paso en este filtro
+				if($found == false)
+					$ar[] = $op;
+			}
+			return $ar;
+		}
+
+		private function _filtroControllerName($ops, $controllerName){
+			$key = '_'.strtolower($controllerName).'_';
+			$ar = array();
+			foreach($ops as $item)
+				if(strstr($item->name, $key))
+					$ar[] = $item;
+			return $ar;
+		}
+
+	
+	/**
+	 * getOperationsFiltered 
+	 *	entrega un array de CAuthItem de tipo "operacion" pero en base a
+	 *	un filtro:
+	 *
+	 *		0 => todas
+	 *		1 => aquellas definidas en codigo
+	 *		2 => las de cruge
+	 *		3 => solo contollers maestro
+	 *		X => solo las del controller name = X
+	 *
+	 * @param string $filter 
+	 * @access public
+	 * @return array de CAuthItem
+	 */
+	public function getOperationsFiltered($filter, $oprList = null){
+		$ar = array();
+		if($oprList == null)
+			$oprList = $this->getOperations();
+		if(($filter == '') || ($filter=='0')){
+			// entrega el array completo de operaciones
+			$ar = $oprList;
+		}elseif($filter=='1'){
+			// OTRAS
+			// aquellas que no pertenecen a ningun controller especifico
+			// 	(porque no indican ningun "action_controller_" conocido)
+			// y que tampoco son de Cruge..
+			$ar = $this->_filtroNotControllerMaestro($this->_filtroNoCruge(
+				$this->_filtroNoController($oprList)));
+		}elseif($filter == '2'){
+			// CRUGE
+			// aqui se usa el $this->mapping tambien para reconocer
+			// los actions de Cruge
+			$ar = $this->_filtroCruge($oprList);
+		}elseif($filter == '3'){
+			// CONTROLLERS
+			// solo aquellos controllers maestros
+			$ar = $this->_filtroControllerMaestro(
+				$this->_filtroNoController($oprList)
+			);
+		}
+		else{
+			// CONTROLLER X
+			// aquellas que coinciden con un nombre de controller seleccion.
+			$ar = $this->_filtroControllerName($oprList, $filter);
+		}
+		return $ar;
+	}
+
+
 	public function getDataProviderRoles($pageSize=20){
 		return new CArrayDataProvider($this->getRoles(), array(
 			'keyField'=>'name',
 			),
 		));		
 	}
-	public function getDataProviderTasks($pageSize=20){
-		return new CArrayDataProvider($this->getTasks(), array(
+	public function getDataProviderTasks($pageSize=50){
+		return new CArrayDataProvider(
+				$this->reorderItemArray($this->getTasks())
+				, array(
 			'keyField'=>'name',
 			'sort'=>array(
 				'defaultOrder'=>array('name'),
 			),
 		));				
 	}
-	public function getDataProviderOperations($pageSize=20){
-		return new CArrayDataProvider($this->getOperations(), array(
+	/**
+	 * getDataProviderOperations 
+	 *	entrega un dataprovider segun el filtro seleccionado.
+	 *
+	 *	@see getOperationsFiltered
+	 *
+	 * @param string $filter 
+	 * @param int $pageSize 
+	 * @access public
+	 * @return CArrayDataProvider de elementos CAuthItem
+	 */
+	public function getDataProviderOperations($filter='',$pageSize=50){
+		return new CArrayDataProvider($this->getOperationsFiltered($filter)
+		,array(
 			'keyField'=>'name',
 			'sort'=>array(
 				'defaultOrder'=>array('name'),
 		}
 		return $ar;
 	}
+
+
+	// FUNCIONES PARA EL MANEJO DE SINTAXIS APLICADA A LA DESCRIPCION
+	// DEL AUTH ITEM:
+	//
+	//	
+	//
+
+	/**
+	 * isItem 
+	 *	 detecta si la descripcion del item indica que es un menu (o submenu).
+	 *	 lo hace buscando el simbolo ":" al inicio de la descripcion
+	 * @param mixed $obj 
+	 * @access private
+	 * @return boolean true es un menu o un submenuitem.
+	 */
+	private function isItem($obj){
+		$d = trim($obj->getDescription());
+		if(strlen($d) > 0){
+			return ($d[0]==':') ? true : false;
+		}
+		return false;
+	}
+
+
+	/**
+	 * isSubItem 
+	 *	detecta si el item es un submenu, primero preguntando si isItem()
+	 *	y finalmente preguntando si contiene caracteres { }
+	 * 
+	 * @param mixed $obj 
+	 * @access private
+	 * @return bool true si es un subitem.
+	 */
+	public function isSubItem($obj){
+		$d = trim($obj->getDescription());
+		if(!$this->isItem($obj))
+			return false;
+		// sin mucho analisis lexico-sintaxis...
+		// asi facilito..
+		return strstr($d,"{") && strstr($d, "}");
+	}
+
 	
+	/**
+	 * getTaskText 
+	 *	devuelve la descripcion pura de un CAuthItem considerando la
+	 *	sintaxis:
+	 *
+	 *		":Descripcion Pura{menu_padre}{action_xxx}"
+	 *
+	 *		entregando de aqui solo a: "Descripcion Pura"
+	 *
+	 * @param mixed $obj 
+	 * @access private
+	 * @return void
+	 */
+	public function getTaskText($obj){
+		return $this->_getTextFromDescription($obj->getDescription());
+	}
+
+	/**
+	 * getItemPosition 
+	 *	obtiene de la descripcion el argumento numerico de posicion tras
+	 *	el simbolo inicial ":".
+	 *	  ejemplo ":123 mi item" devolvera: 123, sino 0
+	 *
+	 * @param mixed $obj 
+	 * @access private
+	 * @return integer
+	 */
+	public function getItemPosition($obj){
+		$d =  trim(ltrim($obj->getDescription(),':'));
+		$dig='';
+		for($i=0;$i<strlen($d);$i++)
+			if(ctype_digit($d[$i])){
+			$dig .= $d[$i];
+			}
+			else
+			return ($dig)*1;
+		return null;
+	}
+
+
+	/**
+	 * _getParentMenuName 
+	 *	de un texto ":hola {menu_item}" devolvera: "menu_item" 
+	 * @param mixed $m 
+	 * @access private
+	 * @return void
+	 */
+	private function _getParentMenuName($m){
+		if($m == null)
+			return "";
+		if(strlen($m)==0)
+			return "";
+		$r="";
+		$s=0;
+		for($i=0;$i<strlen($m);$i++){
+			if($s==0){
+				if($m[$i]=='{')
+					$s=1;
+			}
+			elseif($s==1){
+				if($m[$i]=='}'){
+					return trim($r);
+				}
+				elseif($m[$i]=='{'){
+					$r="";
+				}
+				else
+					$r .= $m[$i];
+			}
+		}
+		return trim($r);
+	}
+
+	/**
+	 * _getActionItemName 
+	 *	obtiene el contenido de la segunda llave en la sintaxis de la descr
+	 *		ejemplo:
+	 *			$m = "blabla {perrito} y {gatico}";
+	 *		retorna:
+	 *			"gatico" (la segunda llave)
+	 * @param string $m  contenido de la descripcion
+	 * @access protected
+	 * @return string
+	 */
+	private function _getActionItemName($m){
+		if($m == null)
+			return "";
+		if(strlen($m)==0)
+			return "";
+		$r="";
+		$s=0;
+		for($i=0;$i<strlen($m);$i++){
+			if($s==0){
+				if($m[$i]=='{')
+					$s=1;
+			}
+			elseif($s==1){
+				if($m[$i]=='}'){
+					$s=2;
+				}
+			}elseif($s==2){
+				if($m[$i]=='{'){
+					$s=3;
+					$r='';
+				}
+			}elseif($s==3){
+				if($m[$i]=='}'){
+					return trim($r);
+				}elseif($m[$i]=='{'){
+					$r='';
+				}
+				else $r .= $m[$i];
+			}
+		}
+		return trim($r);
+	}
+	private function _getTextFromDescription($description){
+		// limpia cualquier ":" delante
+		$d = trim(ltrim($description,':'));
+		// pasa el numero que pudiese venir a continuacion 
+		$p=0;
+		for($i=0;$i<strlen($d);$i++){
+			if(ctype_digit($d[$i])){
+			  continue;
+			}
+			else{
+				$p=$i;
+				break;
+			}
+		}
+
+		if($p == 0){
+			// no hay numero
+		}
+		else{ 
+			// si hay numero, descripcion continua en posicion $p
+			$d = substr($d,$p);
+		}
+
+		// busca hasta algun posible "{"
+		$tmp = "";
+		for($i=0;$i<strlen($d);$i++)
+			if($d[$i] != '{'){
+				$tmp .= $d[$i];
+			}else
+				break;
+
+		return trim($tmp);
+	}
+	private function _mapAction($action, $mappings){
+		if(trim($action)=="")
+			return "";
+		foreach($mappings as $map=>$xy)
+			if(substr($action,0,strlen($map))==$map)
+				return $xy.substr($action,strlen($map));
+		return $action;
+	}
+
+
+	public function isTaskMenuItem($obj){
+		return $this->isItem($obj);
+	}
+	public function isTaskSubMenuItem($obj){
+		return $this->isSubItem($obj);
+	}
+	public function isTaskTopMenuItem($obj){
+		$n = trim($obj->getName());
+		if($this->isItem($obj)){
+			if(strstr($n,"menu_"))
+			 return true;
+			return false;
+		}
+		else return false;
+	}
+	/**
+	 * getTaskParentMenuName 
+	 *	de una tarea con descripcion: ":Menu1 {menu_principal}" devolvera:
+	 *		el string "menu_principal"
+	 * @param mixed $obj 
+	 * @access public
+	 * @return string  o ""
+	 */
+	public function getTaskParentMenuName($obj){
+		if($this->isTaskSubMenuItem($obj)){
+			return $this->_getParentMenuName($obj->getDescription());
+		}
+		else
+			return "";
+	}
+	/**
+	 * getParentMenuAuthItem
+	 *	pregunta si el CAuthItem $obj tiene un itename padre en su sintaxis
+	 *	de descripcion y si este item name existe como un CAuthItem.
+
+	 * @param CAuthItem $obj el authitem a consultar.
+	 * @access public
+	 * @return CAuthItem del padre dado por la sintaxis de la descripcion.
+	 */
+	public function getParentMenuAuthItem($obj)
+	{
+		$itemname_padre = $this->getTaskParentMenuName($obj);
+		return $this->getAuthItem($itemname_padre);
+	}
+	/**
+	 * getTaskActionItemName
+	 *	de una tarea con descripcion: ":Menu1 {menu_principal} {action_site_index}" 	
+	 * 		devolvera:
+	 *			"action_site_index" (el itemname de aquella operacion "child"
+	 								que sera usada como disparador del menu)
+	 *
+	 * @param mixed $obj 
+	 * @access public
+	 * @return string o ""
+	 */
+	public function getTaskActionItemName($obj){
+		if($this->isTaskSubMenuItem($obj)){
+			return $this->_getActionItemName($obj->getDescription());
+		}
+		else
+			return "";
+	}
+
+	/**
+	 * getTaskUrl 
+	 *	de aquella tarea que usa sintaxis en su descripcion para manejar menues
+	 *	retorna la parte que hace referencia al auth item name del action
+	 *	seleccionado por el usuario para responder para este menu.
+	 *
+	 * @param CAuthItem $obj la tarea
+	 * @param array $args Argumentos en forma de array para adosar
+	 * @access public
+	 * @return array url en forma de array
+	 */
+	public function getTaskUrl($obj, $args=null){
+
+		$itemname = $this->getTaskActionItemName($obj);
+		if($itemname == '')
+			return '';
+
+		// ver si hay un mapping para el action
+		//	este no es un mapping tradicional, sino de patrones:
+		//		ejemplo:
+		//		mapping: "action_ui_" cambiara por "action_cruge_ui_"
+		//
+		$itemname = $this->_mapAction($itemname, $this->mapping);
+
+		// ejemplo, recibe: action_site_index
+		// lo descompone en array('site/index', ..$args..)
+		$e = explode('_', $itemname);
+		if(sizeof($e)==3){
+			$controllerName = $e[1];
+			$actionName = $e[2];
+			$a = array();
+			$a[] = $controllerName."/".$actionName;
+			foreach($args as $k=>$v)
+				$a[$k] = $v;
+			return $a;
+		}
+		elseif(sizeof($e)==4){
+			$moduleName = $e[1];
+			$controllerName = $e[2];
+			$actionName = $e[3];
+			$a = array();
+			$a[] = $moduleName.'/'.$controllerName."/".$actionName;
+			foreach($args as $k=>$v)
+				$a[$k] = $v;
+			return $a;
+		}
+		else
+		return array();
+	}
+
+	/**
+	 * explodeTask 
+	 *	descompone la descripcion de un TASK en sus partes segun sintaxis:
+	 *		":POS descripcion{menu}{action}"
+	 *
+	 * @param CAuthItem $obj 
+	 * @access public
+	 * @return array indexado array('description','position','menu','action')
+	 */
+	public function explodeTask($obj) {
+		return array(
+			'description'=>$this->getTaskText($obj),
+			'position'=>$this->getItemPosition($obj),
+			'menu'=>$this->getTaskParentMenuName($obj),
+			'action'=>$this->getTaskActionItemName($obj),
+		);
+	}
+
+	/**
+	 * setTaskAction 
+	 *	Actualiza un CAuthItem ($obj), le pondrá en su descripcion (usando la
+	 *	sintaxis para menues) al nuevo action, considerando cualquier action
+	 *	existente. 
+	 *
+	 *	ejemplo:
+	 *		descripcion original: ":Subitem X {menu_prinicipal}"
+	 *		el $action_item_name es: "action_site_prueba"
+	 *		el resultado de la descripcion final sera:
+	 *			":Subitem X {menu_prinicipal} {action_site_prueba}"
+	 *
+	 *			***** EL CAuthItem sera Actualizado *****
+	 *
+	 * @param mixed $obj el objeto CAuthItem a quien se le modificara la descr.
+	 * @param mixed $action_item_name El action a usar (itemname del CAuthItem)
+	 * @access public
+	 * @return void 
+	 */
+	public function setTaskAction($obj, $action_item_name){
+		$ar = $this->explodeTask($obj);
+		$newDescr = ":".$ar['position']." ".$ar['description']
+			."{".$ar['menu']."}{".$action_item_name."}";
+		$obj->setDescription($newDescr);
+		$this->saveAuthItem($obj);
+	}
+
+	/**
+	 * isTaskMenuItemChild 
+	 *	detecta si un CAuthItem ($item) es un hijo de otro ($posibleSuperior)
+	 *  utiliza la sintaxis del atributo Description para detectarlo.
+	 * 
+	 * @param CAuthItem $item 
+	 * @param CAuthItem $posibleSuperior 
+	 * @access public
+	 * @return void
+	 */
+	public function isTaskMenuItemChild($item, $posibleSuperior){
+		return ($this->getTaskParentMenuName($item) 
+			== $posibleSuperior->getName());
+	}
+
+
+	/**
+	 * explodeTaskArray 
+	 *	separa un array original de CAuthItem TASK en partes organizadas por
+	 *	tipo: Menu, MenuItems y Tareas Regulares.
+	 *	
+	 *	ejemplo:
+	 *
+	 *		1. se le da una lista entera de todas las tareas del sistema
+	 *		usando $this->getTasks():
+	 *
+	 *		2. se invoca a este metodo
+	 *
+	 *		3. el metodo retorna un array con esta forma:
+	 *
+	 *			array(
+	 *				'topmenu'=>array(...),
+	 *				'childmenu'=>array('authitemname'=>array(), ... ),
+	 *				'regular'=>array(...)
+	 *			);
+	 *
+	 *		se ven 3 categorias (indices) de array aqui:
+	 *			topmenu
+	 *			childmenu
+	 *			regular
+	 *
+	 *		topmenu: 	array de CAuthItem tipo TASK de todos aquellos
+	 *				 	considerados Menu de 1er nivel al usar sintaxis.
+	 *
+	 *		childmenu:	array de arrays de CAuthItem indexado por el nombre del
+	 *					CAuthItem padre del item usando la sintaxis de menu
+
+	 *		orphan:		array de CAuthItem de aquellos marcados con sintaxis
+	 *					de sub menu item pero cuyo padre no existe.
+	 *
+	 *		regular:	array de CAuthItem de todas las tareas que no son menues.
+	 *
+	 *
+	 * @param array $originTaskList CAuthItem tipo TASK
+	 * @access public
+	 * @return array
+	 */
+	public function explodeTaskArray($originTaskList){
+
+		$top = array();		// top menu
+		$child = array();	// menuitems de alguien (index)
+		$error = array();	// huerfanos
+		$regular = array();	// tareas normales
+
+		// detecta TOP menu tasks
+		foreach($originTaskList as $task)
+			if($this->istaskTopMenuItem($task))
+				$top[] = $task;
+		
+		// busca las tareas que son hijas de las primeras 
+		// halladas. (son hijas dada la sintaxis de descripcion del CAuthItem)
+		//
+		foreach($top as $topmenuitem){
+			foreach($originTaskList as $task)
+				if($this->isTaskMenuItemChild($task, $topmenuitem))
+					$child[$topmenuitem->name][] = $task;
+		}
+		
+
+		// agrega los huerfanos.
+		// aquellas tareas marcadas como menuitems cuyo padre no existe
+		//
+		foreach($originTaskList as $task)
+			if($this->isTaskSubMenuItem($task))
+				if(!$this->getParentMenuAuthItem($task))
+					$error[] = $task;
+
+		// agrega todas aquellas tareas que no son menuitems
+		//
+		foreach($originTaskList as $task)
+			if(!$this->isTaskMenuItem($task))
+				$regular[] = $task;
+
+		return array(
+				'topmenu' => $top,
+				'childmenu' => $child,
+				'orphan' => $error,
+				'regular' => $regular
+			);
+	}
+
+	/**
+	 * reorderItemArray 
+	 *	ordernara un array de CAuthItem  de tipo TASK de modo que
+	 *	el array resultante este organizado asi:
+	 *
+	 *		MENU_ITEM_1
+	 *			SUB_MENU_ITEM {parent: MENU_ITEM_1}
+	 *			SUB_MENU_ITEM    "
+	 *			SUB_MENU_ITEM    "
+	 *		MENU_ITEM_2
+	 *			SUB_MENU_ITEM {parent: MENU_ITEM_2}
+	 *			SUB_MENU_ITEM    "
+	 *			SUB_MENU_ITEM    "
+	 *		NO_MENU_ITEM_1
+	 *		NO_MENU_ITEM_2
+	 *		NO_MENU_ITEM_3
+	 *		NO_MENU_ITEM_N
+	 *
+	 * @param mixed $itemArray array de CAuthItem
+	 * @access public
+	 * @return array de CAuthItem 
+	 */
+	public function reorderItemArray($itemArray){
+
+		$r = array();
+
+		// busca aquellas operaciones que son tareas
+		// y que son menu items, pero que no son sub menu items
+		$r1 = array();
+		foreach($itemArray as $item)
+			if($this->isTaskMenuItem($item) 
+				&& !$this->isTaskSubMenuItem($item))
+					$r1[] = $item;
+
+		// busca las tareas que son hijas de las primeras 
+		// halladas. (son hijas dada la sintaxis de descripcion del CAuthItem)
+		//
+		foreach($r1 as $menuitem){
+			$r[] = $menuitem;
+			foreach($itemArray as $item)
+				if($this->isTaskMenuItemChild($item, $menuitem))
+					$r[] = $item;
+		}
+
+		// agrega los huerfanos
+		// aquellas tareas marcadas como menuitems cuyo padre no existe
+		//
+		foreach($itemArray as $item)
+			if($this->isTaskSubMenuItem($item))
+				if(!$this->getParentMenuAuthItem($item))
+					$r[] = $item;
+
+		// agrega todas aquellas tareas que no son menuitems
+		//
+		foreach($itemArray as $item)
+			if(!$this->isTaskMenuItem($item))
+				$r[] = $item;
+
+		return $r;
+	}
+
+
+	/**
+	 * getMenu 
+	 *  devuelve un array indexado listo para usar en CMenu
+	 *	  incluso con una entrada extra para subitems: 'items'
+	 *	  para menues extendidos.
+	 *
+	 *	el array es obtenido usando la sintaxis de la descripcion.
+	 *
+	 * @access public
+	 * @param $arguments adjunta argumentos a la url, ej: array('abc'=>'123')
+	 * @return array indexado ('label','url' [,'items'=>array(...)])
+	 */
+	public function getMenu($userid=-1, $arguments = array()){
+
+		if($userid==-1)
+			$userid = Yii::app()->user->id;
+
+		$r = array();
+		
+		// todas las TAREAS a las que puede acceder este usuario
+
+		// este metodo no sirve porque solo lista elementos directamente
+		// relacionados al userid y no lista aquellos derivados,
+		//$itemArray = $this->getAuthItems(CAuthItem::TYPE_TASK,$userid);
+		
+		$tasklist = $this->tasks;
+
+		// por tanto a lo anterior: listo todas las tareas de tipo menuitem
+		// y pregunto si el usuario tiene acceso a ellas:
+		$itemArray = array();
+		foreach($tasklist as $task){
+			if($this->isTaskMenuItem($task) && !$this->isTaskSubMenuItem($task)){
+				if($this->checkAccess($task->getName(), $userid)){
+					$itemArray[] = $task;
+				}
+			}
+		}
+
+		// todas las tareas consideradas subitems, no importa
+		// si estan asignadas al usuario
+		//
+		$allsubitems = array();
+		foreach($tasklist as $task)
+			if($this->isTaskSubMenuItem($task))
+				$allsubitems[] = $task;
+
+		// Menues de Primer Nivel
+		//
+		// busca aquellas operaciones que son tareas
+		// y que son menu items, pero que no son sub menu items
+		$r1 = array();
+		foreach($itemArray as $item)
+			if($this->isTaskMenuItem($item) 
+				&& !$this->isTaskSubMenuItem($item))
+					$r1[] = $item;
+
+		// busca las tareas que son hijas de las primeras 
+		// halladas. (son hijas dada la sintaxis de descripcion del CAuthItem)
+		//
+		foreach($r1 as $menuitem){
+			// child menu items
+			$items = array();
+			// agrega al menuitem de 1er nivel todas los subitems (tasks)
+			// sin importar si fueron otorgadas al usuario con checkAccess
+			//
+			foreach($allsubitems as $task)
+				if($this->isTaskMenuItemChild($task, $menuitem))
+					$items[] = array(
+						'label'=>$this->getTaskText($task),
+						'url'=>$this->getTaskUrl($task, $arguments),
+					);
+			// top level menu
+			if(!sizeof($items))
+				$items = null;
+			$r[] =  array(
+				'label'=>$this->getTaskText($menuitem),
+				'url' => '',
+				'items' => $items,
+			);
+		}
+		return $r;
+	}
+
+
+	/**
+	 * enumControllers 
+	 *	lista los nombres de los controllers declarados. 
+	 * @access public
+	 * @return array con nombre del controller
+	 */
+	public function enumControllers(){
+		if($this->_enumcontrollers == null){
+			$this->_enumcontrollers = array();
+			$p = Yii::app()->getControllerPath();
+			foreach(scandir($p) as $f){
+				if($f == '.' || $f == '..')	
+					continue;
+				if(strlen($f))
+					if($f[0]=='.')
+						continue;
+				if($pos = strpos(strtolower($f),"controller.php")){
+					$this->_enumcontrollers[] = substr($f,0, $pos);
+				}
+			}
+			return $this->_enumcontrollers;
+		}
+		else
+		return $this->_enumcontrollers;
+	}
+
+	/**
+	 * enumActions 
+	 *	devuelve un array con los nombres de los actions del controller 
+	 * @param mixed $controllerName nombre del controller
+	 * @access public
+	 * @return array lista de actions.
+	 */
+	public function enumActions($controllerName){
+		$this->_enumactions = array();
+		$className = $controllerName.'Controller';
+		Yii::import('application.controllers.'.$className,true);
+		$refx = new ReflectionClass($className); 
+		foreach($refx->getMethods() as $method){
+			if($method->name != 'actions')
+			if(substr($method->name,0,6)=="action")
+				$this->_enumactions[] = substr($method->name,6);
+		}
+		return $this->_enumactions;
+	}
+
+	/**
+	 * autoDetect 
+	 *	lee todos los controllers y actions y los almacena si previamente
+	 *	no estaban registrados.
+	 * @access public
+	 * @return void
+	 */
+	public function autoDetect(){
+
+		// agrega cada actiond e cada controller detectado en codigo fuente
+		//
+		foreach($this->enumControllers() as $c){
+			// cada controller
+			$itemName = "controller_".strtolower($c);
+			if(!$this->getAuthItem($itemName))
+				$this->createAuthItem($itemName,
+						CAuthItem::TYPE_OPERATION,"");
+			// cada action
+			foreach($this->enumActions($c) as $action){
+				$itemName = "action_".strtolower($c)."_".strtolower($action);
+				if(!$this->getAuthItem($itemName))
+					$this->createAuthItem($itemName,
+						CAuthItem::TYPE_OPERATION,"");
+
+			}
+		}
+
+		$this->ensureMenuItemIntegrity();
+	}
+
+
+	/**
+	 * ensureMenuItemIntegrity 
+	 *	se asegura que todas aquellas tareas que usan sintaxis de descripcion
+	 *	y que sean subitems de uno superior (debido a la sintaxis) 
+	 *	se asegura que cada subitem este asignado como un child auth item a
+	 *  la tarea superior.	
+	 *
+	 * @access public
+	 * @return void
+	 */
+	public function ensureMenuItemIntegrity(){
+		$data = $this->explodeTaskArray($this->getTasks());
+		$submenues = $data['childmenu'];
+		foreach($submenues as $parentItemName=>$tasks){
+			// determina si esta tarea esta asignada a su padre
+			foreach($tasks as $task)
+				if(!$this->hasItemChild($parentItemName, $task->name))
+					$this->addItemChild($parentItemName, $task->name);
+		}
+	}
+
+
 }// finclase

File components/CrugeUi.php

 	public function getRbacListOpsUrl(){
 		return CrugeUtil::uiaction('rbaclistops');
 	}
-	public function getRbacAuthItemCreateUrl($type){
+	// argumento extra es usado en CrugeAuthManager para crear
+	// tareas enlazadas a otras en forma de submenu, extra==authitem_parent.
+	public function getRbacAuthItemCreateUrl($type, $extra=''){
 		// aqui type es uno de los valores de 
 		// CAuthItem::TYPE_ROLE,CAuthItem::TYPE_TASK,CAuthItem::TYPE_OPERATION
-		return CrugeUtil::uiaction('rbacauthitemcreate',array('type'=>$type));
+		return CrugeUtil::uiaction('rbacauthitemcreate',
+			array('type'=>$type,'extra'=>$extra));
 	}
 	public function getRbacAuthItemDeleteUrl($id){
 		return CrugeUtil::uiaction('rbacauthitemdelete',array('id'=>$id));

File controllers/UiController.php

 
 
 	public function actionRbacListRoles() {
+		Yii::app()->user->rbac->autoDetect();
 		$dataProvider = Yii::app()->user->rbac->getDataProviderRoles();
 		$this->render('rbaclistroles',array('dataProvider'=>$dataProvider));
 	}
 	public function actionRbacListTasks() {
+		Yii::app()->user->rbac->autoDetect();
 		$dataProvider = Yii::app()->user->rbac->getDataProviderTasks();
 		$this->render('rbaclisttasks',array('dataProvider'=>$dataProvider));
 	}
-	public function actionRbacListOps() {
-		$dataProvider = Yii::app()->user->rbac->getDataProviderOperations();
+	public function actionRbacListOps($filter='0') {
+		Yii::app()->user->rbac->autoDetect();
+		$dataProvider = Yii::app()->user->rbac->getDataProviderOperations(
+				$filter);
 		$this->render('rbaclistops',array('dataProvider'=>$dataProvider));
 	}
 
+	
+	/**
+	 * actionAjaxRbacItemDescr 
+	 *	cambia la descripcion de un $itemname.
+	 *
+	 *	Cruge incorpora una sintaxis para la descripcion de los CAuthItem.
+	 *		esa descripcion incorpora entre otras cosas el "$action"
+	 *		que se usara para el $itemname que opera en modo de SubMenuItem.
+	 *
+	 *	Este action es invocado via ajax desde:
+	 *		view/ui/_listauthitems.php	
+	 *
+	 *	Para saber mas consulta acerca del funcionamiento de la sintaxis
+	 *	de la descripcion de un CAuthItem.  Info en CrugeAuthItemManager.php
+	 *
+	 * @param string $action  el nombre action del cual se adosara a la descr.
+	 * @param string $itemname el item cuya descripcion se alteara
+	 * @access public
+	 * @return void
+	 */
+	public function actionAjaxRbacItemDescr($action, $itemname){
+
+		$item = Yii::app()->user->rbac->getAuthItem($itemname);
+
+		Yii::app()->user->rbac->setTaskAction($item, $action);
+
+		header("Content-type: application/json");
+		echo CJSON::encode(array('description'=>$item->getDescription()));
+	}
+
 	// aqui type es uno de los valores de
 	// CAuthItem::TYPE_ROLE,CAuthItem::TYPE_TASK,CAuthItem::TYPE_OPERATION
+	// parametro llamado 'extra' es usado y enviado por CrugeAuthManager
+	// para indicar la creacion de una tarea enlazada a otra
 	public function actionRbacAuthItemCreate($type){
 
 		$editor = new CrugeAuthItemEditor('insert');
 		$editor->categoria = Yii::app()->user->rbac->getAuthItemTypeName($type);
 		$editor->isNewRecord = true;
 
+		if(($type == CAuthItem::TYPE_TASK) && isset($_GET['extra']))
+		if(strlen($_GET['extra'])>0){
+			// enlaza esta tarea con otra superior.
+			// aqui se esta usando el mecanism de sintaxis de descripcion
+			// del authitem definido en la documentacion de CrugeAuthManager
+			$editor->description = ":Item Label{".$_GET['extra']."}";
+		}
 
 		if(isset($_POST['CrugeAuthItemEditor']))
 		{
 	}
 
 	public function actionRbacAuthItemChildItems($id){
+		Yii::app()->user->rbac->autoDetect();
 		$aiModel = Yii::app()->user->rbac->getAuthItem($id);
 		if($aiModel == null)
 			throw new CrugeException("el item de autenticacion senalado no existe");
-
 		$this->render('rbacauthitemchilditems',array('model'=>$aiModel));
 	}
 

File models/ui/CrugeAuthItemEditor.php

 			array('name', 'validar_duplicado','on'=>'insert'),	
 				
 			array('description', 'match'
-				, 'pattern'=>'/^([a-zA-Z0-9.,+-_ ]{1,100})$/'
+				, 'pattern'=>'/^([a-zA-Z0-9.,+\-\_ \{\}\:]{1,100})$/'
 				, 'message'=>CrugeTranslator::t(
-					"solo use letras A-Z, espacio, digitos o los simbolos .,+-_")
+					"solo use letras A-Z, espacio, digitos o los simbolos .,+-_{}:")
 				),
 				
 			array('deleteConfirmation','required','on'=>'delete'),

File resources/estilos.css

 /*
 	auth-item muestra los CAuthItems, 
 */
+/*
 ul.auth-item {
 	overflow: auto;
 	list-style: none;
 ul.auth-item  li.loop{
 	background-color: #ddd;
 }
+*/
+
+#auth-item-tree ul ul li{
+	
+}
+
+#auth-item-tree ul ul ul li{ 
+	
+}
+
+#auth-item-tree ul ul .itemtext {
+
+}
+
+#auth-item-tree ul ul ul .itemchildtext{ 
+	
+}
+
+#auth-item-tree ul ul .checked {
+	color: blue;
+}
+
+#auth-item-tree ul ul ul .checked{ 
+	color: blue;
+}
+
+
+#auth-item-tree .loop{
+	color: red;
+	font-style: italic;	
+}
 
 
 
 }
 div.user-assignments-detail #lista2 .boton {
 	background-image: url("hand.png");
-}
+}

File resources/pin.png

Added
New image

File views/ui/_authitem.php

 			
 		$data es una instancia de CAuthItem
 	*/
+
+	$rbac = Yii::app()->user->rbac;
+
+
+	$asignaciones = $rbac->getCountUsersAssigned($data->name);
 	
-	$asignaciones = Yii::app()->user->rbac->getCountUsersAssigned($data->name);
-	
-	$referencias =  Yii::app()->user->rbac->getParents($data->name);
+	$referencias =  $rbac->getParents($data->name);
 	$count_ref = count($referencias);
+
+	// da un color especial a aquellos TASK que son marcadas como
+	// MENUES o SUBMENUES usando la sintaxis de la descripcion del CAuthItem
+	//
+	$colorEspecialBkTaskTipoMenuitem='';
+	if($data->type == CAuthItem::TYPE_TASK){
+		$extra='';
+		if($rbac->isTaskTopMenuItem($data))
+			$extra = 'border: 2px solid gray;';
+		if($rbac->isTaskMenuItem($data))	
+			$colorEspecialBkTaskTipoMenuitem="style='background-color: #ffffe0;{$extra}'";
+		if($rbac->isTaskSubMenuItem($data)){	
+			$colorEspecialBkTaskTipoMenuitem="style='background-color: #e0ffff;{$extra}'";
+			if(!$rbac->getParentMenuAuthItem($data))
+				$colorEspecialBkTaskTipoMenuitem="style='background-color: #ffaaaa;{$extra}'";
+		}
+	}
+
 	
+	// crea un DropDownList con las operaciones de la tarea
+	// pre seleccionando aquella que esta marcada en la sintaxis de la tarea
+	//
+	//	el evento 'onchange' del dropdown sera manejado en la vista maestra:
+	//		_listaauthitems.php
+	//
+	$oplist = '';
+	if($data->type == CAuthItem::TYPE_TASK){
+		if($rbac->isTaskSubMenuItem($data)){
+			// enumera las operaciones bajo esta tarea	
+			$oplistitems = array();
+			foreach($rbac->getItemChildren($data->name) as $item)
+				if($item->type == CAuthItem::TYPE_OPERATION)
+					if(strtolower(substr($item->name,0,7))=='action_')
+						$oplistitems[] = $item;
+
+			if(!empty($oplistitems)){
+				// tiene operaciones hijas
+				$current_action = $rbac->getTaskActionItemName($data);
+				$oplist = CHtml::dropDownList('crugeavailableops_'.$data->name
+						,$current_action
+						,array(''=>'--'.CrugeTranslator::t('seleccione action')
+							.'--')+CHtml::listData($oplistitems,'name','name')
+						,array('alt'=>$data->name)
+					);
+			}
+		}	
+	}
+
+	//  a las TAREAS que son menues de 1er nivel les crea un link ajax
+	//	para que el usuario cree una nueva tarea hija (sub menu) con 
+	//  la sintaxis de enlace lista.
+	$newChildTask='';
+	if($data->type == CAuthItem::TYPE_TASK){
+		if($rbac->isTaskTopMenuItem($data)){
+
+			$url = Yii::app()->user->ui->getRbacAuthItemCreateUrl(
+				CAuthItem::TYPE_TASK, $data->name);
+
+			$newChildTask = CHtml::link(
+				 CrugeTranslator::t("Nuevo sub menu"),$url);
+		}
+	}
+
+
 ?>
 
-<div class='row'>
+<div class='row' <?php echo $colorEspecialBkTaskTipoMenuitem;?> >
 	<div class='col authname'><?php echo $data->name;?></div>
 	
 	
 	<div class='col operacion'>
 		<b><?php 
 			if($asignaciones > 0) 
-				echo "<span style='cursor: pointer;' title='".CrugeTranslator::t("Usuarios a los que les ha sido asignado este ".Yii::app()->user->rbac->getAuthItemTypeName($data->type))."'>".$asignaciones."&nbsp;".CrugeTranslator::t("asignaciones")."</span>";
+				echo "<span style='cursor: pointer;' title='".CrugeTranslator::t("Usuarios a los que les ha sido asignado este ".$rbac->getAuthItemTypeName($data->type))."'>".$asignaciones."&nbsp;".CrugeTranslator::t("asignaciones")."</span>";
 			?>
 		</b>
 	</div>
 	<div class='col descr'>	
 		<?php 	
 			if(trim($data->description) != '')
-				echo "<hr/>".$data->description;
+				echo "<hr/>"."<span class='description'>"
+					.$data->description."</span>";
 		?>
 	</div>
-</div>
+
+	<?php if($oplist != '') { ?>
+	<div style='float: right;' title='<?php echo CrugeTranslator::t("action que sera tomado como url del menuitem") ?>' >
+		<?php 
+			echo CrugeTranslator::t("Action Maestro")." : ".$oplist;
+		?>
+	</div>
+	<?php } ?>
+
+	<?php if($newChildTask != '') { ?>
+	<div style='float: right;' title='<?php echo CrugeTranslator::t("Creara un sub menu item enlazado a esta tarea.") ?>' >
+		<?php 
+			echo $newChildTask;
+		?>
+	</div>
+	<?php } ?>
+
+
+
+</div>

File views/ui/_authitemform.php

 		<?php echo $form->labelEx($model,'description'); ?>
 		<?php echo $form->textField($model,'description',array('size'=>50,'maxlength'=>100)); ?>
 		<?php echo $form->error($model,'description'); ?>
+		<?php if($model->categoria  == "tarea") { ?>
+		<div class='hint'>Tip: precede este valor con un ":" para que la tarea sea exportada como un menuitem al usar<br/> <span class='code'>
+		Yii::app()->user->rbac->getMenu();</span> y finalizala con un {nombremenuitem} para que quede dentro de un -nombremenuitem-.
+		ejemplo: <span class='code'>":Edita tu Perfil{menuprincipal}"</span></div>
+		<?php } ?>
 	</div>
 	<div class='row'>
 		<?php echo $form->labelEx($model,'businessRule'); ?>
 </div>
 <?php echo $form->errorSummary($model); ?>
 <?php $this->endWidget(); ?>
-</div>
+</div>

File views/ui/_listauthitems.php

         'name',
     ),
 ));	
+	$url_updater = CHtml::normalizeUrl(array('/cruge/ui/ajaxrbacitemdescr'));
+	$loading = Yii::app()->user->ui->getResource('loading.gif');
+	$loading = "<img src='{$loading}'>";
 ?>
 <script>
 	$('#list-auth-items .referencias').each(function(){
 			$(this).parent().find('ul').toggle('slow');
 		});
 	});
-</script>
+	// actualizador de la descripcion del authitem en base a reglas de 
+	// sintaxis.
+	$('#list-auth-items select').each(function(){
+		$(this).change(function(){
+			var action = $(this).val();
+			var parent = $(this).attr('alt');
+			if(action != ''){
+				// hace la actualizacion via ajax y actualiza la descripcion
+				// del item
+				var url = '<?php echo $url_updater; ?>';
+				var dateObject = new Date();
+                var nocache = '&nocache='+dateObject.getTime();
+				url += '&action='+action;
+				url += '&itemname='+parent;
+				url += nocache;
+				var descrSpan = $(this).parent().parent().find('span.description');
+				descrSpan.html("<?php echo $loading;?>");
+				$.getJSON(url, function(data) {
+					// actualiza la descripcion segun la respuesta del ajax
+					
+					descrSpan.html(data['description']);
+				}).error(function(x){
+					descrSpan.html('error: '+x.responseText);
+				});
+			}
+		});
+	});
+</script>

File views/ui/rbacauthitemchilditems.php

 <?php 
-	// permite al usuario seleccionar los AuthItems que conforman a este ROL o TASK,
+	// permite al usuario seleccionar los AuthItems que conforman a ROLE o TASK
 	// las OPERATIONS no tienen childs.
 	//
 	// argumentos recibidos:
 	// $model: instancia de CAuthItem
 	//
 	Yii::app()->clientScript->registerCoreScript('jquery');
-	
 	$rbac = Yii::app()->user->rbac;
-	
-	echo "<h1>".ucfirst($model->name)." (".
-		CrugeTranslator::t($rbac->getAuthItemTypeName($model->type)).")</h1>";
-		
-	echo "<h3 class='hint'>".$model->description."</h3>";
-	
 	$roles = $rbac->getRoles();
 	$tareas = $rbac->getTasks();
 	$operaciones = $rbac->getOperations();
-
-	// estos son los items asignados a la instancia de CAuthItem seleccionado ($model)
-	//
+	// items asignados a la instancia de CAuthItem seleccionado ($model)
 	$childrens = $rbac->getItemChildren($model->name);
 
+	// ROLES
+	$treeDataRoles = array();	
+	// TASKS
+	$treeDataMenu = array();
+	$treeDataError = array();
+	$treeDataRegular = array();
+	// OPERATIONS
+	$treeDataOps = array();
+
+	// titulos
+	echo "<h1>".ucfirst($model->name)." (".
+		CrugeTranslator::t($rbac->getAuthItemTypeName($model->type)).")</h1>";
+	echo "<h3 class='hint'>".$model->description."</h3>";
 	echo "<p>".ucfirst(CrugeTranslator::t(
 	"haga click en un item para activarlo o desactivarlo"))."</p>";
 
+	$iconPin = Yii::app()->user->ui->getResource('pin.png');
+	$imgPin = "<img class='pin-on' src='{$iconPin}' title='"
+		.CrugeTranslator::t("Click para asignar/desasignar el item")."'>";
+
+	//  LISTA DE ROLES 
+	//		si hay roles definidos y la vista no es para un TASK.
+	//
 	if((count($roles) > 0) && ($model->type != CAuthItem::TYPE_TASK)){
-		echo "<hr/><h3>".ucfirst($rbac->getAuthItemTypeName(CAuthItem::TYPE_ROLE,true))."</h3>";
-		echo "<ul class='auth-item'>";
+		
 		foreach($roles as $item){
-			
-			$yaVieneMarcado = isset($childrens[$item->name]);
-			$checked = '';
-			if($yaVieneMarcado)
-				$checked = 'checked';
-			
-			$loader = "<span class='loader'></span>";
+			$asignado = isset($childrens[$item->name]) ? 'checked' : '';
 			$loop = $rbac->detectLoop($model->name,$item->name) ? "loop" : "" ;
-			
-			echo "<li class='{$checked} {$loop}' alt='".$item->name."'>".$item->name.$loader."</li>";
-		}	
-		echo "</ul>";
+			$treeDataRoles[] = array(
+				'id'=>$item->name,
+				'text'=>"<span class='{$asignado} {$loop}'>".$item->name
+					."</span>".$imgPin,
+				'htmlOptions'=>array('class'=>'authitem',
+					'alt'=>$item->name),
+			);
+		}
 	}
 
-	
+	// LISTA DE TAREAS - organizadas segun el uso de sintaxis en descripcion
+	//	(leer acerca de la sintaxis en la clase CrugeAuthManager)
 	if(count($tareas) > 0){
-		echo "<hr/><h3>".ucfirst($rbac->getAuthItemTypeName(CAuthItem::TYPE_TASK,true))."</h3>";
-		echo "<ul class='auth-item'>";
-		foreach($tareas as $item){
-			
-			$yaVieneMarcado = isset($childrens[$item->name]);
-			$checked = '';
-			if($yaVieneMarcado)
-				$checked = 'checked';
-			
-			$loader = "<span class='loader'></span>";
-			$loop = $rbac->detectLoop($model->name,$item->name) ? "loop" : "" ;
-			
-			echo "<li class='{$checked} {$loop}' alt='".$item->name."'>".$item->name.$loader."</li>";
-		}	
-		echo "</ul>";
+		$taskinfo = $rbac->explodeTaskArray($tareas);
+
+		// despliega las tareas que son MENU y SUBMENU pero usando
+		// un TreeView
+		//
+		if(count($taskinfo['topmenu']) > 0)
+		{
+			foreach($taskinfo['topmenu'] as $topTask){
+				$text = $rbac->getTaskText($topTask);
+				$hasChildren = false;
+				$children = array();
+				if(isset($taskinfo['childmenu'][$topTask->name]))
+				foreach($taskinfo['childmenu'][$topTask->name] as $child){
+					$asignado = isset($childrens[$child->name]) ? 
+						'checked' : '';
+					$loop = $rbac->detectLoop($model->name,$child->name) ? 
+						"loop" : "" ;
+					$hasChildren = true;
+					$children[] = array(
+						'id'=>$child->name,
+						'text'=>"<span class='itemchildtext authitemsub {
+							$asignado} {$loop}'>"
+								.$rbac->getTaskText($child)."</span>".$imgPin,
+						'htmlOptions'=>array('class'=>'authitemchild'
+							,'alt'=>$child->name
+							, 'title'=>$child->name),
+					);
+				}
+				$asignado = isset($childrens[$topTask->name]) ? 'checked' : '';
+				$loop = $rbac->detectLoop($model->name,$topTask->name) ? 
+						"loop" : "" ;
+				$treeDataMenu[] = array(
+					'id'=>$topTask->name,
+					'text'=>"<span class='itemtext authitemtop {$asignado} {
+						$loop}'>".$text
+						."</span>".$imgPin,
+					'expanded'=>false,
+					'hasChildren'=>$hasChildren,
+					'children'=>$children,
+					'htmlOptions'=>array('class'=>'authitem',
+							'alt'=>$topTask->name, 'title'=>$topTask->name),
+				);
+			}
+		}
+
+		// Muestra las tareas que fueron consideradas menues pero sus
+		// nodos padre no existen. (tienen su sintaxis de descripcion errada).
+		if(count($taskinfo['orphan'])>0)
+			foreach($taskinfo['orphan'] as $orpTask){
+				$asignado = isset($childrens[$orpTask->name]) ? 'checked' : '';
+				$loop = $rbac->detectLoop($model->name,$orpTask->name) ? 
+						"loop" : "" ;
+				$treeDataError[] = array(
+					'id'=>$orpTask->name,
+					'text'=>"<span class='{$asignado} {$loop}'>".
+						$rbac->getTaskText($orpTask)."</span>".$imgPin,
+					'expanded'=>false,
+					'hasChildren'=>false,
+					'htmlOptions'=>array('class'=>'authitem'
+						, 'alt'=>$orpTask->name),
+				);
+			}
+
+		// Muestra las tareas regulares, aquellas no marcadas con sintaxis.
+		// en otras palabras las tareas comunes y silvestres!
+		if(count($taskinfo['regular'])>0)
+			foreach($taskinfo['regular'] as $task){
+				$asignado = isset($childrens[$task->name]) ? 'checked' : '';
+				$loop = $rbac->detectLoop($model->name,$task->name) ? 
+						"loop" : "" ;
+				$treeDataRegular[] = array(
+					'id'=>$task->name,
+					'text'=>"<span class='{$asignado} {$loop}'>".
+						$task->name."</span>".$imgPin,
+					'expanded'=>false,
+					'hasChildren'=>false,
+					'htmlOptions'=>array('class'=>'authitem',
+						'alt'=>$task->name),
+				);
+			}
 	}
 
+	// LISTA DE OPERACIONES	- organizadas con un filtro
+	//
 	if(count($operaciones) > 0){
-		echo "<hr/><h3>".ucfirst($rbac->getAuthItemTypeName(CAuthItem::TYPE_OPERATION,true))."</h3>";
-		echo "<ul class='auth-item'>";
-		foreach($operaciones as $item){
-			
-			$yaVieneMarcado = isset($childrens[$item->name]);
-			$checked = '';
-			if($yaVieneMarcado)
-				$checked = 'checked';
-			
-			$loader = "<span class='loader'></span>";
-			$loop = $rbac->detectLoop($model->name,$item->name) ? "loop" : "" ;
-			
-			echo "<li class='{$checked} {$loop}' alt='".$item->name."'>".$item->name.$loader."</li>";
-		}	
-		echo "</ul>";
+
+		// arma una lista de categorias en conjunto con el dato 'filter'
+		// usado para agrupar las operaciones con el metodo:
+		//	$rbac->getOperationsFiltered(...)
+		//
+		$listacatg = array();
+		$listacatg['1'] = CrugeTranslator::t('Variadas');
+		$listacatg['3'] = CrugeTranslator::t('Controllers');
+		foreach($rbac->enumControllers() as $controllerName)
+			$listacatg[$controllerName] = $controllerName;
+		$listacatg['2'] = CrugeTranslator::t('Cruge');
+
+		// cada categoria es un sub nodo del arbol CTreeView::operations
+		//
+		foreach($listacatg as $catg_filter => $catg_name){
+			$childs = array();
+			foreach($rbac->getOperationsFiltered($catg_filter, $operaciones) 
+				as $item){
+				// por cada operacion filtrada por $filter la agrega
+
+				$asignado = isset($childrens[$item->name]) ? 'checked' : '';
+				$loop = $rbac->detectLoop($model->name,$item->name) ? 
+						"loop" : "" ;
+
+				$childs[] = array(
+					'id'=>$item->name,
+					'text'=>"<span class='{$asignado} {$loop}'>".
+						$item->name."</span>".$imgPin,
+					'htmlOptions'=>array('class'=>'authitem',
+						'alt'=>$item->name),
+				);
+			}
+
+			$treeDataOps[] = array(
+				'text'=>$catg_name,
+				'hasChildren'=>(count($childs)>0) ? true:false,
+				'expanded'=>false,
+				'children'=>$childs,
+			);
+		}
+
 	}
 
-	
-?>
+	// por razones de generar orden, no le da al usuario la posibilidad
+	// de que a una tarea tipo subitem la componga de otros subitems
+	// si se va a generar un enredo (para el).
+	//
+	if($model->type == CAuthItem::TYPE_ROLE){
+		$arrayTareas = array(
+			array(
+				'text'=>"<b>".CrugeTranslator::t(
+					"Tareas Regulares")."</b>", 
+				'expanded'=>true, 
+				'hasChildren'=>(count($treeDataRegular)>0) ? true : false,
+				'children'=>$treeDataRegular,
+			),
+			array(
+				'text'=>"<b>".CrugeTranslator::t(
+					"Tareas de tipo Menu")."</b>", 
+				'expanded'=>true, 
+				'hasChildren'=>(count($treeDataMenu)>0) ? true : false,
+				'children'=>$treeDataMenu,
+			),
+			array(
+				'text'=>"<b>".CrugeTranslator::t(
+					"Tareas Huerfanas")."</b>", 
+				'expanded'=>true, 
+				'hasChildren'=>(count($treeDataError)>0) ? true : false,
+				'children'=>$treeDataError,
+			),
+		);
+	}else{
+		$arrayTareas = array(
+			array(
+				'text'=>"<b>".CrugeTranslator::t(
+					"Tareas Regulares")."</b>", 
+				'expanded'=>true, 
+				'hasChildren'=>(count($treeDataRegular)>0) ? true : false,
+				'children'=>$treeDataRegular,
+			),
+			array(
+				'text'=>"<b>".CrugeTranslator::t(
+					"Tareas Huerfanas")."</b>", 
+				'expanded'=>true, 
+				'hasChildren'=>(count($treeDataError)>0) ? true : false,
+				'children'=>$treeDataError,
+			),
+		);
+	}
 
+	$this->widget('CTreeView',array(
+		'id'=>'auth-item-tree',
+		'persist'=>'cookie',
+		'data'=>
+		array(
+
+			// ROLES  TREENODE
+			array(
+				'text'=>"<b>".CrugeTranslator::t("Roles")."</b>", 
+				'expanded'=>true, 
+				'children'=>$treeDataRoles,
+			),//end roles treenode
+
+			// TAREAS TREENODE
+			array(
+				'text'=>"<b>".CrugeTranslator::t("Tareas")."</b>", 
+				'expanded'=>true, 
+				'children'=>$arrayTareas,
+			),//end tareas treenode
+
+			// OPERATIONS  TREENODE
+			array(
+				'text'=>"<b>".CrugeTranslator::t(
+					"Operaciones Organizadas por Tipo")."</b>", 
+				'expanded'=>true, 
+				'children'=>$treeDataOps,
+			),//end operations treenode
+			
+		)
+	));
+?>
 
 <script>
-	$('li').each(function(){
-		var li = $(this);
-		li.css("cursor","pointer");
-		li.click(function(){
+	$('img.pin-on').each(function(){
+		var img = $(this);
+		img.css("cursor","pointer");
+		img.click(function(){
 
 			// el atributo alt del LI tiene el nombre del item que representa.
-			var _li = $(this);
+			var _li = $(this).parent();
 			var thisItemName = _li.attr('alt');
-			var setFlag = _li.hasClass('checked') ? false : true;
-			var action = '<?php echo Yii::app()->user->ui->getRbacAjaxSetChildItemUrl()?>';
-			var jsondata = "{ \"parent\": \"<?php echo $model->name;?>\" , \"child\": "
+			var span = _li.find('span');
+
+			//var istop = span.hasClass("authitemtop");
+			//var issub = span.hasClass("authitemsub");
+			//var tiponodo = 0;
+			//if(istop==false && issub==false) tiponodo='normal'; // rol, tarea
+			//if(istop==true && issub==true) tiponodo='top'; // es un topmenu
+			//if(istop==false && issub==true) tiponodo='sub'; // es un submenu
+
+			// el nuevo valor segun el valor checked actual
+			var setFlag = span.hasClass('checked') ? false : true;
+
+			//alert("tiponodo="+tiponodo+" newFlag="+setFlag+", span="
+			//	+span.html());
+			//return;
+			
+			var action = '<?php 
+				echo Yii::app()->user->ui->getRbacAjaxSetChildItemUrl()?>';
+			var jsondata = "{ \"parent\": \"<?php 
+				echo $model->name;?>\" , \"child\": "
 					+"\""+thisItemName+"\" , \"setflag\": "+setFlag+" }";	
-			var loadingUrl = '<?php echo Yii::app()->user->ui->getResource('loading.gif'); ?>';
-			var loader = li.find('span.loader');
+			var loadingUrl = '<?php 
+				echo Yii::app()->user->ui->getResource('loading.gif'); ?>';
+			var loader = _li.find('span.loader');
 
 			loader.html("<img src='"+loadingUrl+"'>");
 			$('#_errorResult').html("");
 				data: jsondata,
 				success: function(data, textStatus, jqXHR){
 					loader.html("");
-					// si se pudo realizar la accion, aqui data trae un objeto json con la data del // item
+					// si se pudo realizar la accion, aqui data trae un objeto 
+					// json con la data del item
 					if(data.result == true){
-						_li.addClass("checked");
+						span.addClass("checked");
 					}else{
-						_li.removeClass("checked");
+						span.removeClass("checked");
 					}
 				},
 				error: function(jqXHR, textStatus, errorThrown){
-					//$('#_errorResult').html("Ocurrio un error:<hr>"+jqXHR.responseText);
-					$('#_errorResult').html("<p class='auth-item-error-msg'>no se pudo agregar</p>");
+					//$('#_errorResult').html("Ocurrio un error:<hr>"
+						//+jqXHR.responseText);
+					$('#_errorResult').html("<p class='auth-item-error-msg'>"
+					  +"no se pudo agregar<br/>"+jqXHR.responseText+"</p>");
 					$('#_errorResult').show("slow");
 					setTimeout(function(){
 						$('#_errorResult').hide("slow");
 			});
 		});
 	});
-	
+
 	
 </script>
 
 <div id='_errorResult'></div>
 <div id='_log'></div>
 
-<?php 
-	$ayuda = "";
-	if($model->type == CAuthItem::TYPE_ROLE)
-		$ayuda = CrugeTranslator::t(
-		"Los roles pueden estar comprendidos de otros roles, tareas u operaciones. el sistema evitara que se hagan ciclos (loops).
-		");
-	if($model->type == CAuthItem::TYPE_TASK)
-		$ayuda = CrugeTranslator::t(
-		"Las tareas pueden estar comprendidos de otras tareas u operaciones. los roles incluyen a tareas por eso no estan presentes aqui para ser seleccionados. el sistema evitara que se hagan ciclos (loops).
-		");
-	echo "<p class='hint'>$ayuda</p>";
-?>
-

File views/ui/rbaclistops.php

 	,Yii::app()->user->ui->getRbacAuthItemCreateUrl(CAuthItem::TYPE_OPERATION));?>
 </div>
 
+<?php 
+	echo CrugeTranslator::t("Filtrar por Controlador:");
+
+	$list = array();
+	$list[0] = '-ver todo-';
+	$list[1] = '-Otras-';
+	$list[2] = '-Cruge-';
+	$list[3] = '-Controladoras-';
+	foreach(Yii::app()->user->rbac->enumControllers() as $c)
+		$list[$c] = $c;
+	$curFilter = isset($_GET['filter']) ? $_GET['filter'] : '';
+	echo "&nbsp;&nbsp;".CHtml::dropDownList("controllers",$curFilter
+			,$list);
+
+	$url = CHtml::normalizeUrl(array('/cruge/ui/rbaclistops'));
+	Yii::app()->clientScript->registerScript('filtrocruge', 
+	"
+		function _CrugeFilterChange() {
+			var filter = $('#controllers').val();
+			var flag=0;
+			document.location.href = '{$url}&filter='+filter+'&menuonly='+flag;
+		}
+		$('#controllers').change(_CrugeFilterChange);
+	");
+?>
+
 <?php $this->renderPartial('_listauthitems'
 	,array('dataProvider'=>$dataProvider)
 	,false
-	);?>
+	);?>

File views/ui/rbaclisttasks.php

 	,Yii::app()->user->ui->getRbacAuthItemCreateUrl(CAuthItem::TYPE_TASK));?>
 </div>
 
-<?php $this->renderPartial('_listauthitems',array('dataProvider'=>$dataProvider),false);?>
+<?php $this->renderPartial('_listauthitems',array('dataProvider'=>$dataProvider),false);?>