Commits

Christoffer Niska committed 437267d

Imported from svn by Bitbucket

  • Participants

Comments (0)

Files changed (76)

File RightsModule.php

+<?php
+/**
+* Rights module class file.
+*
+* @author Christoffer Niska <cniska@live.com>
+* @copyright Copyright &copy; 2010 Christoffer Niska
+* @version 1.3.0
+* 
+* DO NOT CHANGE THE DEFAULT CONFIGURATION VALUES!
+* 
+* You may overload the module configuration values in your rights-module 
+* configuration like so:
+* 
+* 'modules'=>array(
+*     'rights'=>array(
+*         'userNameColumn'=>'name',
+*         'flashSuccessKey'=>'success',
+*         'flashErrorKey'=>'error',
+*     ),
+* ),
+*/
+class RightsModule extends CWebModule
+{
+	/**
+	* @property string the name of the role with superuser privileges.
+	*/
+	public $superuserName = 'Admin';
+	/**
+	* @property string the name of the guest role.
+	*/
+	public $authenticatedName = 'Authenticated';
+	/**
+	* @property string the name of the user model class.
+	*/
+	public $userClass = 'User';
+	/**
+	* @property string the name of the id column in the user table.
+	*/
+	public $userIdColumn = 'id';
+	/**
+	* @property string the name of the username column in the user table.
+	*/
+	public $userNameColumn = 'username';
+	/**
+	* @property boolean whether to enable business rules.
+	*/
+	public $enableBizRule = true;
+	/**
+	* @property boolean whether to enable data for business rules.
+	*/
+	public $enableBizRuleData = false;
+	/**
+	* @property boolean whether to display authorization items description 
+	* instead of name it is set.
+	*/
+	public $displayDescription = true;
+	/**
+	* @property string the flash message key to use for success messages.
+	*/
+	public $flashSuccessKey = 'RightsSuccess';
+	/**
+	* @property string the flash message key to use for error messages.
+	*/
+	public $flashErrorKey = 'RightsError';
+	/**
+	* @property boolean whether to install rights when accessed.
+	*/
+	public $install = false;
+	/**
+	* @property string the base url to Rights. Override when module is nested.
+	*/
+	public $baseUrl = '/rights';
+	/**
+	* @property string the path to the layout file to use for displaying Rights.
+	*/
+	public $layout = 'rights.views.layouts.main';
+	/**
+	* @property string the path to the application layout file.
+	*/
+	public $appLayout = 'application.views.layouts.main';
+	/**
+	* @property string the style sheet file to use for Rights.
+	*/
+	public $cssFile;
+	/**
+	* @property boolean whether to enable debug mode.
+	*/
+	public $debug = false;
+
+	private $_assetsUrl;
+
+	/**
+	* Initializes the "rights" module.
+	*/
+	public function init()
+	{
+		// Set required classes for import.
+		$this->setImport(array(
+			'rights.components.*',
+			'rights.components.behaviors.*',
+			'rights.components.dataproviders.*',
+			'rights.controllers.*',
+			'rights.models.*',
+		));
+
+		// Set the required components.
+		$this->setComponents(array(
+			'authorizer'=>array(
+				'class'=>'RAuthorizer',
+				'superuserName'=>$this->superuserName,
+			),
+			'generator'=>array(
+				'class'=>'RGenerator',
+			),
+		));
+
+		// Normally the default controller is Assignment.
+		$this->defaultController = 'assignment';
+
+		// Set the installer if necessary.
+		if( $this->install===true )
+		{
+			$this->setComponents(array(
+				'installer'=>array(
+					'class'=>'RInstaller',
+					'superuserName'=>$this->superuserName,
+					'authenticatedName'=>$this->authenticatedName,
+					'guestName'=>Yii::app()->user->guestName,
+					'defaultRoles'=>Yii::app()->authManager->defaultRoles,
+				),
+			));
+
+			// When installing we need to set the default controller to Install.
+			$this->defaultController = 'install';
+		}
+	}
+
+	/**
+	* Registers the necessary scripts.
+	*/
+	public function registerScripts()
+	{
+		// Get the url to the module assets
+		$assetsUrl = $this->getAssetsUrl();
+
+		// Register the necessary scripts
+		$cs = Yii::app()->getClientScript();
+		$cs->registerCoreScript('jquery');
+		$cs->registerCoreScript('jquery.ui');
+		$cs->registerScriptFile($assetsUrl.'/js/rights.js');
+		$cs->registerCssFile($assetsUrl.'/css/core.css');
+
+		// Make sure we want to register a style sheet.
+		if( $this->cssFile!==false )
+		{
+			// Default style sheet is used unless one is provided.
+			if( $this->cssFile===null )
+				$this->cssFile = $assetsUrl.'/css/default.css';
+			else
+				$this->cssFile = Yii::app()->request->baseUrl.$this->cssFile;
+
+			// Register the style sheet
+			$cs->registerCssFile($this->cssFile);
+		}
+	}
+
+	/**
+	* Publishes the module assets path.
+	* @return string the base URL that contains all published asset files of Rights.
+	*/
+	public function getAssetsUrl()
+	{
+		if( $this->_assetsUrl===null )
+		{
+			$assetsPath = Yii::getPathOfAlias('rights.assets');
+
+			// We need to republish the assets if debug mode is enabled.
+			if( $this->debug===true )
+				$this->_assetsUrl = Yii::app()->getAssetManager()->publish($assetsPath, false, -1, true);
+			else
+				$this->_assetsUrl = Yii::app()->getAssetManager()->publish($assetsPath);
+		}
+
+		return $this->_assetsUrl;
+	}
+
+	/**
+	* @return RightsAuthorizer the authorizer component.
+	*/
+	public function getAuthorizer()
+	{
+		return $this->getComponent('authorizer');
+	}
+
+	/**
+	* @return RightsInstaller the installer component.
+	*/
+	public function getInstaller()
+	{
+		return $this->getComponent('installer');
+	}
+
+	/**
+	* @return RightsGenerator the generator component.
+	*/
+	public function getGenerator()
+	{
+		return $this->getComponent('generator');
+	}
+
+	/**
+	* @return the current version.
+	*/
+	public function getVersion()
+	{
+		return '1.3.0';
+	}
+}

File assets/css/core.css

+/**
+* Rights module core style sheet file.
+*
+* @author Christoffer Niska
+* @copyright Copyright &copy; 2008 Christoffer Niska
+* @since 0.9.11
+*/
+
+#rights .form .text-field { width:320px; }
+
+#rights #updateAuthItem .parents,
+#rights #updateAuthItem .children { margin-bottom:30px; }
+
+#rights .type-column { width:100px; }
+#rights .actions-column { width:80px; text-align:right; }
+
+#rights .parent-table,
+#rights .child-table,
+#rights .user-assignment-table { width:420px; }
+
+#rights .assignment-table td { vertical-align:top; }
+#rights .assignment-table .role-column,
+#rights .assignment-table .task-column,
+#rights .assignment-table .operation-column { width:25%; }
+#rights .permission-table .inherited-item { cursor:pointer; }
+
+/**
+* Flash message style.
+*/
+
+#rights .flashes {
+	width:910px;
+	height:40px;
+	position:absolute;
+	top:60px;
+	left:20px;
+}
+#rights .flash {
+	margin:0;
+	padding:8px 12px;
+	font-size:12px;
+	font-style:italic;
+	-moz-border-radius:6px;
+	-webkit-border-radius:6px;
+}
+#rights .flash.success { color:#007700; background-color:#e0f8dd; }
+#rights .flash.error { color:#7a0000; background-color:#fdd9d9; }
+
+/**
+* Installer style.
+*/
+
+#rights #installer { margin:30px 0; text-align:center; }
+
+#rights .green-text { color:#00aa00; }
+#rights .red-text { color:#ff0000; }
+
+/**
+* Generator style.
+*/
+
+#rights #generator table.items { margin:0; }
+#rights #generator table.items th,
+#rights #generator table.items td { padding:10px; }
+#rights #generator table.items td { cursor:pointer; }
+#rights #generator table.items .module-row { background-color:#bdc1d1; }
+#rights #generator table.items .controller-row { background-color:#d3d7e8; }
+#rights #generator table.items .action-row .name-column { padding-left:20px; }
+#rights #generator table.items .checkbox-column { width:20px; text-align:center; }
+#rights #generator table.items .checkbox-column input { margin:2px 0; }
+#rights #generator table.items .path-column { color:#808080; font-style:italic; }
+#rights #generator table.items .exists * { color:#a0a0a0; }
+#rights #generator table.items .odd td { background:#e8edff; }
+#rights #generator table.items .even td { background:transparent; }
+
+/**
+* Sortable style.
+*/
+
+#rights .sortable-table .sortable-placeholder { height:38px; background-color:#000000; }
+
+/**
+* Tooltip style.
+*/
+
+#rightsTooltip {
+	max-width:500px;
+	padding:8px;
+    position:absolute;
+	background:url('../images/bg_tooltip.gif') 100% 100% repeat-x #ffffff;
+    border:1px solid #c0c0c0;
+    font-family:Consolas, Arial, san-serif !important;
+    font-size:11px;
+    -moz-border-radius:6px;
+    -webkit-border-radius:6px;
+}
+#rightsTooltip .heading { font-weight:bold; }
+#rightsTooltip .content { color:#808080; }

