<?php
/*
	+-----------------------------------------------------------------------------+
	| ILIAS open source                                                           |
	+-----------------------------------------------------------------------------+
	| Copyright (c) 1998-2001 ILIAS open source, University of Cologne            |
	|                                                                             |
	| This program is free software; you can redistribute it and/or               |
	| modify it under the terms of the GNU General Public License                 |
	| as published by the Free Software Foundation; either version 2              |
	| of the License, or (at your option) any later version.                      |
	|                                                                             |
	| This program is distributed in the hope that it will be useful,             |
	| but WITHOUT ANY WARRANTY; without even the implied warranty of              |
	| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               |
	| GNU General Public License for more details.                                |
	|                                                                             |
	| You should have received a copy of the GNU General Public License           |
	| along with this program; if not, write to the Free Software                 |
	| Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. |
	+-----------------------------------------------------------------------------+
*/


/**
* class ilRbacSystem
* system function like checkAccess, addActiveRole ...
* Supporting system functions are required for session management and in making access control decisions.
* This class depends on the session since we offer the possiblility to add or delete active roles during one session.
* 
* @author Stefan Meyer <smeyer@databay.de> 
* @version $Id: class.ilRbacSystem.php 22411 2009-11-25 10:41:22Z smeyer $
* 
* @ingroup ServicesAccessControl
*/
class ilRbacSystem
{
	protected static $instance = null;
	
	protected $mem_view;
	
	protected static $user_role_cache = array();
	var $ilias;

	// Cache accesses to RBAC PA
	private static $_paCache = null;

	// Cache outcomes of calls to checkAccessOfuser
	private static $_checkAccessOfUserCache = null;

	/**
	* Constructor
	* @access	public
	*/
	protected function ilRbacSystem()
	{
		global $ilDB,$ilErr,$ilias;

		$this->ilias =& $ilias;

		// set db & error handler
		(isset($ilDB)) ? $this->ilDB =& $ilDB : $this->ilDB =& $ilias->db;
		
		if (!isset($ilErr))
		{
			$ilErr = new ilErrorHandling();
			$ilErr->setErrorHandling(PEAR_ERROR_CALLBACK,array($ilErr,'errorHandler'));
		}
		else
		{
			$this->ilErr =& $ilErr;
		}
	}
	
	public static function getInstance()
	{
		if(self::$instance)
		{
			return self::$instance;
		}
		return self::$instance = new ilRbacSystem();
	}
	
	/**	
	* checkAccess represents the main method of the RBAC-system in ILIAS3 developers want to use
	*  With this method you check the permissions a use may have due to its roles
	*  on an specific object.
	*  The first parameter are the operation(s) the user must have
	*  The second & third parameter specifies the object where the operation(s) may apply to
	*  The last parameter is only required, if you ask for the 'create' operation. Here you specify
	*  the object type which you want to create.
	* 
	*  example: $rbacSystem->checkAccess("visible,read",23);
	*  Here you ask if the user is allowed to see ('visible') and access the object by reading it ('read').
	*  The reference_id is 23 in the tree structure.
	*  
	* @access	public
	* @param	string		one or more operations, separated by commas (i.e.: visible,read,join)
	* @param	integer		the child_id in tree (usually a reference_id, no object_id !!)
	* @param	string		the type definition abbreviation (i.e.: frm,grp,crs)
	* @return	boolean		returns true if ALL passed operations are given, otherwise false
	*/
	function checkAccess($a_operations,$a_ref_id,$a_type = "")
	{
		global $ilUser,$ilBench;
		
		$ilBench->start("RBAC", "system_checkAccess");

		$result = $this->checkAccessOfUser($ilUser->getId(), $a_operations, $a_ref_id, $a_type);

		$ilBench->stop("RBAC", "system_checkAccess");
		
		return $result;
	}
	
