<?php
class SecurityComponent extends Object {
	
	
	/**
	 * holds a reference to the controller that is using this component
	 */
		private $controller;
		
	/**
	 * Holds the array of menu rights for the current viewer 
	 * this is populated on every request when there is an active session
	 */
		private $rights = null;	

	/**
	 * the session key that holds the loaded domain rights array
	 */		
		private $rightsKey = 'userRights';
	
	/**
	 * the session key that holds the loaded domain info
	 */		
		private $domainKey = 'domainRights';
		
	/**
	 * holds the current state string passed in post data from the client
	 */	
		var $currentState = null;
	
	/**
	 * holds the info for the current domain;
	 * if the domain is not an account (ie public or management) 
	 * the id is set to the current viewers id
	 */	
		private $domain = null;
	
	/**
	 * holds the id of the current domain;
	 * if the domain is not an account (ie public or management) 
	 * the id is set to the current viewers id
	 */
		private $mIndex = 0;

	/**
	 * model used by this component
	 */	
		private $Clearance = null;
		
	/**
	 * helper component used by this component
	 * the session comp is used to restore the users data
	 */	
		public $components = array('Session');
		
	/**
	 * session array key for captcha code
	 */	
		private $captchaKey = 'logincaptcha';
	
				
	/**
	 * class constructor method
	 *
	 * @param Object $controller - a reference to the controller that is using this component
	 */
		public function startup(&$controller) {
			
			$this->controller =& $controller;
			$this->Clearance = new Clearance();			
			
			$this->rights = null;	
			if($this->Session->check( $this->rightsKey ) ) {
				$this->rights = $this->Session->read( $this->rightsKey );
			}
			
			if( isset( $_POST['state'] ) ) {
				$this->setState( $_POST['state'] );
				unset( $_POST['state'] ); 	
			}
		}
		
		private function setState( $state ) {
			
			$this->currentState = explode('+', $state);
			
			$lmain = strtolower($this->currentState[0]); 
			$viewerName = strtolower( $this->controller->Viewer->getName() );
			
			if( in_array( $lmain, array( 'management', 'public', $viewerName ) ) ) {
				
				$this->domain = array(
					'id' => $this->controller->Viewer->getId(),
					'type' => $this->controller->Viewer->getTypeId(),
					'name' => $this->controller->Viewer->getName(),
					'major' => ( $lmain == $viewerName ) ? 'admin' : $lmain
				);	
				
			} elseif( $this->Clearance->isMenu( $this->currentState[0] ) ) {
				
				$this->domain = $this->Session->read( $this->domainKey );
				
			} else {
				
				$Account = new Account();
				if( $member = $Account->getByUserName( $this->currentState[0] ) ) {
					
					$this->domain = array(
						'id' => $member['Account']['id'],
						'type' => $member['Account']['type'],
						'name' => $member['Account']['username'],
						'major' => 'account'
					);
				}
			}
			$this->Session->write($this->domainKey, $this->domain);
		}
		
		private function _buildTree( &$menu, $level, &$pos ) {
			
			while( isset($menu[$this->mIndex]) ) {
				if( $menu[$this->mIndex]['Menu']['level'] == $level ) {
					$pos[ $menu[$this->mIndex]['Menu']['id'] ] = array('actions' => $this->getActionKeys($menu[$this->mIndex++]['Action']['actions'] ) );
				} elseif( $menu[$this->mIndex]['Menu']['level'] > $level ) {
					$this->_buildTree( $menu, $menu[$this->mIndex]['Menu']['level'], $pos[ $menu[$this->mIndex-1]['Menu']['id'] ] );
				}
			}
		}
		
		private function getActionKeys( $a ) {
			
			$out = array();
			foreach( $a as $key => $value ) {
				if( is_array( $value ) ) {
					$out = array_merge( $out, $value );
				}
			}
			return $out;
		}
		
		private function saveAtIndex( &$result, &$search ) {
			
			foreach( $search as $key => &$value ) {
				if( $key == $result['comp'][0]['Menu']['id'] ) {
					@$key['actions'] = $this->getActionKeys( $result['comp'][0]['Action']['actions'] );
					if( !empty( $result['menu'] ) ) {
						$this->_buildTree( $result['menu'], $result['menu'][0]['Menu']['level'], $value);
					}
					return true;
				} elseif( $key != 'actions' && is_array( $value ) ) {
					if($this->saveAtIndex( $result, $value) ) {
						return true;
					}
				}
			}
			return false;	
		}
		
