Jump to content
Larry Ullman's Book Forums
ronallensmith

Creating Custom Views For Different Users

Recommended Posts

I'm looking through the book, but getting turned around on the concept of redirecting authenticated users to custom views.  Is it that I create another Model and Controller with the Gii tool to create a custom view, or can that be done using the existing Model's Controller??

 

I'm using the following Access Rules:

 

    public function accessRules()
    {
        return array(
            array('deny', // deny guest to perform 'create', 'update' and 'delete' actions
                'actions'=>array('create','update','delete'),
                'users'=>array('guest'),
            ),
            array('allow', // allow authenticated user to perform 'create' and 'update' actions
                'actions'=>array('create','update'),
                'users'=>array('admin'),
            ),
            array('allow', // allow admin user to perform 'admin' and 'delete' actions
                'actions'=>array('admin','delete'),
                'users'=>array('admin'),
            ),
            array('allow',  // allow all users to perform 'index' and 'view' actions
                'actions'=>array('index','view'),
                'users'=>array('@'),
            ),
            array('deny',  // deny all users
                'users'=>array('*'),
            ),
            array('deny', // deny 'delete' action for all contexts
                'actions'=>array('delete'),
            
            ),
        );
    }

 

admin is no problem, but I need the guest to redirect to a different GridView than the one admin sees, that only has ability to View and not Update and Delete. The problem is I have to do this manually in the URL so far--even for admin login.

Share this post


Link to post
Share on other sites

If i was you i would just create another controller action and render the view according to whether a user was an admin or user viewing the page. Here is a sample for the view action in the controller. You will also require two view files in your views folder under the controller name you wish to execute these actions admin_view.php and user_view.php.

public function actionView($id)
{
	if(Yii::app()->user->type === 'admin')
	{
		$this->render('admin_view',array(
			'model'=>$this->loadModel($id),
		));
	}
	else
	{
		$this->render('user_view',array(
			'model'=>$this->loadModel($id),
		));
	}
}

You will also be required to set the type of user in components/userindentity.php

 

$this->setState('type',$user->type);

 

This value can be given from loading up the users records from the database through active record.

 

In your accessRules() in your controller you will also need to setup a little better authentication if you are using the setState and saving user type for example