File assets/css/default.css

+/**
+* Rights module default style sheet file.
+* You may override this style sheet by setting the 'cssFile'
+* in the module's configuration.
+*
+* @author Christoffer Niska
+* @copyright Copyright &copy; 2008 Christoffer Niska
+* @since 0.5
+*/
+
+/**
+* Default style.
+*/
+
+#rights * { font-family:Trebuchet MS, Verdana, san-serif !important; }
+#rights a { color:#666699; font-weight:bold; text-decoration:none; }
+#rights a:hover { color:#666699; text-decoration:underline; }
+#rights hr { height:0; border-style:none; border-bottom:1px dashed #f0f0f0; }
+
+#rights #content { position:relative; }
+
+#rights #menu { margin:0 0 25px 0; }
+#rights #menu ul.actions { margin:0; padding:0; list-style-type:none; }
+#rights #menu ul.actions li { display:inline; }
+#rights #menu ul.actions a {
+	color:#ffffff;
+	font-size:12px;
+	text-decoration:none;
+	padding:8px 12px;
+	background:#666699;
+	-moz-border-radius:6px;
+	-webkit-border-radius:6px;
+}
+#rights #menu ul.actions a:hover { color:#ffffff; text-decoration:none; }
+
+#rights .form label {
+	display:block;
+	margin:0 0 8px 0;
+	font-weight:bold;
+	font-size:12px; }
+#rights .form input[type=text],
+#rights .form input[type=password] {
+	padding:5px;
+	background:url('../images/bg_formInputText.gif') repeat-x;
+	border:1px solid #c0c0c0;
+	-moz-border-radius:6px;
+	-webkit-border-radius:6px;
+}
+
+#rights .form input { outline:none !important; }
+#rights .form input[type=submit] { padding:2px 5px; }
+#rights .form span.required { color:#ff0000; }
+
+/**
+* Grid view and table styles.
+*/
+
+#rights .grid-view table.items { border:0; margin:0 0 20px 0; }
+#rights .grid-view table.items * { font-size:12px; }
+#rights .grid-view table.items thead th {
+	background:transparent;
+	color:#404040;
+	font-weight:bold;
+	font-size:13px;
+	text-align:left;
+}
+#rights .grid-view table.items thead th { background:#ffffff; }
+#rights .grid-view table.items th,
+#rights .grid-view table.items td { padding:10px; border-style:none; }
+#rights .grid-view table.items .even { background:transparent; }
+#rights .grid-view table.items .odd { background:#e8edff; }
+
+#rights .grid-view table.mini { border:0; }
+#rights .grid-view table.mini * { font-size:11px; }
+#rights .grid-view table.mini th, #rights .miniTable td { padding:8px; }
+#rights .grid-view table.mini .odd td { background:#e8edff; }
+
+/**
+* Pager style.
+*/
+
+#rights .pager { color:#ffffff; }
+#rights .pager li { margin:0; padding:0; }
+#rights .pager li a { padding:5px 10px; border-style:none; text-decoration:none; }
+#rights .pager li.hidden a { color:#c0c0c0; }
+#rights .pager li.selected a { 
+	background:#666699;
+	color:#ffffff;
+	-moz-border-radius:6px;
+	-webkit-border-radius:6px;
+}
+
+#rights .bizrule-column,
+#rights .data-column {
+	color:#808080;
+	font-family:Consolas, Arial, san-serif !important;
+	font-size:11px;
+}
+
+#rights .hover td { background-color:#ffffdd !important; }
+
+#rights .info { color:#808080; font-size:12px; font-style:italic; }
+#rights .hint { width:320px; color:#808080; font-size:11px; font-style:italic; }
+
+#rights .permission-table .inherited-item { cursor:pointer; font-style:italic; }
+#rights .child-count { font-weight:bold; }
+
+/**
+* Jquery UI style.
+*/
+
+.ui-widget * { font-family:Trebuchet MS, Verdana, san-serif !important; font-size:11px; }

File assets/images/bg_formInputText.gif

Added
New image

File assets/images/bg_menuButton.gif

Added
New image

File assets/images/bg_tooltip.gif

Added
New image

File assets/images/logo.png

Added
New image

File assets/images/logo_small.png

Added
New image

File assets/js/rights.js

