How to create a non-public site?

Hi,

I have a question about the site im making in the prosess of learning Yii. This site is a non-public site, means the site will only show the main index page if you are logged in, else it will show a login page.

My main layout is for when a user is logged on. I also made a login layout, with just a simple login form.

How do I implement this logic:

  • app starts

  • app checks if logged in

  • logged in -> show main layout, if not show login layout

…the "first application" tutorial shows the main layout no matter if you are logged in or not, and only checks login for certain actions. Cant wrap my head around a solution here.

Thanks a lot for any ideas! :)

You can do the following:

  1. Write a BaseController with init() method that checks if the user is logged in or not. If not, redirects him to the login page.

  2. All your protected pages should be rendered using controllers which extend BaseController.

Thanks for that quick reply!

You say write a BaseController, I have a couple of questions about that:

  • This BaseController should extend CController right?

  • I havent read anything about init() functions in Yii yet, is this something special or some other form of __construct?

  • From the "Creating First Yii Application" tutorial, could I just use SiteController, instead of writing a new one?

I'm trying hard to wrap my head around whic logic is correct to use in this case. Hoping for some more hints!

Many sites are non-public, so I'm guessing its a perfect example for the cookbook once i get it running…

Yes, BaseController extends CController. You may put BaseController.php under /protected/components.

Your other controller classes (e.g. SiteController) should extend BaseController.

init() is a method declared in CController. In BaseController, you can override this method to check if a user is authenticated or not.

And may I ask why you want to put this controller in the components folder and not the controller one? (I assume its a controller, from your naming?)

It is a base class, not meant to be accessed by end-users.

Ok I’m gonna have to take this at face value, cause I really cant understand why the protected/controller folder is more accessable to end-users than protected/components. Yes it is a base class, but still a controller, and controllers stay in the controller folder, no?

What you are saying just goes against what i have learned so far, i cant make sense of it. If time allows, feel free to elaborate or point me to some spesific documentation.

And thanks for effort with Yii and your time for helping out! :)

If you put BaseController.php under controllers/ folder, end-users will be able to access it using /index.php?r=base

This is not what you want, since BaseController is meant to be an abstract base class that is to be extended by concrete controller classes.

OK, I kind of get what you are saying, but…

I had a feeling there should be an easier way of doing it, and I think I might have found it. The SiteController.php contains an action called actionIndex which renders the index. What i did was add a login-check there, like this:



public function actionIndex()


{


  if (Yii::app()->user->isGuest) {


    $this->actionLogin();


  } else {


    $this->render('index');


  }


}


Then in the action actionLogin i say to use another layout like this $this->layout = 'login_layout';

This does exactly what I want to do, is there any security holes or other issues in doing it this way? Seems a lot more easy to me than the other opition.

The problem with your implementation is that it can't protect other controller actions. It only protects the site/index action.

Damn. You are of course very right. Even so, I will keep it for now and be very carefull with the other controllers. When I feel more comfortable with the inner workings of Yii, I will try implement what you suggest, cause right now I find it hard to wrap my head around, too many questions.

Hot Tip! Anyone who knows and understand what qiang means fully, maby you can make a short contrib to the cookbook? :)

Wouldn’t another possible solution be like this:

  • Make the ‘logged in’ layout the default.
  • Make all controllers require login with access control filter.
  • In the actionLogin(), set the $this->layout = 'logged out layout'
Qiang's method is more sophisticated but this I think is easier to understand but requires access control for every controller.

OK, so I've spend a lot of time on yii these past 2 weeks, and feel a little more comfortable with how things work. I have tried to implement qiang's first suggestion, as I now see the smartness behind it. I have not however reached the final goal, but I think I'm on the right track.

First I made a BaseController.php and put it in components:

<?php


class BaseController extends CController


{


	public function init()


	{


    if (Yii::app()->user->isGuest) {


      $this->actionLogin();


    }


	}





	public function actionLogin()