array('allow', // allow admin only for these actions
				'actions'=>array('admin','index'),
				'users'=>array('@'),
				'expression'=>"isset(Yii::app()->user->type) && (Yii::app()->user->type === 'admin')",

Hope this helps out.

Share this post


Link to post
Share on other sites

Thanks Edward,

 

Do I create the two separate views with the Gii tool; when I went to do that it looked like a totally separate Controller would be created? And, this is a very simple site where I only have the 2 users 'admin' and 'guest'. So, will the 'Yii::app()->user->type === ...' work with the following:

 

class UserIdentity extends CUserIdentity
{
    /**
     * Authenticates a user.
     * The example implementation makes sure if the username and password
     * are both 'demo'.
     * In practical applications, this should be changed to authenticate
     * against some persistent user identity storage (e.g. database).
     * @return boolean whether authentication succeeds.
     */
    public function authenticate()
    {
        $users=array(
            // username => password
            // 'demo'=>'demopassword',
            'admin'=>'adminpassword',
            'guest'=>'guestpassword',
        );
        if(!isset($users[$this->username]))
            $this->errorCode=self::ERROR_USERNAME_INVALID;
        elseif($users[$this->username]!==$this->password)
            $this->errorCode=self::ERROR_PASSWORD_INVALID;
        else
            $this->errorCode=self::ERROR_NONE;
        return !$this->errorCode;
    }
}

Share this post


Link to post
Share on other sites

I just had a thought while researching the above.  Could it be that, using the code you suggest above, I just copy and rename the .../protected/views/MyModel/admin.php file to something like '.../protected/views/MyModel/guest.php' in order to make the second view for the 'guest' user? Then edit the 'CButtonColumn' Class in guest.php to remove the 'update' and 'delete' buttons?

Share this post


Link to post
Share on other sites

Thanks Edward,

 

Do I create the two separate views with the Gii tool; when I went to do that it looked like a totally separate Controller would be created? And, this is a very simple site where I only have the 2 users 'admin' and 'guest'. So, will the 'Yii::app()->user->type === ...' work with the following:

 

class UserIdentity extends CUserIdentity

{

    /**

     * Authenticates a user.

     * The example implementation makes sure if the username and password

     * are both 'demo'.

     * In practical applications, this should be changed to authenticate

     * against some persistent user identity storage (e.g. database).

     * @return boolean whether authentication succeeds.

     */

    public function authenticate()

    {

        $users=array(

            // username => password

            // 'demo'=>'demopassword',

            'admin'=>'adminpassword',

            'guest'=>'guestpassword',

        );

        if(!isset($users[$this->username]))

            $this->errorCode=self::ERROR_USERNAME_INVALID;

        elseif($users[$this->username]!==$this->password)

            $this->errorCode=self::ERROR_PASSWORD_INVALID;

        else

            $this->errorCode=self::ERROR_NONE;

        return !$this->errorCode;

    }

}

 

If you are using static values for logging in users you can do it the way you are doing it and just use the standard rules like you are using

 array('allow', // allow admin user to perform 'admin' and 'delete' actions
                'actions'=>array('admin','delete'),
                'users'=>array('admin'),

The way i suggested it was having users logged in the database but you are definitely right if its just a small site that would be ideal the way you have it.

Share this post


Link to post
Share on other sites

I just had a thought while researching the above.  Could it be that, using the code you suggest above, I just copy and rename the .../protected/views/MyModel/admin.php file to something like '.../protected/views/MyModel/guest.php' in order to make the second view for the 'guest' user? Then edit the 'CButtonColumn' Class in guest.php to remove the 'update' and 'delete' buttons?

 

Yes i also agree that would be a perfect method i have also done something similar you can use the 'visible' CButtonColumn feature and fill it in with php expression to disable it in a certain situation. I disabled users from being able to remove their registration address while other addresses like their shipping and payment addresses could be deleted. I think i am still learning things about CGridView but i really love it a lot now, we are truly blessed to have such great widgets.

 

I used this php expression in my code, however you will have to tweak it to your situation with the users

'visible'=>'($data->registration_address === "yes") ? false : true;',

Share this post


Link to post
Share on other sites

In the Yii Book in the section "Handling Redirections" it talks about $this->redirect(Yii::app()->user->returnUrl);.  However, in my opinion, for the beginner, there could be more info on how to redirect to a different page/view upon a successful login.  In my case I need to redirect to /protected/views/myModel/admin.php.  This was completely skipped over in the material.  What would I replace Yii::app()->user->returnUrl with in my case??

Share this post


Link to post
Share on other sites

SOLVED: KonApaz in the Yii Framework Forum suggested:

 

#/protected/controllers/SiteController::actionLogin()

$this->redirect(Yii::app()->user->returnUrl=array('controller/action'));

 

This works beautifully, unless someone has a better (best practices) way.

Share this post


Link to post
Share on other sites

I'm surprised that works, but if it does, that's great. The construct of assigning a value as a parameter to a function call is unusual. But again, if it works for you, that's all there is.

Share this post


Link to post
Share on other sites

If you go into CWebUser you can find the following, it surprises me why that worked at first but it can be explained.

public $loginUrl=array('/site/login');

	/**
* Returns the URL that the user should be redirected to after successful login.
* This property is usually used by the login action. If the login is successful,
* the action should read this property and use it to redirect the user browser.
* @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 login.
* @see loginRequired
*/
public function getReturnUrl($defaultUrl=null)
{
if($defaultUrl===null)
{
$defaultReturnUrl=Yii::app()->getUrlManager()->showScriptName ? Yii::app()->getRequest()->getScriptUrl() : Yii::app()->getRequest()->getBaseUrl().'/';
}
else
{
$defaultReturnUrl=CHtml::normalizeUrl($defaultUrl);
}
return $this->getState('__returnUrl',$defaultReturnUrl);
}

/**
* @param string $value the URL that the user should be redirected to after login.
*/
public function setReturnUrl($value)
{
$this->setState('__returnUrl',$value);
}

Share this post


Link to post
Share on other sites

The following seems to work for redirecting to two different views based on identity of user, but I'd like someone to check it's validity and best practices, since I'm a newbie:

 

    # protected/controllers/SiteController.php

    public function actionLogin()
    {
        $model=new LoginForm;

        // if it is ajax validation request
        if(isset($_POST['ajax']) && $_POST['ajax']==='login-form')
        {
            echo CActiveForm::validate($model);
            Yii::app()->end();
        }

        // collect user input data
        if(isset($_POST['LoginForm']))
        {
            $model->attributes=$_POST['LoginForm'];
            // validate user input and redirect to the specified page if valid
            if($model->validate() && $model->login() && Yii::app()->user->name == 'admin')
                        {
                                $this->redirect(Yii::app()->user->returnUrl=array('MyModelController/admin'));
                        }
                        else
                        {
                                $this->redirect(Yii::app()->user->returnUrl=array('MyModelController/index'));
                        }          
                }
        // display the login form
    $this->render('login',array('model'=>$model));
        }

 

How's this? Now the only thing left is to have the else{} clause redirect to a cloned admin view without the Delete and Update buttons, instead of index, as in this test.  Can Gii be used to generate the other view, or whatever?  Anyone have any hints?

Share this post


Link to post
Share on other sites

This looks good but why didn't you just direct admin or the user to the same CGridView or which ever presentation grid you were using to present your data and then just disable its attributes depending on the user. I think that way you can save all the cloning.

 

Gii can generate new crud functionality but it would be the same as you already have. You could just make a new controller name and generate some new crud then copy and paste over the templates deleting these files when you are finished. Personally i don't see the point in this, it would be far quicker just to make a new view.php file and just copy and paste code over and tweaking it a bit but still its not better than the above i suggested. The widgets are easily customizable its far easier to just tweak them a bit.

Share this post


Link to post
Share on other sites

Thanks Edward, I reeeaaallly appreciate your help.  Concerning your first suggestion "...just direct admin or the user to the same CGridView..."  When you say "...just disable its attributes depending on the user.", are you meaning to do so in /protected/controllers/MyModelController.php, public function accessRules(), or somewhere else??  Here's where I'd like to have users, other than admin, only have the "View" button displayed; all 3 default buttons displayed for admin, of course.  I took a look around and can't find anything on how to do this using just one "CGridView"--unless I'm overlooking something.

 

That's when I thought of creating 2 admin views, but that's also when I become veeery confused, Haha.  It seems to me this should be done in the controller, but then how do you do it?  It just dawns on me to maybe create another action something like 'public function actionGuest()' in the Model controller, then have '$this->redirect(Yii::app()->user->returnUrl=array('tblContacts/guest'));' in the Site controller.  Now the problem is how to remove the Update and Delete buttons.  Sorry for so many words.

Share this post


Link to post
Share on other sites

I will help you with customizations CGridView as i believe one coded correctly is better than having two. You know what i mean repeated code is something we should all be avoiding.

 

I did mention something about this already in post #6 which i made, i also found this post

 

http://stackoverflow.com/questions/9429987/cgridview-conditional-delete-button

 

I have provided part of my code below and also delete parts out so not all there but that was how i customized in my CGridView. I have actually come across two things in CGridView that do not work at all but all the below works well, don't forget your CHtml::Encode i left mine out till later. You will need to identify whether the user is an admin or user then make a php expression for this in the visible array key value then the job will be done.

'columns'=>array(
		array (
            
            'name'=>'Payment Address',
			'header'=>'Payment Address',
			'value'=>function($data){
            	$checked = false;
				if($data->payment_address === 'yes') $checked = true;
				return CHtml::radioButton('paymentAddress',$checked,array('id'=>$data->id,'value'=>$data->id));
			},
			'type'=>'raw',
        ),
		array(
    		'class'=>'bootstrap.widgets.TbButtonColumn',
			'template'=>'{addressUpdate}{addressDelete}',
			'buttons'=>array(
				'addressUpdate'=>array(
            		'label'=>'Update Address',
					'icon'=>'icon-pencil',
					'imageUrl'=>false,
					'url'=>'Yii::app()->createUrl("address/update", array("id"=>$data->id))',
					'options'=>array('class'=>'addressUpdate'),
        		),
				'addressDelete'=>array(
            		'label'=>'Delete Address',
					'icon'=>'icon-remove',
					'imageUrl'=>false,
					'click' => new CJavaScriptExpression(""),
					'url'=>'Yii::app()->createUrl("address/delete", array("id"=>$data->id))',
					'options'=>array('class'=>'addressEnd'),
					'visible'=>'($data->registration_address === "yes") ? false : true;',
        		),
    		),
        ),
  • Upvote 1

Share this post


Link to post
Share on other sites

No problem but sometimes i wonder if someone could help me also or have a Yii solutions. By the way i know what you mean about Yii and coding your website for the first time, Ive had to go back and recode a lot of stuff. I couldn't really see what was the best way of doing things until later down the line but anyway it seems like Ive taken a massive jump this year regardless of that.

Share this post


Link to post
Share on other sites

Thanks Edward and Larry.  Sorry I stepped away from the problem for a day to handle other issues.  I'm taking a look at your response Edward; give me a liitle time to give any feedback.  Hey, Larry!  I've gotten better at knowing where to look regarding solving Yii related problems, and in my opinion the Yii Book and the Yii framework Class Reference docs have yielded the most frequent hints and solutions.  Thanks I'm very gratefull for your help.

Share this post


Link to post
Share on other sites

Hmm, based on http://stackoverflow...l-delete-button and http://www.yiiframework.com/doc/api/CButtonColumn, I used
 

# /protected/views/MyModel/admin.php

                array(
            'class'=>'CButtonColumn',
                        'buttons'=>array
                        (
                            'delete'=>array
                            (
                                'label'=>'Delete',
                                'visible'=>!Yii::app()->user->checkAccess('guest'),
                            ),
                        ),
                    ),

 

But, get php error message saying: call_user_func_array() expects parameter 1 to be a valid callback, no array or string given

Share this post


Link to post
Share on other sites

Thanks for the nice words, Ron. Edward, I'm always happy to help. In my experience, though, you tend to post questions, keep working on it, and eventually figure out and post the answer before I ever see the question in the first place. That's great, but I am absolutely going to help when you need it. 

 

Ron, could you give the complete error message?

Share this post


Link to post
Share on other sites

@Ron Which part of the code is throwing the array. Just checking you did add that code in your CGridView widget code right?

 

@Larry Yeah i know what you mean, i kind of know i can do it but sometimes it doesn't always happen every time you are at the computer, i worked about 5 hours before trying to give that a bash brain was fried. I hope when my brain is fresh i can get it worked out, hahaha. I'll see what i come up with first and if i can't get it ill post up what i have so far and then you can hopefully help me find a final solution. Thanks

Share this post


Link to post
Share on other sites

Thanks Larry,

 

The error follows below and was generated when using the code mentioned in post# 14 above:

 

# /protected/views/MyModel/admin.php

                array(
            'class'=>'CButtonColumn',
                                'buttons'=>array
    (
        'delete' => array
        (
            'label'=>'Delete',
            //other params
            'visible'=>!Yii::app()->user->checkAccess('admin'),
        ),
                ),
                ),
            ),
        )); ?>

 

