<?php

/**
 * Role based access control service
 *
 * Verifies user access based on 3-layer roles definitions.
 * Implements Singleton pattern.
 *
 * @version DR1.6
 */
class RoleBasedAccessControlService {



	protected static $dbAdapter;
	protected static $schema = '';
	protected static $prefix = 'rbacs_';
	protected static $instance;
	protected static $cache;



	/**
	 * Creates instance of access control service
	 */
	protected function __construct() {
		self::$cache = new ArrayObject();
	}



	/**
	 * Returns singleton instance
	 */
	public static function getInstance() {
		if (!self::$instance) {
			self::$instance = new RoleBasedAccessControlService();
		}
		return self::$instance;
	}



	protected static function checkInstance() {
		if (self::$instance) {
			throw new Exception('service cannot be reconfigured once it has been instantiated');
		}
	}



	/**
	 * Configures specific database schema to use
	 *
	 * @param string schema name
	 */
	public static function useSchema($schema) {
		self::checkInstance();
		self::$schema = trim($schema);
	}



	/**
	 * Configures prefix to be added to table names
	 *
	 * @param string table names prefix
	 */
	public static function setPrefix($prefix) {
		self::checkInstance();
		self::$prefix = trim($prefix);
	}



	/**
	 * Configures external database adapter
	 *
	 * @param DBAdapter
	 */
	public static function setDBAdapter(DBAdapter $dbAdapter) {
		self::checkInstance();
		self::$dbAdapter = $dbAdapter;
	}



	/**
	 * Verifies whether user is authorized to perform action or not
	 *
	 * Authorization is granted when action is listed in activities required by roles assigned to user id.
	 * Additionaly authorization succeeds when action is listed in activities marked as public.
	 *
	 * @return boolean
	 */
	public function isAuthorized($action, $userId = null) {
		if (array_key_exists($userId, self::$cache) and array_key_exists($action, self::$cache[$userId])) {
			return self::$cache[$userId][$action];
		}
		if (!self::$dbAdapter) {
			self::$dbAdapter = DBFactory::getAdapter();
		}

		$true = self::$dbAdapter->getDialect()->getTrue();
		$s = self::$schema ? self::$schema . '.' : '';
		$p = self::$prefix;
		if ($userId) {
			$sql = "
					SELECT a.id FROM {$s}{$p}actions AS a
					LEFT JOIN {$s}{$p}activities_to_actions AS ata ON ata.action_id = a.id
					LEFT JOIN {$s}{$p}activities AS actv ON actv.id = ata.activity_id
					WHERE action = '$action' AND (
						actv.is_public = $true OR ata.activity_id IN (
							SELECT activity_id FROM {$s}{$p}roles_to_activities AS rta
							LEFT JOIN {$s}{$p}users_to_roles AS utr ON utr.role_id = rta.role_id
							WHERE utr.user_id = $userId
						)
					)
			";
		} else {
			$sql = "
					SELECT a.id FROM {$s}{$p}actions AS a
					LEFT JOIN {$s}{$p}activities_to_actions AS ata ON ata.action_id = a.id
					LEFT JOIN {$s}{$p}activities AS actv ON actv.id = ata.activity_id
					WHERE action = '$action' AND actv.is_public = $true
			";
		}
		self::$dbAdapter->execute($sql);
		$out = self::$dbAdapter->getIterator()->valid();
		if (!array_key_exists($userId, self::$cache)) {
			self::$cache[$userId] = new ArrayObject();
		}
		self::$cache[$userId][$action] = $out;
		return $out;
	}



} // class RoleBasedAccessControlService

?>