	function checkAccessOfUser($a_user_id, $a_operations, $a_ref_id, $a_type = "")
	{
		global $ilUser, $rbacreview,$ilObjDataCache,$ilDB,$ilLog;

		// Create the user cache key
		$cacheKey = $a_user_id.':'.$a_operations.':'.$a_ref_id.':'.$a_type;

		// Create the cache if it does not yet exist
		if (! is_array(self::$_checkAccessOfUserCache)) {
			self::$_checkAccessOfUserCache = array();
		}
		
		// Try to return result from cache
		if (array_key_exists($cacheKey, self::$_checkAccessOfUserCache)) {
			return self::$_checkAccessOfUserCache[$cacheKey];
		}

		#echo ++$counter;

		// DISABLED 
		// Check For owner
		// Owners do always have full access to their objects
		// Excluded are the permissions create and perm
		// This method call return all operations that are NOT granted by the owner status 
		if(!$a_operations = $this->__filterOwnerPermissions($a_user_id,$a_operations,$a_ref_id))
		{
			// Store positive outcome in cache.
			// Note: we only cache up to 1000 results to avoid memory overflows
			if (count(self::$_checkAccessOfUserCache) < 1000) {
				self::$_checkAccessOfUserCache[$cacheKey] = true;
			}
			return true;
		}

		
		// get roles using role cache
		$roles = $this->fetchAssignedRoles($a_user_id,$a_ref_id);
		
		// exclude system role from rbac
		if (in_array(SYSTEM_ROLE_ID, $roles))
		{
			// Store positive outcome in cache.
			// Note: we only cache up to 1000 results to avoid memory overflows
			if (count(self::$_checkAccessOfUserCache) < 1000) {
				self::$_checkAccessOfUserCache[$cacheKey] = true;
			}
			return true;		
		}

		if (!isset($a_operations) or !isset($a_ref_id))
		{
			$GLOBALS['ilLog']->logStack();
			$this->ilErr->raiseError(get_class($this)."::checkAccess(): Missing parameter! ".
							"ref_id: ".$a_ref_id." operations: ".$a_operations,$this->ilErr->WARNING);
		}

		if (!is_string($a_operations))
		{
			$GLOBALS['ilLog']->logStack();
			$this->ilErr->raiseError(get_class($this)."::checkAccess(): Wrong datatype for operations!",$this->ilErr->WARNING);
		}

		// Create the PA cache if it does not exist yet
		$paCacheKey = $a_user_id.':'.$a_ref_id;
		if (! is_array(self::$_paCache)) {
            self::$_paCache = array();
        }

        if (array_key_exists($paCacheKey, self::$_paCache)) {
			// Return result from PA cache
            $ops = self::$_paCache[$paCacheKey];
        } 
		else 
		{
			// Data is not in PA cache, perform database query
			$q = "SELECT * FROM rbac_pa ".
					 "WHERE ref_id = ".$ilDB->quote($a_ref_id, 'integer');

			$r = $this->ilDB->query($q);

			$ops = array();

			while ($row = $r->fetchRow(DB_FETCHMODE_OBJECT))
			{
				if (in_array($row->rol_id, $roles))
				{
					$ops = array_merge($ops,unserialize(stripslashes($row->ops_id)));
				}
			}
			// Cache up to 1000 entries in the PA cache
			if (count(self::$_paCache) < 1000) 
			{
				self::$_paCache[$paCacheKey] = $ops;
			}
        }

		$operations = explode(",",$a_operations);
		foreach ($operations as $operation)
		{
			if ($operation == "create")
			{
				if (empty($a_type))
				{
					$this->ilErr->raiseError(get_class($this)."::CheckAccess(): Expect a type definition for checking a 'create' permission",
											 $this->ilErr->WARNING);
				}

				$ops_id = ilRbacReview::_getOperationIdByName($operation."_".$a_type);
			}
			else
			{
				$ops_id = ilRbacReview::_getOperationIdByName($operation);
			}
			if (! in_array($ops_id,$ops)) 
			{
				//$ilLog->write('PERMISSION: '.$a_ref_id.' -> '.$a_ops_id.' failed');
				// Store negative outcome in cache.
				// Note: we only cache up to 1000 results to avoid memory overflows
				if (count(self::$_checkAccessOfUserCache) < 1000) 
				{
					self::$_checkAccessOfUserCache[$cacheKey] = false;
				}
				return false;
			}
		}

		// Store positive outcome in cache.
		// Note: we only cache up to 1000 results to avoid memory overflows
		if (count(self::$_checkAccessOfUserCache) < 1000) 
		{
			//$ilLog->write('PERMISSION: '.$a_ref_id.' -> '.$ops_id.' granted');
			self::$_checkAccessOfUserCache[$cacheKey] = true;
		}
		return true;
	}
	