The complete error is:

 

PHP warning

call_user_func_array() expects parameter 1 to be a valid callback, no array or string given

C:\wamp\framework\base\CComponent.php(611)

599      * @since 1.1.0600      */601     public function evaluateExpression($_expression_,$_data_=array())602     {603         if(is_string($_expression_))604         {605             extract($_data_);606             return eval('return '.$_expression_.';');607         }608         else609         {610             $_data_[]=$this;611             return call_user_func_array($_expression_, $_data_);612         }613     }614 }615 616 617 /**618  * CEvent is the base class for all event classes.619  *620  * It encapsulates the parameters associated with an event.621  * The {@link sender} property describes who raises the event.622  * And the {@link handled} property indicates if the event is handled.623  * If an event handler sets {@link handled} to true, those handlers
Stack Trace #0
+
 C:\wamp\framework\base\CComponent.php(611): call_user_func_array(true, array("row" => 0, "data" => TblContacts, 0 => CButtonColumn))
#1
+
 C:\wamp\framework\zii\widgets\grid\CButtonColumn.php(313): CComponent->evaluateExpression(true, array("row" => 0, "data" => TblContacts))
#2
+
 C:\wamp\framework\zii\widgets\grid\CButtonColumn.php(295): CButtonColumn->renderButton("delete", array("label" => "Delete", "url" => "Yii::app()->controller->createUrl("delete",array("id"=>$data->pr...", "imageUrl" => "/calls/assets/a6e77b09/gridview/delete.png", "options" => array("class" => "delete"), ...), 0, TblContacts)
#3
+
 C:\wamp\framework\zii\widgets\grid\CGridColumn.php(144): CButtonColumn->renderDataCellContent(0, TblContacts)
#4
+
 C:\wamp\framework\zii\widgets\grid\CGridView.php(589): CGridColumn->renderDataCell(0)
#5
+
 C:\wamp\framework\zii\widgets\grid\CGridView.php(545): CGridView->renderTableRow(0)
#6
+
 C:\wamp\framework\zii\widgets\grid\CGridView.php(455): CGridView->renderTableBody()
#7
+
 C:\wamp\framework\zii\widgets\CBaseListView.php(160): CGridView->renderItems()
#8
 unknown(0): CBaseListView->renderSection(array("{items}", "items"))
#9
+
 C:\wamp\framework\zii\widgets\CBaseListView.php(143): preg_replace_callback("/{(\w+)}/", array(CGridView, "renderSection"), "{summary} {items} {pager}")
#10
+
 C:\wamp\framework\zii\widgets\CBaseListView.php(128): CBaseListView->renderContent()
#11
+
 C:\wamp\framework\web\CBaseController.php(173): CBaseListView->run()
#12
 C:\wamp\www\calls\protected\views\tblContacts\admin.php(88): CBaseController->widget("zii.widgets.grid.CGridView", array("id" => "tbl-contacts-grid", "dataProvider" => CActiveDataProvider, "filter" => TblContacts, "columns" => array("id", "contactType", "startTime", "endTime", ...)))
83             'visible'=>!Yii::app()->user->checkAccess('admin'),84         ),85                 ),86                 ),87             ),88         )); ?>
#13
+
 C:\wamp\framework\web\CBaseController.php(126): require("C:\wamp\www\calls\protected\views\tblContacts\admin.php")
#14
+
 C:\wamp\framework\web\CBaseController.php(95): CBaseController->renderInternal("C:\wamp\www\calls\protected\views\tblcontacts\admin.php", array("model" => TblContacts), true)
#15
+
 C:\wamp\framework\web\CController.php(869): CBaseController->renderFile("C:\wamp\www\calls\protected\views\tblcontacts\admin.php", array("model" => TblContacts), true)
#16
+
 C:\wamp\framework\web\CController.php(782): CController->renderPartial("admin", array("model" => TblContacts), true)
#17
 C:\wamp\www\calls\protected\controllers\TblContactsController.php(147): CController->render("admin", array("model" => TblContacts))
142         if(isset($_GET['TblContacts']))143             $model->attributes=$_GET['TblContacts'];144 145         $this->render('admin',array(146             'model'=>$model,147         ));148     }149 150     /**151      * Returns the data model based on the primary key given in the GET variable.152      * If the data model is not found, an HTTP exception will be raised.
#18
+
 C:\wamp\framework\web\actions\CInlineAction.php(49): TblContactsController->actionAdmin()
#19
+
 C:\wamp\framework\web\CController.php(308): CInlineAction->runWithParams(array())
#20
+
 C:\wamp\framework\web\filters\CFilterChain.php(133): CController->runAction(CInlineAction)
#21
+
 C:\wamp\framework\web\filters\CFilter.php(40): CFilterChain->run()
#22
+
 C:\wamp\framework\web\CController.php(1145): CFilter->filter(CFilterChain)
#23
+
 C:\wamp\framework\web\filters\CInlineFilter.php(58): CController->filterAccessControl(CFilterChain)
#24
+
 C:\wamp\framework\web\filters\CFilterChain.php(130): CInlineFilter->filter(CFilterChain)
#25
+
 C:\wamp\framework\web\CController.php(291): CFilterChain->run()
#26
+
 C:\wamp\framework\web\CController.php(265): CController->runActionWithFilters(CInlineAction, array("accessControl", "postOnly + delete"))
#27
+
 C:\wamp\framework\web\CWebApplication.php(282): CController->run("admin")
#28
+
 C:\wamp\framework\web\CWebApplication.php(141): CWebApplication->runController("tblcontacts/admin")
#29
+
 C:\wamp\framework\base\CApplication.php(169): CWebApplication->processRequest()
#30
 C:\wamp\www\calls\index.php(13): CApplication->run()
08 defined('YII_DEBUG') or define('YII_DEBUG',true);09 // specify how many levels of call stack should be shown in each log message10 defined('YII_TRACE_LEVEL') or define('YII_TRACE_LEVEL',3);11 12 require_once($yii);13 Yii::createWebApplication($config)->run();
2013-10-05 03:12:50 Apache/2.4.4 (Win32) PHP/5.4.16 Yii Framework/1.1.13

Share this post


Link to post
Share on other sites

Is it possible you can add into a post your full code for CGridView, then i can test it on my computer for errors. I did use the YiiBooster CGridView extension for that widget but regardless of that the coding should be similar.

Share this post


Link to post
Share on other sites

The full code for admin.php is:

 

<?php
/* @var $this TblContactsController */
/* @var $model TblContacts */

