Frontend, Backend Folders And Rights

Hello,

I’m a complete beginner when it comes to Yii. I’ve started learning it two days ago.

I decided to use this folder structure: http://www.yiiframework.com/wiki/33/ ("normal" application and a backend folder inside). I know there are alternatives but I liked this one.

Anyway, I’ve also installed the Yii rights module (http://www.yiiframework.com/extension/rights/). I put it into the backend folder, but then I realized, that I won’t be able to manage front-end users rights.

So what’s the right solution (maybe I should install it the frontend folder either?).

BTW: I read that for backend functionality writing a module is an option, but then in many places I read that this leads to very ugly code for some reason, and that separate folders are much better. I decided that code common for both frontend and backend will simply be in frontend models. Theoretically I could install this “rights module” in frontend, but then it won’t be able to see and set rights to the backend controllers/actions, right?

Or maybe I should forget messing with folder structure, keep everything in one place and then using the ‘rights’ module specify access for each function (which would be quite uneffective).

I don’t know, if this will bring you any help, but here’s my idea and point of view on this matters.

First you have to understand and clarify to yourself, what "front-end" and "back-end" means to you. Because definition of it could vary among developers, customers and projects.

I build my applications two ways. Not always concerning to their size, as in the article, you mentioned. Often situation, customer requirements or other circumstances decides.

But in general, for most my projects, whole application is backend (cause it is written in Yii/PHP, which is backend, server-side language, after all, right), with views folder functioning as front-end. I usually gave graphic team access to views folder only (sometimes to layouts, if app supports layouts) and that is all.

If application is strictly for front-end use, I build it that way, reserving console part for pure backend operation. Simply like that. If I need some database-cleanup funtions for manual call or to open "a gate" for CRON to execute some deep-server-side, internal code, then I do this as console part of the same application. It is solved very flexible in Yii and I find it suiting all my needs.

The idea presented in article, you mentioned, is quite good, but even though I find it a bit to messy for my code. I get used to default Yii-app code organization and wouldn’t like to change it to much. But that’s my opinion.

Also consider this Wiki article.

Hi,

Thank you for your answer.

By backend I mean administrative tasks. For example a blog would have a frontend (posts visible for everyone) and in backend I would put things like adding/deleting/modifing posts, removing comments etc.

I come from the world of Codeigniter. In this framework we slightly modified hooks/allowed, so we could write something like this:

function showPost(){//everyone can access this

}

function adminDeletePost(){//thanks to the ‘admin’ prefix, this function is available to users with administrative rights.

}

This was quite cool, because most modules (controllers actually) needed both functionality - a gallery for example, needs "public" functions to show images to all users, but it also obiously needs admin panel to add/remove images etc. And this is kind of situation I face most offten.

Anyway, when I started googling about the conventions in Yii I found two articles, both suggesting creating separate folders. And now I have the problem, which I described in my initial post.

Do I understand your post right, that you finally have only one folder? In this case, how do you manage what functions are available to currently logged in user?

If I’m getting you correctly, if I would like to change access rights for particular function from admin to a normal user, in Codeigniter I would have to actually change function name from “adminXXX” to “XXX”, right? That isn’t cool anyway! :] I completely don’t get what could be useful in forcing developer to rename functions, if he wants to change access controll to it? :]

Anyway, in Yii we’re doing this a bit different way. The easiest way is to hire accessControl mechanism. Read more about it here:

http://www.yiiframework.com/doc/api/1.1/CAccessControlFilter/

http://www.yiiframework.com/doc/guide/1.1/en/topics.auth

For larger project, you can cosider doing this on database side. Either manually:

http://www.yiiframework.com/wiki/191/implementing-a-user-level-access-system/

http://www.yiiframework.com/wiki/341/simple-access-control/

or by installing and using specific extension like for example:

http://www.yiiframework.com/extension/rights

http://www.yiiframework.com/extension/yii-user-management

And for the answer to your question. If you only need to separate part of application accessible by all users from the one accesible only by admin, then in my opinion, you don’t need such nested, complex structure. like in mentioned articles, at all. These are for much larger projects. For your purpose, all that CAccessControlFilter and basic, build-in authentication system offers is far enough. I would even think twice, before using database for such purpose. In all my small-to-medium projects access control in Yii is doing just fine.

Hi, I’m a bit late, but thank you for your answer!

Well, not in codeigniter generally but in our modified codeigniter. And don’t remember changing function name, because it’s obvious from the beginning that function like ‘addPost’ will be available for admin only.

Thank you! I wanted to hear it. After reading these articles I was pretty sure this is a convention in Yii. I’m happy it is not:)

Yii framework is very flexible, so you have multiple choices.