+/**
+* Anonymous function that is immediately called
+* for making sure that we can use the $-shortcut for jQuery.
+*/
+(function($) {
+
+	/**
+	* Rights tooltip plugin.
+	* @param Object options Plugin options
+	* @return the jQuery element
+	*/
+	$.fn.rightsTooltip = function(options) {
+
+		// Default values
+		var defaults = {
+			title: ''
+		};
+
+		// Merge the options with the defaults
+		var settings = $.extend(defaults, options);
+
+		// Run this for each selected element
+		return this.each(function() {
+
+			var $this = $(this);
+			var title = this.title;
+			var $tooltip;
+
+			// Make sure the item has a title
+			if( $this.attr('title').length>0 ) {
+
+				// Empty the title
+                this.title = '';
+
+                // Actions to be taken when hovering
+                $this.hover(function(e) {
+
+                	// Build the tooltip and append it to the body
+					$tooltip = $('<div id="rightsTooltip" />')
+					.appendTo('body')
+					.hide();
+
+					// Check if we have a title
+					if( settings.title.length>0 ) {
+						// If so, append it to the tooltip
+						$('<div class="heading" />')
+						.appendTo($tooltip)
+						.html(settings.title);
+					}
+
+					// Append the content to the tooltip
+					$('<div class="content" />')
+					.appendTo($tooltip)
+					.html(title);
+
+					// Set the tooltip position and fade it in
+					$tooltip.css({
+						top: e.pageY+10,
+						left: e.pageX+20
+					})
+					.fadeIn(350);
+                }, function() {
+
+                	// Remove the tooltip
+                    $tooltip.remove();
+                });
+
+                // Bind a mouse move function
+                $this.mousemove(function(e) {
+
+                	// Move the tooltip relative to the mouse
+	                $tooltip.css({
+	                    top: e.pageY+10,
+	                    left: e.pageX+20
+	                });
+            	});
+            }
+		});
+	};
+
+	/**
+	* Rights sortable table plugin that uses of jui-sortable.
+	* @param Object options Plugin options
+	* @return the jQuery element
+	*/
+	$.fn.rightsSortableTable = function(options) {
+
+		// Default settings
+		var defaults = {
+			handle: '',
+			placeholder: 'sortable-placeholder',
+			csrfToken: ''
+		};
+
+		// Merge the options with the defaults
+		var settings = $.extend(defaults, options);
+
+		// Run this for each selected element
+		return this.each(function() {
+
+			var $this = $(this);
+			var $tbody = $this.find('tbody');
+
+			// Apply the id for sorting to the table rows
+			// (id can be found hidden in the name column).
+			$tbody.children().each(function() {
+				$(this).attr('id', $(this).find('.auth-item-name').html());
+			});
+
+			// Apply jui sortable on the element
+			$tbody.sortable({
+				axis: 'y',
+				containment: 'parent',
+				cursor: 'pointer',
+				delay: 100,
+				distance: 5,
+				forceHelperSize: true,
+				forcePlaceholderSize: true,
+				tolerance: 'pointer',
+				handle: settings.handle,
+				placeholder: settings.placeholder,
+				// Helper function to set correct column widths while dragging
+				helper: function(e, tr) {
+					var $helper = tr.clone();
+					$helper.children().each(function(index) {
+						$(this).width(tr.children().eq(index).width());
+					});
+					return $helper;
+				},
+				// Actions to be taken when the row is dropped
+				update: function(e, ui) {
+					// Run an Ajax request to save the new weights
+					$.post(settings.url, {
+						result: $tbody.sortable('toArray'),
+						YII_CSRF_TOKEN: settings.csrfToken
+					});
+				},
+				// Actions to be taken when sorting is stopped
+				stop: function(e, ui) {
+					// Update the row classes
+					$tbody.children().each(function(index) {
+						index%2===0 ? $(this).removeClass('even').addClass('odd') : $(this).removeClass('odd').addClass('even');
+					});
+				}
+			})
+			.disableSelection();
+		});
+	};
+
+	/**
+	* Rights select table rows plugin.
+	* @param Object options Plugin options
+	* @return the jQuery element
+	*/
+	$.fn.rightsSelectRows = function(options) {
+
+		// Default settings
+		var defaults = {
+
+		};
+
+		// Merge the options with the defaults
+		var settings = $.extend(defaults, options);
+
+		return this.each(function() {
+
+			var $this = $(this);
+
+			$this.find('tr')
+			.filter(':has(:checkbox:checked)')
+			.addClass('selected')
+			.end()
+			.click(function(e) {
+				if( e.target.type!=='checkbox' ) {
+					$(':checkbox', this).trigger('click');
+				}
+			})
+			.find(':checkbox')
+			.click(function(event) {
+				$(this).parents('tr:first').toggleClass('selected');
+			});
+
+			$this.disableSelection();
+		});
+	};
+
+	/**
+	* Actions to be taken when the document is loaded.
+	*/
+	$(document).ready(function() {
+
+		/**
+		* Hover functionality for rights' tables.
+		*/
+		$('#rights tbody tr').hover(function() {
+			$(this).addClass('hover'); // On mouse over
+		}, function() {
+			$(this).removeClass('hover'); // On mouse out
+		});
+
+		/**
+		* Fade effect for flash messages.
+		*/
+   		$('#rights .flash').animate({ opacity: 1.0 }, { duration: 3000 })
+   		.fadeOut(650);
+
+	});
+
+})(jQuery);

File components/RAuthorizer.php

