8detect
7/3/2013 - 11:06 PM

Acl.php

Acl.php

<?php

namespace PHN\Model;
class Acl{
	
	public $tree;
	public $controller;
	public $db;
	public $cfg;
	public $acl; 
	
	//predefined convention role names
	const ROLE_GUEST='guest';
	const ROLE_USER='user';

	function __construct(&$cfg,$controller) {
		$this->controller = &$controller;
		$this->user = &$controller->user;
		
		$this->cfg = &$cfg;
		//convention role name
		if (!isset($this->cfg['role_names'])) $this->cfg['role_names'] = array(
			'guest'=>self::ROLE_GUEST,
			'user'=>self::ROLE_USER,
		);
		
		if ($cfg['usedb']) //chỉ dùng db để load tree ... khi usedb=true ( mặc định = true )
		{
			if (isset($cfg['db']['connect'])) $this->db = $this->controller->db($cfg['db']['connect']);
			else  $this->db = $this->controller->db();
			//connection error
			if (!$this->db) {
				$e = new \Exception("DB_SERVER_ERROR", 404);
				throw $e;
			}
			else { //$this->db ready
				
				$treeCfg=array(
					'db'=>$this->db,
					'tableMain'=>'acl_role',
					'tableMainId'=>'role_id',
					'tableMainCode'=>'role_code',
					'tableMainName'=>'role_name',
					'tablePath'=>'acl_closure'
				);			
				
				$this->tree = new \PHN\Model\SQLClosureTree($treeCfg);
			}
		}
	}
	
	
	// <editor-fold defaultstate="collapsed" desc=" Role Tree Related Functions  ">



	/**
	 * 
	 * @param type $role_code
	 * @param type $role_name
	 * @return int role_id
	 */
	public function addRole($role_code,$role_name,$parent=null)
	{
		$this->tree->append(array('code'=>$role_code,'name'=>$role_name),$parent);
	}
	
	public function deleteRole($role_code,$tree=true)
	{
		$this->tree->delete($role_code,$tree);
	}
	
	public function insertRole($role_code,$role_name,$sibling)
	{
		
	}
	
	public function getRolePath($role_code)
	{
		return $this->tree->getPath($role_code);
	}
	
	public function replaceRoleParent($role_code,$parent_code)
	{
		$this->tree->replaceParent($role_code,$parent_code);
	}		


	public function getRoleTree($role_code,$level=0)
	{
		return $this->tree->getChildren($role_code,$level);
	}	
	
	public function getRoleTreeSorted($role_code)
	{
		return $this->tree->getTreeSorted($role_code);
	}

	// </editor-fold>
	