From design side, it is better if you can merge frontend and backend into same design, you will have less files to maintain, but this is not always possible.

I suggest you to check this folder structure too:

http://www.yiiframework.com/wiki/155/the-directory-structure-of-the-yii-project-site

Or even better, Antonio created project, which include project structure and everything else that you need in order to start professional project, with separate backend, frontend and console application:

http://www.yiiframework.com/wiki/374/yiiboilerplate-setup-a-professional-project-structure-in-seconds/

This is great structure for complex projects.

Yes (true) in a simple user-access system (based on user level, isAdmin flag etc.) and no (false) in complex RBAC (Role-Based Access Control) system, where you have multiple roles, tasks etc. and where addPost could be available to admins, moderators, editors etc. etc.

But that’s just off-topic blah, blah, blah! :] Your in Yii (welcome!), so this is now not important! :]

Thank you for your answers.

But now I have no idea how to implement this: example.com brings us to frontend and example.com/admin brings us to admin panel. How can I do this, if not by using subfolder?

If you create a subfolder, then most likely you will have to have two separate Yii apps, which is not an option, if I’m not mistaken.

If you haven’t changed anything in default Yii application configuration, then default controller is SiteController, and it is already defined in protected/controllers/SiteController.php. For every controller default action name is index.

If your user (vistor) call only website address, with no parameters (route), then Yii executed default controller with default action. So, what ever should be in front end, you must implement in actionIndex() defined in SiteController.

Then, copy this controller (file), place new copy in protected/controllers, rename both file and class name inside to AdminController, and – again – put everything you need for admin panel inside actionIndex in this new AdminController.

If your visitor types only website address plus slash plus "admin", then Yii will take that "admin" as controller part of route, so it will execute AdminController, again with default action (as user has not specified an action name). And that will be index action, thus actionIndex() function inside AdminController.

Thanks, but this would lead to a very big adminController. And this app will not be so small - this will be cms with articles, blog, gallery and a lot of other things.

Am I right, that folders backend/frontend is the only option?

In my scenario, AdminController is purely for things strictly related to administration – like: login, logout, checking rights, getting user info, etc.

All elements, that you have mentioned (CMS, articles, blogs, gallery) are accessible by both frontend and backend (both regular users and administrator). So operations related to them should be put into separate controllers (ArticleController, BlogController, PostController, GalleryController) and access to each actions within this controllers should be limited using mentioned rules() function.

This is how Yii implements this by default and this is how I do it. There is no need to put that into separate folders, IMHO. But you’re of course free to design this any way you like it.

OK, thanks, I think I’ll go that way.

@konrad1234:

I have used this structure http://www.yiiframework.com/wiki/374/yiiboilerplate-setup-a-professional-project-structure-in-seconds/ for my CMS based on Yii. I’ve implemented Rights module to support this like this:

My current folder structure

-apps

----web

----backend

----common

-core

----cms

----yii

Current Rights Module Permission Management

Here are my modifications

1/ Make sure application id is the same with the folder name. For example, config file of backend app should be:


 return array(

                    

            // Project Name                    

    	    'basePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..',

    	    // Project Id - should be the same with its folder container

            'id'=> 'backend',	

            ...

);

2/ Modify RightsFilter.php




/**

	* Performs the pre-action filtering.

	* @param CFilterChain $filterChain the filter chain that the filter is on.

	* @return boolean whether the filtering process should continue and the action

	* should be executed.

	*/

	protected function preFilter($filterChain)

	{

		// By default we assume that the user is allowed access

		$allow = true;


		$user = Yii::app()->getUser();

		$controller = $filterChain->controller;

		$action = $filterChain->action;


		// Check if the action should be allowed

		if( $this->_allowedActions!=='*' && in_array($action->id, $this->_allowedActions)===false )

		{

			// Initialize the authorization item as an empty string

			$authItem = '';


			// Tuan implements to add app ID to item name GXC-CMS

			$authItem .= strtolower(app()->id).'.';


			// Append the module id to the authorization item name

			// in case the controller called belongs to a module

			if( ($module = $controller->getModule())!==null )

				$authItem .= ucfirst($module->id).'.';


			// Append the controller id to the authorization item name

			$authItem .= ucfirst($controller->id);


			// Check if user has access to the controller

			if( $user->checkAccess($authItem.'.*')!==true )

			{

				// Append the action id to the authorization item name

				$authItem .= '.'.ucfirst($action->id);


				// Check if the user has access to the controller action

				if( $user->checkAccess($authItem)!==true )

					$allow = false;

			}

		}


		// User is not allowed access, deny access

		if( $allow===false )

		{

			$controller->accessDenied();

		 	return false;

		}


		// Authorization item did not exist or the user had access, allow access

		return true;

	}