+<?php
+/**
+* Rights authorizer component class file.
+*
+* @author Christoffer Niska <cniska@live.com>
+* @copyright Copyright &copy; 2010 Christoffer Niska
+* @since 0.5
+*/
+class RAuthorizer extends CApplicationComponent
+{
+	/**
+	* @property string the name of the superuser role.
+	*/
+	public $superuserName;
+	/**
+	 * @property RDbAuthManager the authorization manager.
+	 */
+	private $_authManager;
+
+	/**
+	* Initializes the authorizer.
+	*/
+	public function init()
+	{
+		parent::init();
+
+		$this->_authManager = Yii::app()->getAuthManager();
+	}
+
+	/**
+	* Returns the a list of all roles.
+	* @param boolean $includeSuperuser whether to include the superuser.
+	* @param boolean $sort whether to sort the items by their weights.
+	* @return the roles.
+	*/
+	public function getRoles($includeSuperuser=true, $sort=true)
+	{
+		$exclude = $includeSuperuser===false ? array($this->superuserName) : array();
+	 	$roles = $this->getAuthItems(CAuthItem::TYPE_ROLE, null, null, $sort, $exclude);
+	 	$roles = $this->attachAuthItemBehavior($roles);
+	 	return $roles;
+	}
+
+	/**
+	* Creates an authorization item.
+	* @param string $name the item name. This must be a unique identifier.
+	* @param integer $type the item type (0: operation, 1: task, 2: role).
+	* @param string $description the description for the item.
+	* @param string $bizRule business rule associated with the item. This is a piece of
+	* PHP code that will be executed when {@link checkAccess} is called for the item.
+	* @param mixed $data additional data associated with the item.
+	* @return CAuthItem the authorization item
+	*/
+	public function createAuthItem($name, $type, $description='', $bizRule=null, $data=null)
+	{
+		$bizRule = $bizRule!=='' ? $bizRule : null;
+
+		if( $data!==null )
+			$data = $data!=='' ? $this->sanitizeExpression($data.';') : null;
+
+		return $this->_authManager->createAuthItem($name, $type, $description, $bizRule, $data);
+	}
+
+	/**
+	* Updates an authorization item.
+	* @param string $oldName the item name. This must be a unique identifier.
+	* @param integer $name the item type (0: operation, 1: task, 2: role).
+	* @param string $description the description for the item.
+	* @param string $bizRule business rule associated with the item. This is a piece of
+	* PHP code that will be executed when {@link checkAccess} is called for the item.
+	* @param mixed $data additional data associated with the item.
+	*/
+	public function updateAuthItem($oldName, $name, $description='', $bizRule=null, $data=null)
+	{
+		$authItem = $this->_authManager->getAuthItem($oldName);
+		$authItem->name = $name;
+		$authItem->description = $description!=='' ? $description : null;
+		$authItem->bizRule = $bizRule!=='' ? $bizRule : null;
+
+		// Make sure that data is not already serialized.
+		if( @unserialize($data)===false )
+			$authItem->data = $data!=='' ? $this->sanitizeExpression($data.';') : null;
+
+		$this->_authManager->saveAuthItem($authItem, $oldName);
+	}
+
+	/**
+	 * Returns the authorization items of the specific type and user.
+	 * @param mixed $types the item type (0: operation, 1: task, 2: role). Defaults to null,
+	 * meaning returning all items regardless of their type.
+	 * @param mixed $userId the user ID. Defaults to null, meaning returning all items even if
+	 * they are not assigned to a user.
+	 * @param CAuthItem $parent the item for which to get the select options.
+	 * @param boolean $sort sort items by to weights.
+	 * @param array $exclude the items to be excluded.
+	 * @return array the authorization items of the specific type.
+	 */
+	public function getAuthItems($types=null, $userId=null, CAuthItem $parent=null, $sort=true, $exclude=array())
+	{
+		// We have none or a single type.
+		if( $types!==(array)$types )
+		{
+			$items = $this->_authManager->getAuthItems($types, $userId, $sort);
+		}
+		// We have multiple types.
+		else
+		{
+			$typeItemList = array();
+			foreach( $types as $type )
+				$typeItemList[ $type ] = $this->_authManager->getAuthItems($type, $userId, $sort);
+
+			// Merge the authorization items preserving the keys.
+			$items = array();
+			foreach( $typeItemList as $typeItems )
+				$items = $this->mergeAuthItems($items, $typeItems);
+		}
+
+		$items = $this->excludeInvalidAuthItems($items, $parent, $exclude);
+		$items = $this->attachAuthItemBehavior($items, $userId, $parent);
+
+		return $items;
+	}
+
+	/**
+	* Merges two arrays with authorization items preserving the keys.
+	* @param array $array1 the items to merge to.
+	* @param array $array2 the items to merge from.
+	* @return array the merged items.
+	*/
+	protected function mergeAuthItems($array1, $array2)
+	{
+		foreach( $array2 as $itemName=>$item )
+			if( isset($array1[ $itemName ])===false )
+				$array1[ $itemName ] = $item;
+
+		return $array1;
+	}
+
+	/**
+	* Excludes invalid authorization items.
+	* When an item is provided its parents and children are excluded aswell.
+	* @param array $items the authorization items to process.
+	* @param CAuthItem $parent the item to check valid authorization items for.
+	* @param array $exclude additional items to be excluded.
+	* @return array valid authorization items.
+	*/
+	protected function excludeInvalidAuthItems($items, CAuthItem $parent=null, $exclude=array())
+	{
+		// We are getting authorization items valid for a certain item
+		// exclude its parents and children aswell.
+		if( $parent!==null )
+		{
+		 	$exclude[] = $parent->name;
+		 	foreach( $parent->getChildren() as $childName=>$child )
+		 		$exclude[] = $childName;
+
+		 	// Exclude the parents recursively to avoid inheritance loops.
+		 	$parentNames = array_keys($this->getAuthItemParents($parent->name));
+		 	$exclude = array_merge($parentNames, $exclude);
+		}
+
+		// Unset the items that are supposed to be excluded.
+		foreach( $exclude as $itemName )
+			if( isset($items[ $itemName ]) )
+				unset($items[ $itemName ]);
+
+		return $items;
+	}
+
+	/**
+	* Returns the parents of the specified authorization item.
+	* @param mixed $item the item name for which to get its parents.
+	* @param integer $type the item type (0: operation, 1: task, 2: role). Defaults to null,
+	* meaning returning all items regardless of their type.
+	* @param string $parentName the name of the item in which permissions to search.
+	* @param boolean $direct whether we want the specified items parent or all parents.
+	* @return array the names of the parent items.
+	*/
+	public function getAuthItemParents($item, $type=null, $parentName=null, $direct=false)
+	{
+		if( ($item instanceof CAuthItem)===false )
+			$item = $this->_authManager->getAuthItem($item);
+
+		$permissions = $this->getPermissions($parentName);
+		$parentNames = $this->getAuthItemParentsRecursive($item->name, $permissions, $direct);
+		$parents = $this->_authManager->getAuthItemsByNames($parentNames);
+		$parents = $this->attachAuthItemBehavior($parents, null, $item);
+
+		if( $type!==null )
+			foreach( $parents as $parentName=>$parent )
+				if( (int)$parent->type!==$type )
+					unset($parents[ $parentName ]);
+
+		return $parents;
+	}
+
+	/**
+	* Returns the parents of the specified authorization item recursively.
+	* @param string $itemName the item name for which to get its parents.
+	* @param array $items the items to process.
+	* @param boolean $direct whether we want the specified items parent or all parents.
+	* @return the names of the parents items recursively.
+	*/
+	private function getAuthItemParentsRecursive($itemName, $items, $direct)
+	{
+		$parents = array();
+		foreach( $items as $childName=>$children )
+		{
+		 	if( $children!==array() )
+		 	{
+		 		if( isset($children[ $itemName ]) )
+		 		{
+		 			if( isset($parents[ $childName ])===false )
+		 				$parents[ $childName ] = $childName;
+				}
+				else
+				{
+		 			if( ($p = $this->getAuthItemParentsRecursive($itemName, $children, $direct))!==array() )
+		 			{
+		 				if( $direct===false && isset($parents[ $childName ])===false )
+		 					$parents[ $childName ] = $childName;
+
+		 				$parents = array_merge($parents, $p);
+					}
+				}
+			}
+		}
+
+		return $parents;
+	}
+
+	/**
+	* Returns the children for the specified authorization item recursively.
+	* @param mixed $item the item for which to get its children.
+	* @param integer $type the item type (0: operation, 1: task, 2: role). Defaults to null,
+	* meaning returning all items regardless of their type.
+	* @return array the names of the item's children.
+	*/
+	public function getAuthItemChildren($item, $type=null)
+	{
+		if( ($item instanceof CAuthItem)===false )
+			$item = $this->_authManager->getAuthItem($item);
+
+		$childrenNames = array();
+		foreach( $item->getChildren() as $childName=>$child )
+			if( $type===null || (int)$child->type===$type )
+				$childrenNames[] = $childName;
+
+		$children = $this->_authManager->getAuthItemsByNames($childrenNames);
+		$children = $this->attachAuthItemBehavior($children, null, $item);
+
+		return $children;
+	}
+
+	/**
+	* Attaches the rights authorization item behavior to the given item.
+	* @param mixed $items the item or items to which attach the behavior.
+	* @param int $userId the ID of the user to which the item is assigned.
+	* @param CAuthItem $parent the parent of the given item.
+	* @return mixed the item or items with the behavior attached.
+	*/
+	public function attachAuthItemBehavior($items, $userId=null, CAuthItem $parent=null)
+	{
+		// We have a single item.
+		if( $items instanceof CAuthItem )
+		{
+			$items->attachBehavior('rights', new RAuthItemBehavior($userId, $parent));
+		}
+		// We have multiple items.
+		else if( $items===(array)$items )
+		{
+			foreach( $items as $item )
+				$item->attachBehavior('rights', new RAuthItemBehavior($userId, $parent));
+		}
+
+		return $items;
+	}
+
+	/**
+	* Returns the users with superuser privileges.
+	* @return the superusers.
+	*/
+	public function getSuperusers()
+	{
+		$assignments = $this->_authManager->getAssignmentsByItemName( Rights::module()->superuserName );
+
+		$userIdList = array();
+		foreach( $assignments as $userId=>$assignment )
+			$userIdList[] = $userId;
+
+		$criteria = new CDbCriteria();
+		$criteria->addInCondition(Rights::module()->userIdColumn, $userIdList);
+
+		$userClass = Rights::module()->userClass;
+		$users = CActiveRecord::model($userClass)->findAll($criteria);
+		$users = $this->attachUserBehavior($users);
+
+		$superusers = array();
+		foreach( $users as $user )
+			$superusers[] = $user->name;
+
+		// Make sure that we have superusers, otherwise we would allow full access to Rights
+		// if there for some reason is not any superusers.
+		if( $superusers===array() )
+			throw new CHttpException(403, Rights::t('core', 'There must be at least one superuser!'));
+
+		return $superusers;
+	}
+
+	/**
+	* Attaches the rights user behavior to the given users.
+	* @param mixed $users the user or users to which attach the behavior.
+	* @return mixed the user or users with the behavior attached.
+	*/
+	public function attachUserBehavior($users)
+	{
+		$userClass = Rights::module()->userClass;
+
+		// We have a single user.
+		if( $users instanceof $userClass )
+		{
+			$users->attachBehavior('rights', new RUserBehavior);
+		}
+		// We have multiple user.
+		else if( $users===(array)$users )
+		{
+			foreach( $users as $user )
+				$user->attachBehavior('rights', new RUserBehavior);
+		}
+
+		return $users;
+	}
+
+	/**
+	* Returns whether the user is a superuser.
+	* @param integer $userId the id of the user to do the check for.
+	* @return boolean whether the user is a superuser.
+	*/
+	public function isSuperuser($userId)
+	{
+		$assignments = $this->_authManager->getAuthAssignments($userId);
+		return isset($assignments[ $this->superuserName ]);
+	}
+
+	/**
+	* Returns the permissions for a specific authorization item.
+	* @param string $itemName the name of the item for which to get permissions. Defaults to null,
+	* meaning that the full permission tree is returned.
+	* @return the permission tree.
+	*/
+	public function getPermissions($itemName=null)
+	{
+		$permissions = array();
+
+		if( $itemName!==null )
+		{
+			$item = $this->_authManager->getAuthItem($itemName);
+			$permissions = $this->getPermissionsRecursive($item);
+		}
+		else
+		{
+			foreach( $this->getRoles() as $roleName=>$role )
+				$permissions[ $roleName ] = $this->getPermissionsRecursive($role);
+		}
+
+		return $permissions;
+	}
+
+	/**
+	* Returns the permissions for a specific authorization item recursively.
+	* @param CAuthItem $item the item for which to get permissions.
+	* @return array the section of the permissions tree.
+	*/
+	private function getPermissionsRecursive(CAuthItem $item)
+	{
+		$permissions = array();
+	 	foreach( $item->getChildren() as $childName=>$child )
+	 	{
+	 		$permissions[ $childName ] = array();
+	 		if( ($grandChildren = $this->getPermissionsRecursive($child))!==array() )
+				$permissions[ $childName ] = $grandChildren;
+		}
+
+		return $permissions;
+	}
+
+	/**
+	* Returns the permission type for an authorization item.
+	* @param string $itemName the name of the item to check permission for.
+	* @param string $parentName the name of the item in which permissions to look.
+	* @param array $permissions the permissions.
+	* @return integer the permission type (0: None, 1: Direct, 2: Inherited).
+	*/
+	public function hasPermission($itemName, $parentName=null, $permissions=array())
+	{
+		if( $parentName!==null )
+		{
+			if( $parentName===$this->superuserName )
+				return 1;
+
+			$permissions = $this->getPermissions($parentName);
+		}
+
+		if( isset($permissions[ $itemName ]) )
+			return 1;
+
+		foreach( $permissions as $children )
+			if( $children!==array() )
+				if( $this->hasPermission($itemName, null, $children)>0 )
+					return 2;
+
+		return 0;
+	}
+
+	/**
+	* Tries to sanitize code to make it safe for execution.
+	* @param string $code the code to be execute.
+	* @return mixed the return value of eval() or null if the code was unsafe to execute.
+	*/
+	protected function sanitizeExpression($code)
+	{
+		// Language consturcts.
+		$languageConstructs = array(
+			'echo',
+			'empty',
+			'isset',
+			'unset',
+			'exit',
+			'die',
+			'include',
+			'include_once',
+			'require',
+			'require_once',
+		);
+
+		// Loop through the language constructs.
+		foreach( $languageConstructs as $lc )
+			if( preg_match('/'.$lc.'\ *\(?\ *[\"\']+/', $code)>0 )
+				return null; // Language construct found, not safe for eval.
+
+		// Get a list of all defined functions
+		$definedFunctions = get_defined_functions();
+		$functions = array_merge($definedFunctions['internal'], $definedFunctions['user']);
+
+		// Loop through the functions and check the code for function calls.
+		// Append a '(' to the functions to avoid confusion between e.g. array() and array_merge().
+		foreach( $functions as $f )
+			if( preg_match('/'.$f.'\ *\({1}/', $code)>0 )
+				return null; // Function call found, not safe for eval.
+
+		// Evaluate the safer code
+		$result = @eval($code);
+
+		// Return the evaluated code or null if the result was false.
+		return $result!==false ? $result : null;
+	}
+
+	/**
+	* @return RAuthManager the authorization manager.
+	*/
+	public function getAuthManager()
+	{
+		return $this->_authManager;
+	}
+}