	/**
	 * private
	 * get any user's acl , default is current user
	 * @param array/null $user <=> array('user_id'=>,'username'=>) if null then use controller->user[]
	 * @return array
	 * array(2) {
		["role-pmses"]=>{
			["user"]=>{
					["pms_cfg_1"]=>1
			}
			["admin"]=>{
			  [0]=>
			  string(1) "*"
			  ["pms-1"]=>
			  int(1)

			}
			["admin-sub2"]=>
			array(1) {
			  ["pms-1"]=>
			  int(1)
			}
		}
		["roles"]=>
		array(9) {
		  ["user"]=>
		  int(2)
		  ["admin"]=>
		  string(1) "3"
		  ["admin-sub1"]=>
		  string(1) "4"
		  ["admin-sub1-sub1"]=>
		  string(1) "7"
		  ["admin-sub1-sub1-sub1"]=>8
		}
		}
	 */
	public function getAcl($user=null)
	{

		$cfg = &$this->cfg;
		$my_roles =array(); //cumulative roles
		$my_cfg_roles=array(); //roles from cfg
		$my_role_pmses=array(); //cumulative role-pmses
		
		
		if (empty($user)) {
			$username = $this->controller->user['username'];
			$user_id = $this->controller->user['user_id'];
		}
		else {
			$username=$user['username'];
			$user_id=$user['user_id'];
		}
		
		// <editor-fold defaultstate="collapsed" desc=" normalize some configs first ">
		if (isset($cfg['role-pmses'])) {
			
			foreach ($cfg['role-pmses'] as $key=>$value) {
				if ($value =='*') $cfg['role-pmses'][$key] = array('*'=>1);
			}
		}		
		// </editor-fold>
		
		//không đăng nhập thì thuộc nhóm guest
		if (!$username) {
			$my_cfg_roles[] = $cfg['role_names']['guest'];
		}
		//nếu đăng nhập tức có id thì mặc định thuộc nhóm user
		else {
			$my_cfg_roles[] = $cfg['role_names']['user'];
			//dò tiếp xem còn thuộc nhóm gì trong cfg ?
			if (isset($cfg['user-roles']) && isset($cfg['user-roles'][$username])) $my_cfg_roles = array_merge( $my_cfg_roles,$cfg['user-roles'][$username]);			
		}
		
		//normalize my_cfg_roles as [role_code=>role_id]
		$my_roles = array();
		$my_role_ids = array();
		foreach($my_cfg_roles as $role){
			if (isset($cfg['roles']) && isset($cfg['roles'][$role])) $my_roles[$role] = $cfg['roles'][$role];
		}
		
		//đã xác định xong các nhóm từ cfg mà user thuộc về
		//từ đó tìm pms tương ứng
		foreach ($my_roles as $role=>$id)
		{
			if (isset($cfg['role-pmses'][$role]))
				$my_role_pmses[$role] =$cfg['role-pmses'][$role];
		}
		
		//var_dump($my_roles,$my_role_pmses);exit;
		
		//nếu user logged in thì mới dùng db để tìm roles/pms
		if ($cfg['usedb']){
			
			//try cache first
			$this->db->table('acl_cache');
			
			//chỉ lấy cache nếu user đã đăng nhập
			if ($user_id) 	$cache=$this->db->getVal(array("select"=>"data","where"=>"user_id=$user_id"));
			else $cache=null;
			
			//nếu chưa có cache 
			if (!$cache){
				//query db
				$this->db->table('acl_role');
				
				//nếu user đăng nhập thì
				//lấy roles mà user_id này có từ trong db
				if ($user_id) {

					$roles =$this->db->getAll("
						SELECT acl_role.role_id FROM acl_role
						JOIN acl_user_role ON (acl_role.role_id=acl_user_role.role_id)
						WHERE acl_user_role.user_id = $user_id
					");

					//nếu tồn tại roles trong db mà user này thuộc về thì tiến hành cummulative và merge
					if (!empty($roles)) {

						$cumulative_roles=array();
						foreach ($roles as $role) 
						{						
							$path=$this->getRolePath($role['role_id']);						
							$cumulative_roles = array_merge($cumulative_roles,$path);						
						}

						//echo "<hr>";
						//var_dump($cumulative_roles); exit
						//ex : 1; 1,4,7; 1,5 

						//chuyển sang dạng {'code'=>id} merge với my_roles trước đó từ cfg
						//chuyển sang dạng key nên những id trùng lắp sẽ chỉ còn 1 
						foreach ($cumulative_roles as $role){
							$role_code = $role['role_code'];
							$my_roles[$role_code] =  $role['role_id'] ;

							//nếu role mới có 'pms' trong cfg trước đó thì update $my_role_pmses
							if ( isset($cfg['role-pmses'][$role_code]))
								$my_role_pmses[$role_code] =$cfg['role-pmses'][$role_code];
						}

					}//if (!empty($roles)					

				}//end if ($user_id) 
				
					
				//echo "<hr>";
				//var_dump($my_roles);exit;
				//var_dump($my_role_pmses);exit;

				//chú ý ngay cả user không đăng nhập thì user vẫn thuộc role guest nên role_ids luôn tồn tại
				$role_ids = implode(',', array_values($my_roles));

				//echo "<hr>";
				//var_dump($role_ids);

				//tìm mọi pms cho role có trong danh sách cần tìm kể cả guest
				$role_pmses = $this->db->getAll("
					SELECT acl_role.role_id,acl_role.role_code,acl_pms.pms_code,acl_role_pms.allow 
					FROM acl_role
					JOIN acl_role_pms ON (acl_role.role_id=acl_role_pms.role_id)
					JOIN acl_pms ON (acl_role_pms.pms_id=acl_pms.pms_id)
					WHERE acl_role_pms.role_id IN ($role_ids)
				");

				if (!empty($role_pmses)){
						//convert to $code=>array(pms=>id)
						$db_role_pmses=array();
						foreach($role_pmses as $role)
						{
							$role_code = $role['role_code'];
							if (!isset($db_role_pmses[$role_code])) $db_role_pmses[$role_code] = array();
							$db_role_pmses[$role_code][$role['pms_code']] = (int) $role['allow'];										
						}
						//merge 
						$my_role_pmses = array_merge_recursive($my_role_pmses,$db_role_pmses);

				}
					

				//chỉ lưu cache nếu user đã đăng nhập và my_roles có từ 2 roles trở lên vì mặc định user luôn thuộc 
				//role user nếu đăng nhập hoặc guest nếu chưa đăng nhập
				//tức count(my_roles) luôn tối thiểu bằng 1
				//hoặc pmses phải có ít nhất 1 quyền
				if ($user_id && (count($my_roles) > 1 || count($my_role_pmses)) )
				{
					$cache = array(
							'role-pmses'=>$my_role_pmses,
							'roles'=>$my_roles						
					);
					
					
					$this->db->table('acl_cache');
					$this->db->insert(array("user_id"=>$user_id,
							'data'=>serialize($cache),
							'create_time'=>array('NOW()'),
							'modify_time'=>array('NOW()')
							));
					
				}
				
				
			}//if not cache
			
				//có thể cache trong db nhưng là chuỗi rỗng/null tức ngầm hiểu user chỉ thuộc duy nhất là nhóm user 
				//mặc định mọi user có đăng ký ( tức có user_id ) đều thuộc user nên không cần lưu cache chi cho tốn kém
			elseif (!empty($cache)) $cache = unserialize($cache);
			
			if ($cache) return $cache;
			else return array(
				'role-pmses'=>$my_role_pmses,
				'roles'=>$my_roles				
			);
		
		}//end usedb
		else{
			return array(
				'role-pmses'=>$my_role_pmses,
				'roles'=>$my_roles				
			);
			
		}

	}
	
	/**
	 * 
	 * @return array
	 */
	public function getPms($user=null)
	{
		if (empty($user)) {
			$username = $this->controller->user['username'];
			$user_id = $this->controller->user['user_id'];
		}
		else {
			$username=$user['username'];
			$user_id=$user['user_id'];
		}
		
		$key = 'acl';
		if (isset($user_id)) $key.=$user_id;
		
		if (isset($_SESSION[$key])) $this->acl=&$_SESSION[$key];
		else {
			
			$acl=$this->getAcl($user);
			$roles=&$acl['roles'];
			$role_pmses=&$acl['role-pmses'];
			
			$pmses=array();
			$reg_pmses=array();
			
			foreach ($acl['roles'] as $role=>$id) 
			{
				if (isset($role_pmses[$role])) {
					$pmses=array_merge($pmses,$role_pmses[$role]);
				}
			}
			
			foreach ($pmses as $k=>$v)
			{
				if (preg_match("#^\^#", $k)) {
					$reg_pmses[$k]=$v;
				}
			}	
				
			
			$this->acl=array(
				'roles'=>$roles,
				'pmses'=>$pmses
			);
			
			if (!empty($reg_pmses)) $this->acl['reg-pmses']=$reg_pmses;
			
			//$_SESSION[$key]=$acl;
		}
		
		return $this->acl;
	}		

	/**
	 * kiểm tra xem pms có khớp trong pms mask không ?
	 * @param type $pms
	 */
	private function matchPMS(&$pms,&$pmses)
	{
		if ( isset($pmses['pmses'][$pms]) &&$pmses['pmses'][$pms] ) return true;
		elseif (isset($pmses['pmses']['*']) && !(isset($pmses['pmses'][$pms]) && !$pmses['pmses'][$pms])) return true;
		elseif(isset($pmses['reg-pmses'])) {
			foreach($pmses['reg-pmses'] as $k=>$v)
			{
				if (preg_match("#$k#",$pms)) return $v;
			}

			//not found
			return false;
		}
		
		return false;
	}		


	/**
	 * 
	 * @param string $pms 
	 * @param int $user_id, default using current logged user_id
	 * @return boolean
	 */
	public function allow($pms=null,$user=null)
	{
		
		if (empty($pms))
		{
			$pms=$this->controller->app->controllerName .'-'.$this->controller->app->controllerAction;
		}	
		
		if (empty($user)) {
			$username = $this->controller->user['username'];
			$user_id = $this->controller->user['user_id'];
		}
		else {
			$username=$user['username'];
			$user_id=$user['user_id'];
		}
		
		$pmses=$this->getPms($user);
		
		if (!is_array($pms)) {
			return $this->matchPMS($pms,$pmses);
		}
		else{
			foreach($pms as $p){
				//chỉ cần phát hiện 1 pms không thỏa thì return false;
				if (!$this->matchPMS($p,$pmses)) return false;				
			}
			return true;
		}

	}
	
	/**
	 * check if user belongs to role(s)
	 * @param string/array of string $role
	 * @param type $user_id
	 */
	public function hasRole($role,$user=null)
	{

		if (empty($user)) {
			$username = $this->controller->user['username'];
			$user_id = $this->controller->user['user_id'];
		}
		else {
			$username=$user['username'];
			$user_id=$user['user_id'];
		}

		$pmses=$this->getPms($user);

		if (!is_array($role)) {
			return isset($pmses['roles'][$role]);
		}
		else{
			foreach($role as $r){
				if (!isset($pmses['roles'][$r])) return false;
			}
			return true;
		}	
	}
		
	
}
?>