3/ Modify permissions.php layout




<div id="permissions">

<?php 

	$app=isset($_GET['app'])? strtolower($_GET['app']) : strtolower(app()->id);

	Yii::app()->controller->pageTitle = Rights::t('core', 'Permissions').' - '.ucfirst($app).' Application' ; ?>


	<?php 

		$apps=getAllApps();

	?>

	<p>

	<?php foreach($apps as $app):?>		

			<?php echo CHtml::link(Rights::t('core', 'Set permissions for').' '.ucfirst($app), array('authItem/permissions','app'=>$app), array(

	   	'class'=>'generator-link',)); ?>  | 

		

	<?php endforeach; ?>

	</p>


<?php foreach($apps as $app): ?>

		

			<?php echo CHtml::link(Rights::t('core', 'Generate Items for').' '.ucfirst($app), array('authItem/generate','app'=>$app), array(

	   	'class'=>'generator-link',)); ?>  | 

		

	<?php endforeach; ?>

	</p>

...

</div>



I have a globals.php file where I define global functions. I put getAllApps() function there

Remember to define path alias to COMMON folder


/**

	* Function to Get All Apps Available

	**/

	

	public function getAllApps(){		

		$cache_id='gxchelpers-apps';

    	$apps=Yii::app()->cache->get($cache_id);		

		if($apps===false){

			$apps=array();

			$folders_app = get_subfolders_name(Yii::getPathOfAlias('common').DIRECTORY_SEPARATOR.'..') ;    

	        foreach($folders_app as $folder){	 	        	

	        	if(file_exists(Yii::getPathOfAlias('common').DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.$folder.DIRECTORY_SEPARATOR.'protected'

	        		.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'environment.php'))

	        		$apps[]=$folder;         	        	

	        }  	        

			Yii::app()->cache->set($cache_id,$apps,7200);

		}

				

		return $apps;

	}

4/ Modify RGenerator.php file




/**

	* Returns a list of all application controllers.

	* @return array the controllers.

	*/

	protected function getAllControllers()

	{


		$items['controllers']=array();

		$items['modules']=array();


		//Get App Path

		$app=isset($_GET['app'])? strtolower($_GET['app']) : false;


		// Tuan Implement to look for module controllers in common and cms folder also

		//If there is no $_GET['app'], we will use the current app

		if(!$app)

			$basePath = Yii::app()->basePath;

		else 

			$basePath = Yii::app()->basePath.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.$app.DIRECTORY_SEPARATOR.'protected';

		

		if(is_dir($basePath)){

			//Look for controller in current app

			$items['controllers'] = $this->getControllersInPath($basePath.DIRECTORY_SEPARATOR.'controllers');


			

			//Look for module controller in current app

			$items['modules'] = $this->getControllersInModules($basePath);


			//Look for module controller in common folder

			$items['modules'] = array_merge($items['modules'],$this->getControllersInModules(Yii::getPathOfAlias('common')));


			

		}

	


		return $items;

	}



4/ Modify RPermissionDataProvider.php




	/**

	* Generates the data for the data provider.

	*/

	protected function generateData()

	{

		$data = array();

		$permissions = $this->_permissions;

		$parents = $this->_parents;

		foreach( $this->_items as $itemName=>$item )

		{

			$row = array();

			$row['description'] = $item->getNameLink();


			foreach( $this->_roles as $roleName=>$role )

			{

				// Item is directly assigned to the role

				if( $permissions[ $roleName ][ $itemName ]===Rights::PERM_DIRECT )

				{

					$permissionColumn = $item->getRevokePermissionLink($role);

				}

				// Item is inherited by the role from one of its children

				else if( $permissions[ $roleName ][ $itemName ]===Rights::PERM_INHERITED && isset($parents[ $roleName ][ $itemName ])===true )

				{

					$permissionColumn = $item->getInheritedPermissionText($parents[ $roleName ][ $itemName ], $this->displayParentType);

				}

				// Item is not assigned to the role

				else

				{

					$permissionColumn = $item->getAssignPermissionLink($role);

				}


				// Populate role column

				$row[ strtolower($roleName) ] = isset($permissionColumn)===true ? $permissionColumn : '';

			}


			// Append the row to data

			$data[] = $row;

		}


		

		//Tuan implement to show Item based on App only

		//Get App Path

		$app=isset($_GET['app'])? strtolower($_GET['app']) : strtolower(app()->id);

		foreach($data as $key=>$item){			

			if(strpos($item['description'],$app)===false){

				//Strip Item that is not based on current app

				unset($data[$key]);

			}

		}

		


		$this->setData($data);

	}



That’s all - I hope this help.