File components/RController.php

+<?php
+/**
+* Rights base controller class file.
+*
+* @author Christoffer Niska <cniska@live.com>
+* @copyright Copyright &copy; 2010 Christoffer Niska
+* @since 0.6
+*/
+class RController extends CController
+{
+	/**
+	* @property string the default layout for the controller view. Defaults to '//layouts/column1',
+	* meaning using a single column layout. See 'protected/views/layouts/column1.php'.
+	*/
+	public $layout='//layouts/column1';
+	/**
+	* @property array context menu items. This property will be assigned to {@link CMenu::items}.
+	*/
+	public $menu=array();
+	/**
+	* @property array the breadcrumbs of the current page. The value of this property will
+	* be assigned to {@link CBreadcrumbs::links}. Please refer to {@link CBreadcrumbs::links}
+	* for more details on how to specify this property.
+	*/
+	public $breadcrumbs=array();
+
+	/**
+	* The filter method for 'rights' access filter.
+	* This filter is a wrapper of {@link CAccessControlFilter}.
+	* @param CFilterChain $filterChain the filter chain that the filter is on.
+	*/
+	public function filterRights($filterChain)
+	{
+		$filter = new RightsFilter;
+		$filter->allowedActions = $this->allowedActions();
+		$filter->filter($filterChain);
+	}
+
+	/**
+	* @return string the actions that are always allowed separated by commas.
+	*/
+	public function allowedActions()
+	{
+		return '';
+	}
+
+	/**
+	* Denies the access of the user.
+	* @param string $message the message to display to the user.
+	* This method may be invoked when access check fails.
+	* @throws CHttpException when called unless login is required.
+	*/
+	public function accessDenied($message=null)
+	{
+		if( $message===null )
+			$message = Rights::t('core', 'You are not authorized to perform this action.');
+
+		$user = Yii::app()->getUser();
+		if( $user->isGuest===true )
+			$user->loginRequired();
+		else
+			throw new CHttpException(403, $message);
+	}
+}

File components/RDbAuthManager.php