	{


		$this->layout = 'login';


		$form=new LoginForm;


		// collect user input data


		if(isset($_POST['LoginForm']))


		{


			$form->attributes=$_POST['LoginForm'];


			// validate user input and redirect to previous page if valid


			if($form->validate())


				$this->redirect(Yii::app()->user->returnUrl);


		}


		// display the login form


		$this->render('login',array('form'=>$form));


	}


}


?>


The actionLogin was moved from SiteController.php (from “My first app…”). Now all controllers must extend BaseController, and the login-check is done. How ever, if a user is not logged on, the actionIndex from the SiteController is still rendered. How can I make it so that if a user is not logged on, the actionIndex (or any other action called for in some controller) is not carried out?

you should redirect to action login rather than calling the actionLogin() method. Otherwise, after init(), the normal controller cycles will continue.

OK, so I put the actionLogin function back into SiteController.php. I still dont understand how a parent class can call a function from a child class, ie:      $this->actionLogin() being called from BaseController, though it exist in SiteController…but somewho it works.

More important, it doesn't stop the initial actionIndex to render the index even though user is not logged in.

My code for reference:

<?php


class SiteController extends BaseController


{


	public function actionIndex()


	{


		$this->render('index');


	}


	public function actionLogin()


	{


		$this->layout = 'login';


		$form=new LoginForm;


		// collect user input data


		if(isset($_POST['LoginForm']))


		{


			$form->attributes=$_POST['LoginForm'];


			// validate user input and redirect to previous page if valid


			if($form->validate())


				$this->redirect(Yii::app()->user->returnUrl);


		}


		// display the login form


		$this->render('login',array('form'=>$form));


	}


}


class BaseController extends CController


{


	public function init()


	{


    if (Yii::app()->user->isGuest) {


      $this->actionLogin();


    }


	}


?>


Should I have an else statement in init() to kill the rest of the cycles?

nono. I mean your init() method should look like the following:



   if (Yii::app()->user->isGuest) {


      $this->redirect(array('site/login'));


    }


Good, for a second my whole concept of OOP was shaking. Ok, so I tried what you suggested (a redirect cuts the current cycle, which is why its better than calling another function, i assume).

But, now we are in an infinite loop… BaseController checks for login, and if not redirects to site/login. However, the action site/login is within SiteController which extends BaseController!

I can guess a way to solve this: Make a LoginController under controllers that just extends CController (not BaseController).

Do you se another better way to do it?

oops, I didn't realize that. Yes, your approach is fine. You may also use the following:



	protected function beforeAction($action)


	{


		if(Yii::app()->user->isGuest && $this->id.'/'.$action->id!=='site/login')


		{


			Yii::app()->user->loginRequired();


		}


		return true;


	}


I'm unfamiliar with beforeAction and loginRequred, but found this in the docs:

Quote

CController: beforeAction() - This method is invoked right before an action is to be executed (after all possible filters.)

CWebUser: loginRequired() - Redirects the user browser to the login page.

I wasn't quite sure on how to use this, but after a lot of trial and failure, I got it working like this: (ditching the initial idea with replacing the init() function)

<?php


class BaseController extends CController


{


  protected function beforeAction($action)


  {


    if(Yii::app()->user->isGuest && $this->id.'/'.$action->id!=='site/login')


    {


       Yii::app()->user->loginRequired();


    }


    return true;


  }  


}


?>

OK, lets see if I get what’s going on here… Im not logged in when app call the actionIndex in SiteController. BUT before this action is carried out, the parent BaseController gets to carry out beforeAction, which basicly says: If you are not logged in and  your action is NOT ‘site/login’ then do the loginRequired().

  • Is my understanding correct?

  • How is loginRequired() invoked since it obviously doesn’t create a loop like the redirect did?

PS. I plan to make a "howto" of these things i learn, thats why i ask of all the details, so i can understand what im talking about.

PPS. Might I add that this was a very nice and easy way to solve my problem, thanks a lot :)

Your understanding is correct. You may take a look at API manual about CWebUser::loginRequired(). It is a convenient alternative to the redirect() call directly.