	/**
	 * saves root menu permissions to session
	 * this allows us to cut down on db rights requests
	 *
	 * @param array $results - an array of db records that have been pre-filtered as allowed
	 * @return  boolean - success
	 */		
		public function cacheMenu( $menu ) {
			
			$this->mIndex = 0;
			$this->_buildTree( $menu, 0, $this->rights ); 
			$this->Session->write($this->rightsKey, $this->rights);
			return true;
		}
			
	/**
	 * saves an action menu to session.
	 * this differs from a normal menu in that it is temporary
	 * and requires an action key to be saved allong with it
	 * it saved directly under the domain (users) initial permission
	 * this allows us to cut down on db rights requests
	 *
	 * @param array $results - an array of db records that have been pre-filtered as allowed
	 * @return  action key - a unique key that must be return to the server when this action is executed
	 */		
		public function cacheActionMenu( $menu ) {
			
			$this->mIndex = 0;
			$this->_buildTree( $menu, 0, $this->rights ); 
			$key = sha1( time() );
			$this->rights[$menu[0]['Menu']['id']]['actionkey'] = $key;
			$this->Session->write($this->rightsKey, $this->rights);
			return $key;
		}
	
	/**
	 * saves accessed menu permissions to the users session
	 * this allows us to cut down on db rights requests
	 *
	 * @param array $results - an array of db records that have been pre-filtered as allowed
	 * @return  boolean - success
	 */	
		public function cacheRights( $results ) {
			//print_r( $this->rights );
			if( $this->saveAtIndex( $results, $this->rights[ $this->currentState[0] ] ) ) {
				$this->Session->write($this->rightsKey, $this->rights);
				return true;
			}
			return false;
		}
		
	/**
	 * empties/removes the entire menu rights cache
	 *
	 * @return viod
	 */
		public function clearCache() {
			
			$this->rights = array();
			$this->Session->write($this->rightsKey, $this->rights);
		}
	
	/**
	 * empties/removes the cached menu rights for a specified menu 
	 *
	 * @param sting $menu - the name of the menu to clear
	 * @return viod
	 */
		public function clearCacheAt( $menu ) {
			
			unset($this->rights[$menu]);
			$this->Session->write($this->rightsKey, $this->rights);
		}
		
	/**
	 * empties/removes the cached menu rights for a specified menu 
	 *
	 * @param sting $menu - the name of the menu to clear
	 * @return viod
	 */
		public function clearActionCacheAt( ) {
			
			unset( $this->rights[ $this->currentState[0] ] );
			$this->Session->write($this->rightsKey, $this->rights);
		}
		
	/**
	 * checks the rights cache to make sure the passed action key 
	 * is set for the current menu state
	 *
	 * @param sting $key - the action key to check
	 * @return boolean - true if the viewer has access false if not
	 */	
		public function checkAction( $key ) {
			
			$i = 1;
			//print_r( $this->rights );
			$h = $this->rights[ $this->currentState[0] ];
			//print_r($this->rights);
			while( isset($this->currentState[$i], $h[ $this->currentState[$i] ] ) ) {
				$h = $h[ $this->currentState[$i++] ];
			}
			return in_array( $key, $h['actions'] );
		}
		
	/**
	 * checks a unique key submitted with a form against the saved session.  
	 * this must pass inorder for the form to be saved
	 *
	 * @param sting $key - the key submitted with the form
	 * @return boolean - true if the key is in session false if not
	 */		
		public function checkActionKey( $key ) {
			
			$h = $this->rights[ $this->currentState[0] ];
			return (isset($h['actionkey']) && $h['actionkey'] === $key);			
		}
		
	/**
	 * creates a unique id for the user and app 
	 * this is saved to the app_session table for the current session and is used to confirm
	 * the base STV app has been loaded for the current viewer
	 * this key MUST be returned in a post param for every subsiquent request
	 *
	 * @param string $signiture - an existing signiture to use instead of generating a new one
	 *							  this is used to refresh timed out connections
	 * @return the saved signiture;
	 */	
		public function generateSigniture( $signiture=null ) {
			
			//create unique key for client server data validation
			$sig = isset($signiture)? $signiture : sha1(microtime() * mktime());
			
			if( !$this->Clearance->saveAppSig( $sig ) ) {
				//log error??? or is it fatial	
			}
			return $sig;
		}
		
	/**
 	 * determines if an app can be salviged from the session table
	 * session records with account ids are not garbage collected and there for
	 * if the user can reconfirm there u & p they can continue as if nothing timed out
	 * if a user was not logged in then there session is completely lost and unsalvageable
	 * once expired
	 * NOTE : this will leave 1 session record per logged in user in the table (THIS COULD GET BIG AND UGLY)
	 *
	 * @param string $sig - the tv signiture in the $_POST array for the current request
	 * @return boolean success
	 */
		public function getAppStatus( $sig ) {
			
			if( $sig ) {
				if( $i = $this->Session->getAppData( $sig ) ) {
					return array(
						'valid' => (boolean) ($i['app_key'] === $sig ),
						'account_id' => $i['account_id']
					);	
				}
			}
			return array(
				'valid' => false,
				'account_id' => null
			);	
		}
		