+<?php
+/**
+* Rights authorization manager class file.
+*
+* @author Christoffer Niska <cniska@live.com>
+* @copyright Copyright &copy; 2010 Christoffer Niska
+* @since 0.9.7
+*/
+class RDbAuthManager extends CDbAuthManager
+{
+	/**
+	 * @var string the name of the rights table.
+	 */
+	public $rightsTable = 'Rights';
+
+	private $_items = array();
+	private $_itemChildren = array();
+
+	/**
+	 * Adds an item as a child of another item.
+	 * Overloads the parent method to make sure that
+	 * we do not add already existing children.
+	 * @param string $itemName the item name.
+	 * @param string $childName the child item name.
+	 * @throws CException if either parent or child doesn't exist or if a loop has been detected.
+	 */
+	public function addItemChild($itemName, $childName)
+	{
+		// Make sure that the item doesn't already have this child.
+		if( $this->hasItemChild($itemName, $childName)===false )
+			return parent::addItemChild($itemName, $childName);
+	}
+
+	/**
+	* Assigns an authorization item to a user making sure that
+	* the user doesn't already have this assignment.
+	* Overloads the parent method to make sure that
+	* we do not assign already assigned items.
+	* @param string $itemName the item name.
+	* @param mixed $userId the user ID (see {@link IWebUser::getId})
+	* @param string $bizRule the business rule to be executed when {@link checkAccess} is called
+	* for this particular authorization item.
+	* @param mixed $data additional data associated with this assignment.
+	* @return CAuthAssignment the authorization assignment information.
+	* @throws CException if the item does not exist or if the item has already been assigned to the user.
+	*/
+	public function assign($itemName, $userId, $bizRule=null, $data=null)
+	{
+		// Make sure that this user doesn't already have this assignment.
+		if( $this->getAuthAssignment($itemName, $userId)===null )
+			return parent::assign($itemName, $userId, $bizRule, $data);
+	}
+
+	/**
+	* Returns the authorization item with the specified name.
+	* Overloads the parent method to allow for runtime caching.
+	* @param string $name the name of the item.
+	* @param boolean $allowCaching whether to accept cached data.
+	* @return CAuthItem the authorization item. Null if the item cannot be found.
+	*/
+	public function getAuthItem($name, $allowCaching=true)
+	{
+		// Get all items if necessary and cache them.
+		if( $allowCaching && $this->_items===array() )
+			$this->_items = $this->getAuthItems();
+
+		// Get the items from cache if possible.
+		if( $allowCaching && isset($this->_items[ $name ]) )
+		{
+			return $this->_items[ $name ];
+		}
+		// Attempt to get the item.
+		else if( ($item = parent::getAuthItem($name))!==null )
+		{
+			return $item;
+		}
+
+		// Item does not exist.
+		return null;
+	}
+
+
+	/**
+	* Returns the specified authorization items.
+	* @param array $names the names of the authorization items to get.
+	* @param boolean $nested whether to nest the items by type.
+	* @return array the authorization items.
+	*/
+	public function getAuthItemsByNames($names, $nested=false)
+	{
+		// Get all items if necessary and cache them.
+		if( $this->_items===array() )
+			$this->_items = $this->getAuthItems();
+
+		// Collect the items we want.
+		$items = array();
+		foreach( $this->_items as $name=>$item )
+		{
+			if( in_array($name, $names) )
+			{
+				if( $nested===true )
+					$items[ $item->getType() ][ $name ] = $item;
+				else
+					$items[ $name ] = $item;
+			}
+		}
+
+		return $items;
+	}
+
+	/**
+	* Returns the authorization items of the specific type and user.
+	* Overloads the parent method to allow for sorting.
+	* @param integer $type the item type (0: operation, 1: task, 2: role). Defaults to null,
+	* meaning returning all items regardless of their type.
+	* @param mixed $userId the user ID. Defaults to null, meaning returning all items even if
+	* they are not assigned to a user.
+	* @param boolean $sort whether to sort the items according to their weights.
+	* @return array the authorization items of the specific type.
+	*/
+	public function getAuthItems($type=null, $userId=null, $sort=true)
+	{
+		// We need to sort the items.
+		if( $sort===true )
+		{
+			if( $type===null && $userId===null )
+			{
+				$sql = "SELECT name,t1.type,description,t1.bizrule,t1.data,weight
+					FROM {$this->itemTable} t1
+					LEFT JOIN {$this->rightsTable} t2 ON name=itemname
+					ORDER BY t1.type DESC, weight ASC";
+				$command=$this->db->createCommand($sql);
+			}
+			else if( $userId===null )
+			{
+				$sql = "SELECT name,t1.type,description,t1.bizrule,t1.data,weight
+					FROM {$this->itemTable} t1
+					LEFT JOIN {$this->rightsTable} t2 ON name=itemname
+					WHERE t1.type=:type
+					ORDER BY t1.type DESC, weight ASC";
+				$command=$this->db->createCommand($sql);
+				$command->bindValue(':type', $type);
+			}
+			else if( $type===null )
+			{
+				$sql = "SELECT name,t1.type,description,t1.bizrule,t1.data,weight
+					FROM {$this->itemTable} t1
+					LEFT JOIN {$this->assignmentTable} t2 ON name=t2.itemname
+					LEFT JOIN {$this->rightsTable} t3 ON name=t3.itemname
+					WHERE userid=:userid
+					ORDER BY t1.type DESC, weight ASC";
+				$command=$this->db->createCommand($sql);
+				$command->bindValue(':userid', $userId);
+			}
+			else
+			{
+				$sql = "SELECT name,t1.type,description,t1.bizrule,t1.data,weight
+					FROM {$this->itemTable} t1
+					LEFT JOIN {$this->assignmentTable} t2 ON name=t2.itemname
+					LEFT JOIN {$this->rightsTable} t3 ON name=t3.itemname
+					WHERE t1.type=:type AND userid=:userid
+					ORDER BY t1.type DESC, weight ASC";
+				$command=$this->db->createCommand($sql);
+				$command->bindValue(':type', $type);
+				$command->bindValue(':userid', $userId);
+			}
+
+			$items = array();
+			foreach($command->queryAll() as $row)
+				$items[ $row['name'] ] = new CAuthItem($this, $row['name'], $row['type'], $row['description'], $row['bizrule'], unserialize($row['data']));
+		}
+		// No sorting required.
+		else
+		{
+			$items = parent::getAuthItems($type, $userId);
+		}
+
+		return $items;
+	}
+
+	/**
+	 * Returns the children of the specified item.
+	 * Overloads the parent method to allow for caching.
+	 * @param mixed $names the parent item name. This can be either a string or an array.
+	 * The latter represents a list of item names (available since version 1.0.5).
+	 * @param boolean $allowCaching whether to accept cached data.
+	 * @return array all child items of the parent
+	 */
+	public function getItemChildren($names, $allowCaching=true)
+	{
+		// Resolve the key for runtime caching.
+		$key = $names===(array)$names ? implode('|', $names) : $names;
+
+		// Get the children from cache if possible.
+		if( $allowCaching && isset($this->_itemChildren[ $key ])===true )
+		{
+			return $this->_itemChildren[ $key ];
+		}
+		// Children not cached or cached data is not accepted.
+		else
+		{
+			// We only have one name.
+			if( is_string($names) )
+			{
+				$condition = 'parent='.$this->db->quoteValue($names);
+			}
+			// We have multiple names.
+			else if( $names===(array)$names && $names!==array() )
+			{
+				foreach($names as &$name)
+					$name=$this->db->quoteValue($name);
+
+				$condition = 'parent IN ('.implode(', ', $names).')';
+			}
+			else
+			{
+				$condition = '1';
+			}
+
+			$sql = "SELECT name, type, description, bizrule, data
+				FROM {$this->itemTable}, {$this->itemChildTable}
+				WHERE {$condition} AND name=child";
+			$children = array();
+			foreach( $this->db->createCommand($sql)->queryAll() as $row )
+			{
+				if( ($data = @unserialize($row['data']))===false )
+					$data = null;
+
+				$children[ $row['name'] ] = new CAuthItem($this, $row['name'], $row['type'], $row['description'], $row['bizrule'], $data);
+			}
+
+			// Attach the authorization item behavior.
+			$children = Rights::getAuthorizer()->attachAuthItemBehavior($children);
+
+			// Cache the result.
+			return $this->_itemChildren[ $key ] = $children;
+		}
+	}
+
+	public function getAssignmentsByItemName($name)
+	{
+		$sql = "SELECT * FROM {$this->assignmentTable} WHERE itemname=:itemname";
+		$command = $this->db->createCommand($sql);
+		$command->bindValue(':itemname', $name);
+
+		$assignments=array();
+		foreach($command->queryAll($sql) as $row)
+		{
+			if(($data=@unserialize($row['data']))===false)
+				$data=null;
+
+			$assignments[ $row['userid'] ] = new CAuthAssignment($this, $row['itemname'], $row['userid'], $row['bizrule'], $data);
+		}
+
+		return $assignments;
+	}
+
+	/**
+	* Updates the authorization items weight.
+	* @param array $result the result returned from jui-sortable.
+	*/
+	public function updateItemWeight($result)
+	{
+		foreach( $result as $weight=>$itemname )
+		{
+			$sql = "SELECT COUNT(*) FROM {$this->rightsTable}
+				WHERE itemname=:itemname";
+			$command = $this->db->createCommand($sql);
+			$command->bindValue(':itemname', $itemname);
+
+			// Check if the item already has a weight.
+			if( $command->queryScalar()>0 )
+			{
+				$sql = "UPDATE {$this->rightsTable}
+					SET weight=:weight
+					WHERE itemname=:itemname";
+				$command = $this->db->createCommand($sql);
+				$command->bindValue(':weight', $weight);
+				$command->bindValue(':itemname', $itemname);
+				$command->execute();
+			}
+			// Item does not have a weight, insert it.
+			else
+			{
+				if( ($item = $this->getAuthItem($itemname))!==null )
+				{
+					$sql = "INSERT INTO {$this->rightsTable} (itemname, type, weight)
+						VALUES (:itemname, :type, :weight)";
+					$command = $this->db->createCommand($sql);
+					$command->bindValue(':itemname', $itemname);
+					$command->bindValue(':type', $item->getType());
+					$command->bindValue(':weight', $weight);
+					$command->execute();
+				}
+			}
+		}
+	}
+}

File components/RGenerator.php