	/**
	* check if a specific role has the permission '$a_operation' of an object
	* @access	public
	* @param	integer		reference id of object
	* @param	integer		role id 
	* @param	string		the permission to check
	* @return	boolean
	*/
	function checkPermission($a_ref_id,$a_rol_id,$a_operation)
	{
		global $ilDB;
		
		$ops = array();

		$query = 'SELECT ops_id FROM rbac_operations '.
			'WHERE operation = '.$ilDB->quote($a_operation,'text');
		$res = $ilDB->query($query);		
		while($row = $ilDB->fetchObject($res))
		{
			$ops_id = $row->ops_id;
		}
	
		$query = "SELECT * FROM rbac_pa ".
			 "WHERE rol_id = ".$ilDB->quote($a_rol_id,'integer')." ".
			 "AND ref_id = ".$ilDB->quote($a_ref_id,'integer')." ";
		$res = $ilDB->query($query);		

		while($row = $ilDB->fetchObject($res))
		{
			$ops = array_merge($ops,unserialize($row->ops_id));
		}
		return in_array($ops_id,$ops);
	}

	function __filterOwnerPermissions($a_user_id,$a_operations,$a_ref_id)
	{
		global $ilObjDataCache,$ilUser;

		// member view constraints
		if($this->mem_view['active'] and $a_user_id == $ilUser->getId())
		{
			if(in_array($a_ref_id, $this->mem_view['items']))
			{
				return $a_operations;
			}
		}

		if($a_user_id != $ilObjDataCache->lookupOwner($ilObjDataCache->lookupObjId($a_ref_id)))
		{
			return $a_operations;
		}
		// Is owner
		foreach(explode(",",$a_operations) as $operation)
		{
			if($operation != 'cat_administrate_users' and $operation != 'edit_permission' and !preg_match('/^create/',$operation))
			{
				continue;
			}
			if(!strlen($new_ops))
			{
				$new_ops = $operation;
			}
			else
			{
				$new_ops .= (','.$operation);
			}
		}
		return $new_ops;
		
		
	}
	
	/**
	 * Fetch assigned roles
	 * This method caches the assigned roles per user  
	 *
	 * @access private
	 * @param int user id
	 * 
	 */
	private function fetchAssignedRoles($a_usr_id,$a_ref_id)
	{
	 	global $ilUser,$rbacreview;
		
		// Member view constraints
		if($this->mem_view['active'] and $a_usr_id == $ilUser->getId())
		{
			// check if current ref_id is subitem of active container
			if(in_array($a_ref_id, $this->mem_view['items']) and $this->mem_view['role'])
			{
				// Return default member role
				return array($this->mem_view['role']);
			}
		}

		if(isset(self::$user_role_cache[$a_usr_id]) and is_array(self::$user_role_cache))
		{
			return self::$user_role_cache[$a_usr_id];
		}



		return self::$user_role_cache[$a_usr_id] = $rbacreview->assignedRoles($a_usr_id);
	}
	
	/**
	 * Init member view
	 * @return 
	 */
	public function initMemberView()
	{
		include_once './Services/Container/classes/class.ilMemberViewSettings.php';
		$settings = ilMemberViewSettings::getInstance();
		if($settings->isEnabled() and isset($_GET['mv']))
		{
			$settings->toggleActivation((int) $_GET['ref_id'], (int) $_GET['mv']);
		}
		
		if(!$settings->isActive())
		{
			$this->mem_view['active'] = false;
			$this->mem_view['items'] = array();
			$this->mem_view['role'] = 0;
		}
		else
		{
			global $tree;
			
			$this->mem_view['active'] = true;
			$this->mem_view['items'] = $tree->getSubTreeIds($settings->getContainer());
			$this->mem_view['items'] = array_merge($this->mem_view['items'],array($settings->getContainer()));
			include_once './Services/Membership/classes/class.ilParticipants.php';
			$this->mem_view['role'] = ilParticipants::getDefaultMemberRole($settings->getContainer());
			
		}
		return true;
	}

} // END class.RbacSystem
?>