	/**
	 * returns any roles that the viewer may NOT assume in the current menu context
	 * this will remove any permissions give to the viewer by the role 
	 */	
		public function getFilterRole() {
			
			$filter = null;
			switch( $this->domain['major'] ) {
				case 'admin':
					//there own domain can not assume public
					$filter = 1;
				break;
				case 'public':
					// nothing needs to be filtered for public
				break;
				case 'management':
					// nothing needs to be filtered for public
				break;
				case  'account':
					//visiting another accounts domain filter the viewers account type
					//but first check to make sure they were not visiting there own when not logged in
					if( $this->domain['id'] != $this->controller->Viewer->getId() ) {
						$filter = $this->domain['type'];
					}
				break;
			}
			return $filter; 
		}
	
	/**
	 * generates a captcha code saves it to session and returns the image
	 * 
	 * @param int $width - the desired width of the image defaults to 120px
	 * @param int $height - the desired height of the image defaults to 40px
	 * @param int $characters - the number of characters to add to the captcha string
	 * @returns jpg - a jpg image with the character code in it
	 */
		public function generateCaptcha($width='120',$height='40',$characters='6') {
      		
			$font = '/opt/www/tv/app/controllers/components/monofont.ttf';
			$possible = '23456789bcdfghjkmnpqrstvwxyz';
		  	$code = '';
		  	$i = 0;
		  	while ($i < $characters) { 
				$code .= substr($possible, mt_rand(0, strlen($possible)-1), 1);
			 	$i++;
		  	}
			
      		/* font size will be 75% of the image height */
      		$font_size = $height * 0.75;
		  	$image = imagecreate($width, $height) or die('Cannot initialize new GD image stream');
		  	/* set the colours */
		  	$background_color = imagecolorallocate($image, 255, 255, 255);
		  	$text_color = imagecolorallocate($image, 20, 40, 100);
		  	$noise_color = imagecolorallocate($image, 100, 120, 180);
		  	/* generate random dots in background */
		  	for( $i=0; $i<($width*$height)/3; $i++ ) {
				imagefilledellipse($image, mt_rand(0,$width), mt_rand(0,$height), 1, 1, $noise_color);
		  	}
		  	/* generate random lines in background */
		  	for( $i=0; $i<($width*$height)/150; $i++ ) {
				imageline($image, mt_rand(0,$width), mt_rand(0,$height), mt_rand(0,$width), mt_rand(0,$height), $noise_color);
		  	}
		  	/* create textbox and add text */
		  	$textbox = imagettfbbox($font_size, 0, $font, $code) or die('Error in imagettfbbox function');
		  	$x = ($width - $textbox[4])/2;
		  	$y = ($height - $textbox[5])/2;
		  	imagettftext($image, $font_size, 0, $x, $y, $text_color, $font , $code) or die('Error in imagettftext function');
		  	/* output captcha image to browser */
		  	header('Content-Type: image/jpeg');
		  	imagejpeg($image);
		  	imagedestroy($image);
			$this->Session->write($this->captchaKey, $code);
		}
	
	/**
	 * checkes to see if the passed key matches a captcha code saved to session by a previous call to Security::generateCaptcha
	 *
	 * @param string $key - the user submitted character string to test against session value
	 * @returns boolean success
	 */
		public function checkCaptcha( $key ) {
			
			if($this->Session->check( $this->captchaKey ) && $this->Session->read( $this->captchaKey ) == $key  ) {
				return true;
			}
			return false;
		}
	
	/**
	 * clears any saved captcha strings from the current session
	 *
	 * @return VOID
	 */
		public function clearCaptcha() {
			
			$this->Session->delete( $this->captchaKey );
		}			
		
		public function getDomainId() {
			
			//print_r( $this->domain);
			return $this->domain['id'];
		} 
		
		public function getDomainName() {
			
			return $this->domain['name'];
		} 
		
		public function getMajorDomain() {
			
			return $this->domain['major'];	
		}
		
		public function isFriend( $accountA, $accountB ) {
			
			return $this->Clearance->isFriend( $accountA, $accountB );
		}
		
		public function getClearance() {
			
			return  $this->Clearance->getSecurityLevel( end($this->currentState), $this->domain['type'], $this->domain['id'] );		
		}
		
		public function clear() {
			
			$this->rights = null;
			$this->domain = null;
			$this->Session->delete( $this->rightsKey );
			$this->Session->delete( $this->domainKey );
		}
}
?>