+<?php
+/**
+* Rights generator component class file.
+*
+* @author Christoffer Niska <cniska@live.com>
+* @copyright Copyright &copy; 2010 Christoffer Niska
+* @since 0.9.8
+*/
+class RGenerator extends CApplicationComponent
+{
+	private $_authManager;
+	private $_items;
+
+	/**
+	* @property CDbConnection
+	*/
+	public $db;
+
+	/**
+	* Initializes the generator.
+	*/
+	public function init()
+	{
+		parent::init();
+
+		$this->_authManager = Yii::app()->getAuthManager();
+		$this->db = $this->_authManager->db;
+	}
+
+	/**
+	* Runs the generator.
+	* @return the items generated or false if failed.
+	*/
+	public function run()
+	{
+		$authManager = $this->_authManager;
+		$itemTable = $authManager->itemTable;
+
+		// Start transaction
+		$txn = $this->db->beginTransaction();
+
+		try
+		{
+			$generatedItems = array();
+
+			// Loop through each type of items
+			foreach( $this->_items as $type=>$items )
+			{
+				// Loop through items
+				foreach( $items as $name )
+				{
+					// Make sure the item does not already exist
+					if( $authManager->getAuthItem($name)===null )
+					{
+						// Insert item
+						$sql = "INSERT INTO {$itemTable} (name, type, data)
+							VALUES (:name, :type, :data)";
+						$command = $this->db->createCommand($sql);
+						$command->bindValue(':name', $name);
+						$command->bindValue(':type', $type);
+						$command->bindValue(':data', 'N;');
+						$command->execute();
+
+						$generatedItems[] = $name;
+					}
+				}
+			}
+
+			// All commands executed successfully, commit
+			$txn->commit();
+			return $generatedItems;
+		}
+		catch( CDbException $e )
+		{
+			// Something went wrong, rollback
+			$txn->rollback();
+			return false;
+		}
+	}
+
+	/**
+	* Appends items to be generated of a specific type.
+	* @param array $items the items to be generated.
+	* @param integer $type the item type.
+	*/
+	public function addItems($items, $type)
+	{
+		if( isset($this->_items[ $type ])===false )
+			$this->_items[ $type ] = array();
+
+		foreach( $items as $itemname )
+			$this->_items[ $type ][] = $itemname;
+	}
+
+	/**
+	* Returns all the controllers and their actions.
+	* @param array $items the controllers and actions.
+	*/
+	public function getControllerActions($items=null)
+	{
+		if( $items===null )
+			$items = $this->getAllControllers();
+
+		foreach( $items['controllers'] as $controllerName=>$controller )
+		{
+			$actions = array();
+			$file = fopen($controller['path'], 'r');
+			$lineNumber = 0;
+			while( feof($file)===false )
+			{
+				++$lineNumber;
+				$line = fgets($file);
+				preg_match('/public[ \t]+function[ \t]+action([A-Z]{1}[a-zA-Z0-9]+)[ \t]*\(/', $line, $matches);
+				if( $matches!==array() )
+				{
+					$name = $matches[1];
+					$actions[ strtolower($name) ] = array(
+						'name'=>$name,
+						'line'=>$lineNumber
+					);
+				}
+			}
+
+			$items['controllers'][ $controllerName ]['actions'] = $actions;
+		}
+
+		foreach( $items['modules'] as $moduleName=>$module )
+			$items['modules'][ $moduleName ] = $this->getControllerActions($module);
+
+		return $items;
+	}
+
+	/**
+	* Returns a list of all application controllers.
+	* @return array the controllers.
+	*/
+	protected function getAllControllers()
+	{
+		$basePath = Yii::app()->basePath;
+		$items['controllers'] = $this->getControllersInPath($basePath.DIRECTORY_SEPARATOR.'controllers');
+		$items['modules'] = $this->getControllersInModules($basePath);
+		return $items;
+	}
+
+	/**
+	* Returns all controllers under the specified path.
+	* @param string $path the path.
+	* @return array the controllers.
+	*/
+	protected function getControllersInPath($path)
+	{
+		$controllers = array();
+
+		if( file_exists($path)===true )
+		{
+			$controllerDirectory = scandir($path);
+			foreach( $controllerDirectory as $entry )
+			{
+				if( $entry{0}!=='.' )
+				{
+					$entryPath = $path.DIRECTORY_SEPARATOR.$entry;
+					if( strpos(strtolower($entry), 'controller')!==false )
+					{
+						$name = substr($entry, 0, -14);
+						$controllers[ strtolower($name) ] = array(
+							'name'=>$name,
+							'file'=>$entry,
+							'path'=>$entryPath,
+						);
+					}
+
+					if( is_dir($entryPath)===true )
+						foreach( $this->getControllersInPath($entryPath) as $controllerName=>$controller )
+							$controllers[ $controllerName ] = $controller;
+				}
+			}
+		}
+
+		return $controllers;
+	}
+
+	/**
+	* Returns all the controllers under the specified path.
+	* @param string $path the path.
+	* @return array the controllers.
+	*/
+	protected function getControllersInModules($path)
+	{
+		$items = array();
+
+		$modulePath = $path.DIRECTORY_SEPARATOR.'modules';
+		if( file_exists($modulePath)===true )
+		{
+			$moduleDirectory = scandir($modulePath);
+			foreach( $moduleDirectory as $entry )
+			{
+				if( substr($entry, 0, 1)!=='.' && $entry!=='rights' )
+				{
+					$subModulePath = $modulePath.DIRECTORY_SEPARATOR.$entry;
+					if( file_exists($subModulePath)===true )
+					{
+						$items[ $entry ]['controllers'] = $this->getControllersInPath($subModulePath.DIRECTORY_SEPARATOR.'controllers');
+						$items[ $entry ]['modules'] = $this->getControllersInModules($subModulePath);
+					}
+				}
+			}
+		}
+
+		return $items;
+	}
+}

File components/RInstaller.php