$this->breadcrumbs=array(
    'Tbl Contacts'=>array('index'),
    'Manage',
);

$this->menu=array(
    array('label'=>'List TblContacts', 'url'=>array('index')),
    array('label'=>'Create TblContacts', 'url'=>array('create')),
);

Yii::app()->clientScript->registerScript('search', "
$('.search-button').click(function(){
    $('.search-form').toggle();
    return false;
});
$('.search-form form').submit(function(){
    $('#tbl-contacts-grid').yiiGridView('update', {
        data: $(this).serialize()
    });
    return false;
});
");
?>

<h1>Manage Tbl Contacts</h1>

<p>
You may optionally enter a comparison operator (<b><</b>, <b><=</b>, <b>></b>, <b>>=</b>, <b><></b>
or <b>=</b>) at the beginning of each of your search values to specify how the comparison should be done.
</p>

<?php echo CHtml::link('Advanced Search','#',array('class'=>'search-button')); ?>
<div class="search-form" style="display:none">
<?php $this->renderPartial('_search',array(
    'model'=>$model,
)); ?>
</div><!-- search-form -->

<?php $this->widget('zii.widgets.grid.CGridView', array(
    'id'=>'tbl-contacts-grid',
    'dataProvider'=>$model->search(),
    'filter'=>$model,
    'columns'=>array(
        'id',
        'contactType',
        'startTime',
        'endTime',
        'duration',
        'site',
        /*
        'prefix',
        'firstName',
        'lastName',
        'suffix',
        'phone1',
        'ext1',
        'phone2',
        'ext2',
        'email',
        'subject',
        'notes',
        'followUp',
        */
                array(
            'class'=>'CButtonColumn',
                                'buttons'=>array
                                (
                                    'delete' => array
                                    (
                                        'label'=>'Delete',
                                        //other params
                                        'visible'=>!Yii::app()->user->checkAccess('admin'),
                                    ),
                                ),
                    ),
            ),
        )); ?>
 

Share this post


Link to post
Share on other sites

You can fix that problem in CGridView by adding in this to the visible array key

'visible'=>"(Yii::app()->user->checkAccess('admin')) ? true : false;",

I would code the expression without the ! NOT and use the tenary operator, so if this is admin then visibility is true otherwise it will result in false.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

×
×
  • Create New...