+<?php
+/**
+* Rights installer component class file.
+*
+* @author Christoffer Niska <cniska@live.com>
+* @copyright Copyright &copy; 2010 Christoffer Niska
+* @since 0.9.3
+*/
+class RInstaller extends CApplicationComponent
+{
+    const ERROR_NONE=0;
+    const ERROR_QUERY_FAILED=1;
+
+	/**
+	* @property array the roles assigned to users implicitly.
+	*/
+	public $defaultRoles;
+	/**
+	* @property string the name of the superuser role.
+	*/
+	public $superuserName;
+	/**
+	* @property string the name of the authenticated role.
+	*/
+	public $authenticatedName;
+	/**
+	* @property string the name of the guest role.
+	*/
+	public $guestName;
+    /**
+     * @property RAuthManager the authorization manager.
+     */
+	private $_authManager;
+    /**
+     * @property boolean whether Rights is installed.
+     */
+	private $_installed;
+
+	/**
+	* @property CDbConnection
+	*/
+	public $db;
+
+	/**
+	* Initializes the installer.
+	* @throws CException if the authorization manager or the web user
+	* is not configured to use the correct class.
+	*/
+	public function init()
+	{
+		parent::init();
+
+		// Make sure the application is configured
+		// to use a valid authorization manager.
+		$authManager = Yii::app()->getAuthManager();
+		if( ($authManager instanceof RDbAuthManager)===false )
+			throw new CException(Rights::t('install', 'Application authorization manager must extend the RDbAuthManager class.'));
+
+		// Make sure the application is configured
+		// to use a valid web user.
+		$user = Yii::app()->getUser();
+		if( ($user instanceof RWebUser)===false )
+			throw new CException(Rights::t('install', 'Application web user must extend the RWebUser class.'));
+
+		$this->_authManager = $authManager;
+		$this->db = $this->_authManager->db;
+	}
+
+	/**
+	* Runs the installer.
+	* @param boolean whether to drop tables if they exists.
+	* @return boolean whether the installer ran successfully.
+	*/
+	public function run()
+	{
+        // Get the table names.
+        $itemTable = $this->_authManager->itemTable;
+        $itemChildTable = $this->_authManager->itemChildTable;
+        $assignmentTable = $this->_authManager->assignmentTable;
+        $rightsTable = $this->_authManager->rightsTable;
+
+        // Fetch the schema.
+        $schema = file_get_contents(dirname(__FILE__).'/../data/schema.sql');
+
+        // Correct the table names.
+        $schema = strtr($schema, array(
+            'AuthItem'=>$itemTable,
+            'AuthItemChild'=>$itemChildTable,
+            'AuthAssignment'=>$assignmentTable,
+            'Rights'=>$rightsTable,
+        ));
+
+        // Convert the schema into an array of sql queries.
+        $schema = preg_split("/;\s*/", trim($schema, ';'));
+
+        // Start transaction
+        $txn = $this->db->beginTransaction();
+
+        try
+        {
+            // Execute each query in the schema.
+            foreach( $schema as $sql )
+            {
+                $command = $this->db->createCommand($sql);
+                $command->execute();
+            }
+
+            // Insert the necessary roles.
+            $roles = $this->getUniqueRoles();
+            foreach( $roles as $roleName )
+            {
+                $sql = "INSERT INTO {$itemTable} (name, type, data)
+                    VALUES (:name, :type, :data)";
+                $command = $this->db->createCommand($sql);
+                $command->bindValue(':name', $roleName);
+                $command->bindValue(':type', CAuthItem::TYPE_ROLE);
+                $command->bindValue(':data', 'N;');
+                $command->execute();
+            }
+
+            // Assign the logged in user the superusers role.
+            $sql = "INSERT INTO {$assignmentTable} (itemname, userid, data)
+                VALUES (:itemname, :userid, :data)";
+            $command = $this->db->createCommand($sql);
+            $command->bindValue(':itemname', $this->superuserName);
+            $command->bindValue(':userid', Yii::app()->getUser()->id);
+            $command->bindValue(':data', 'N;');
+            $command->execute();
+
+            // All commands executed successfully, commit.
+            $txn->commit();
+            return self::ERROR_NONE;
+        }
+        catch( CDbException $e )
+        {
+            // Something went wrong, rollback.
+            $txn->rollback();
+
+            return self::ERROR_QUERY_FAILED;
+        }
+	}
+
+	/**
+	* Returns a list of unique roles names.
+	* @return array the list of roles.
+	*/
+	private function getUniqueRoles()
+	{
+		$roles = CMap::mergeArray($this->defaultRoles, array(
+            $this->superuserName,
+            $this->authenticatedName,
+            $this->guestName,
+        ));
+		return array_unique($roles);
+	}
+
+	/**
+	* @return boolean whether Rights is installed.
+	*/
+	public function getInstalled()
+	{
+		if( $this->_installed!==null )
+		{
+			return $this->_installed;
+		}
+		else
+		{
+            $schema = array(
+                "SELECT COUNT(*) FROM {$this->_authManager->itemTable}",
+                "SELECT COUNT(*) FROM {$this->_authManager->itemChildTable}",
+                "SELECT COUNT(*) FROM {$this->_authManager->assignmentTable}",
+                "SELECT COUNT(*) FROM {$this->_authManager->rightsTable}",
+            );
+
+			try
+			{
+                foreach( $schema as $sql )
+                {
+                    $command = $this->db->createCommand($sql);
+                    $command->queryScalar();
+                }
+
+				$installed = true;
+			}
+			catch( CDbException $e )
+			{
+				$installed = false;
+			}
+
+			return $this->_installed = $installed;
+		}
+	}
+}

File components/RWebUser.php

+<?php
+/**
+* Rights web user class file.
+*
+* @author Christoffer Niska <cniska@live.com>
+* @copyright Copyright &copy; 2010 Christoffer Niska
+* @since 0.5
+*/
+class RWebUser extends CWebUser
+{
+	/**
+	* Actions to be taken after logging in.
+	* Overloads the parent method in order to mark superusers.
+	* @param boolean $fromCookie whether the login is based on cookie.
+	*/
+	public function afterLogin($fromCookie)
+	{
+		parent::afterLogin($fromCookie);
+
+		// Mark the user as a superuser if necessary.
+		if( Rights::getAuthorizer()->isSuperuser($this->getId())===true )
+			$this->isSuperuser = true;
+	}
+
+	/**
+	* Performs access check for this user.
+	* Overloads the parent method in order to allow superusers access implicitly.
+	* @param string $operation the name of the operation that need access check.
+	* @param array $params name-value pairs that would be passed to business rules associated
+	* with the tasks and roles assigned to the user.
+	* @param boolean $allowCaching whether to allow caching the result of access checki.
+	* This parameter has been available since version 1.0.5. When this parameter
+	* is true (default), if the access check of an operation was performed before,
+	* its result will be directly returned when calling this method to check the same operation.
+	* If this parameter is false, this method will always call {@link CAuthManager::checkAccess}
+	* to obtain the up-to-date access result. Note that this caching is effective
+	* only within the same request.
+	* @return boolean whether the operations can be performed by this user.
+	*/
+	public function checkAccess($operation, $params=array(), $allowCaching=true)
+	{
+		// Allow superusers access implicitly and do CWebUser::checkAccess for others.
+		return $this->isSuperuser===true ? true : parent::checkAccess($operation, $params, $allowCaching);
+	}
+
+	/**
+	* @param boolean $value whether the user is a superuser.
+	*/
+	public function setIsSuperuser($value)
+	{
+		$this->setState('Rights_isSuperuser', $value);
+	}
+
+	/**
+	* @return boolean whether the user is a superuser.
+	*/
+	public function getIsSuperuser()
+	{
+		return $this->getState('Rights_isSuperuser');
+	}
+	
+	/**
+	 * @param array $value return url.
+	 */
+	public function setRightsReturnUrl($value)
+	{
+		$this->setState('Rights_returnUrl', $value);
+	}
+	
+	/**
+	 * Returns the URL that the user should be redirected to 
+	 * after updating an authorization item.
+	 * @param string $defaultUrl the default return URL in case it was not set previously. If this is null,
+	 * the application entry URL will be considered as the default return URL.
+	 * @return string the URL that the user should be redirected to 
+	 * after updating an authorization item.
+	 */
+	public function getRightsReturnUrl($defaultUrl=null)
+	{
+		if( ($returnUrl = $this->getState('Rights_returnUrl'))!==null )
+			$this->returnUrl = null;
+		
+		return $returnUrl!==null ? CHtml::normalizeUrl($returnUrl) : CHtml::normalizeUrl($defaultUrl);
+	}
+}

File components/Rights.php

+<?php
+/**
+* Rights helper class file.
+*
+* Provides static functions for interaction with Rights from outside of the module.
+*
+* @author Christoffer Niska <cniska@live.com>
+* @copyright Copyright &copy; 2010 Christoffer Niska
+* @since 0.9.1
+*/
+class Rights
+{
+	const PERM_NONE = 0;
+	const PERM_DIRECT = 1;
+	const PERM_INHERITED = 2;
+
+	private static $_m;
+	private static $_a;
+
+	/**
+	* Assigns an authorization item to a specific user.
+	* @param string $itemName the name of the item to assign.
+	* @param integer $userId the user id of the user for which to assign the item.
+	* @param string $bizRule business rule associated with the item. This is a piece of
+	* PHP code that will be executed when {@link checkAccess} is called for the item.
+	* @param mixed $data additional data associated with the item.
+	* @return CAuthItem the authorization item
+	*/
+	public static function assign($itemName, $userId, $bizRule=null, $data=null)
+	{
+		$authorizer = self::getAuthorizer();
+		return $authorizer->authManager->assign($itemName, $userId, $bizRule, $data);
+	}
+
+	/**
+	* Revokes an authorization item from a specific user.
+	* @param string $itemName the name of the item to revoke.
+	* @param integer $userId the user id of the user for which to revoke the item.
+	* @return boolean whether the item was removed.
+	*/
+	public static function revoke($itemName, $userId)
+	{
+		$authorizer = self::getAuthorizer();
+		return $authorizer->authManager->revoke($itemName, $userId);
+	}
+
+	/**
+	* Returns the roles assigned to a specific user.
+	* If no user id is provided the logged in user will be used.