Yii CGridView: how to add a static WHERE condtion? - php

I've a standard Gii created admin view, which use a CGridView, and it's showing my user table data.
the problem is that user with name 'root' must NOT BE VISIBLE.
Is there a way to add a static where condition " ... and username !='root' " ?
admin.php [view]
'columns'=>array(
'id',
'username',
'password',
'realname',
'email',
.....
user.php [model]
public function search()
{
// Warning: Please modify the following code to remove attributes that
// should not be searched.
$criteria=new CDbCriteria;
$criteria->compare('id',$this->id);
$criteria->compare('username',$this->username,true);
$criteria->compare('password',$this->password,true);
$criteria->compare('realname',$this->realname,true);
$criteria->compare('email',$this->email,true);
......
return new CActiveDataProvider($this, array(
'criteria'=>$criteria,
));
}

You can use CDbCriteria's addCondition like this:
$criteria->addCondition("username !='root'");

Your best option would be to use Yii scopes which are essentially a saved where clause (or other modification of your existing criteria) that you can apply all over your app and only need to change in one place if your criteria ends up changing later.
What makes them even cooler is that you can string them together with other scopes / criteria changes (from users in grids for instance) without having to keep track of what criteria clause is getting changed by what.
A few examples that might apply to your situation. In your controller you probably have something like this:
$users = User::model()->search()->findAll();
Asgaroth's answer answers what you were asking on the surface. But there is so much more you can do (and do easily) using scopes.
If you add the below to your user model:
class User extends CActiveRecord
{
......
public function scopes()
{
return array(
'active'=>array(
'condition'=>'active=1',
),
'isAdmin'=>array(
'condition'=>'isAdmin=1',
),
);
}
}
then you can retrieve active users (with your users' filters still applied) like this in your controller:
$users = User::model()->active()->search()->findAll();
Or you can retrieve all active admin users (without being filtered by your gridview criteria) like this:
$users = User::model()->active()->isAdmin()->findAll();
Default scopes are just an extension of the same idea:
class User extends CActiveRecord
{
public function defaultScope()
{
return array(
'condition'=>"username != 'root'",
);
}
}
If before your isAdmin scope would return the root user, applying the default scope will eliminate the root user from the models returned, as it applies to every User::model() query you make.

Related

Yii privatekey of Logged in User

my application has 2 Tables in 1 DB. One is called USER ( id, uname, pw ,role) the other is USERZ (id, files, USER_id, name) for the Files.
There is a HAS_MANY and BELONGS_To relation with both Tables.
I can see Display Users and all Files. But what i whant to do is to see just the Files for each User ( So User with id = 1 cannot see Files from the User with id = 2)
My Controller looks like this:
<?php
class SiteController extends Controller
{
...
public function actionVerwal()
{
$model=new USER;
$model2=new USERZ;
$this->render('verwal',array('model'=>$model,'model2'=>$model2));
}
...
}
My View verwal.php:
<?php
$dataProvider=new CActiveDataProvider($model2);
?>
<h1>List of Your Files<i></h1>
$this->widget('zii.widgets.grid.CGridView', array(
'dataProvider'=>$model2->a($model2->id)
, 'columns'=>array(
'id', 'zname', 'USER_id'
)
));
...
?>
My Model USERZ has the funktion a wich looks like this:
public function a($id)
{ $criteria=new CDbCriteria;
$criteria->compare('USER_id', $id);
return new CActiveDataProvider($this, array(
'criteria'=>$criteria,
));
}
So what i get is a list with all the files. I dont know how to change the compare the USER_id just with the ID from the logged in USER so i can get the rigt files and not all
Hmmm, $criteria->compare('USER_id', $id, true); why do You use third parameter here? Remove it or set as false.
Its
$partialMatch whether the value should consider partial text
match (using LIKE and NOT LIKE operators). Defaults to false, meaning
exact comparison.
To get logged user id in Yii you can use Yii::app()->user->getId();

Check for a many to many relation when listing a resource

I'm implementing relationships in Eloquent, and I'm facing the following problem:
An article can have many followers (users), and a user can follow many articles (by follow I mean, the users get notifications when a followed article is updated).
Defining such a relationship is easy:
class User extends Eloquent {
public function followedArticles()
{
return $this->belongsToMany('Article', 'article_followers');
}
}
also
class Article extends Eloquent {
public function followers()
{
return $this->belongsToMany('User', 'article_followers');
}
}
Now, when listing articles I want to show an extra information about each article: if the current user is or is not following it.
So for each article I would have:
article_id
title
content
etc.
is_following (extra field)
What I am doing now is this:
$articles = Article::with(array(
'followers' => function($query) use ($userId) {
$query->where('article_followers.user_id', '=', $userId);
}
)
);
This way I have an extra field for each article: 'followers` containing an array with a single user, if the user is following the article, or an empty array if he is not following it.
In my controller I can process this data to have the form I want, but I feel this kind of a hack.
I would love to have a simple is_following field with a boolean (whether the user following the article).
Is there a simple way of doing this?
One way of doing this would be to create an accessor for the custom field:
class Article extends Eloquent {
protected $appends = array('is_following');
public function followers()
{
return $this->belongsToMany('User', 'article_followers');
}
public function getIsFollowingAttribute() {
// Insert code here to determine if the
// current instance is related to the current user
}
}
What this will do is create a new field named 'is_following' which will automatically be added to the returned json object or model.
The code to determine whether or not the currently logged in user is following the article would depend upon your application.
Something like this should work:
return $this->followers()->contains($user->id);

Yii Framework: Different view (on actionIndex) depending on user's role

I'm a beginner in Yii Framework, and I have a problem I can't fix. I have this in my class controller :
public function actionIndex()
{
$dataProvider=new CActiveDataProvider('Absence');
$this->render('index',array(
'dataProvider'=>$dataProvider,
));
}
That gives me a list of all 'absence'.
In my case, 'erty' is logged in and sees a list of every absence. But, with his role, stored in my user's table, I want him to see only a list of absences with his 'Collaborateur alias'.
Can someone helps me with it ?
Just create
$criteria=new CDbCriteria;
$criteria->compare('role', $user->role /* replace this with required role*/, true);
And attach it into DataProvider
$dataProvider = new CActiveDataProvider('Absence', array( 'criteria'=>$criteria));
Better still, and as it's a business rule, it should go in the Absence data model.
So you can add a scope in your Absence data model:
'mine'=>array(
'order'=>'a_sort_column DESC',
'condition'=>'role=:role',
'params'=>array(
'owner'=>Yii::app()->user->getState('roles'),
),
),
and then in your code use
$dataProvider=new CActiveDataProvider(Absence::model()->mine())
If its relevant you can always use a default scope if this filter is always applied.
If this filter is always applied except in an admin context, think about using another class that extends the Absence model and applies the default scope like
class myAbsence extends Absence
{
public function defaultScope() {
return array(
'order'=>'a_sort_column DESC',
'condition'=>'role=:role',
'params'=>array(
'owner'=>Yii::app()->user->getState('roles'),
),
);
}
and then in your non-Admin controllers you would use
$dataProvider=new CActiveDataProvider('myAbsence')

record level access in cakephp

I want want to restrict the records based on the user logged in viewing the records.
For example I have following two models:
Assignments
Users
So if a student (user) is viewing the summary of assignments he should be able to see only his assignments and able to perform view/delete/edit only his assignment.
But if a teacher (user) is viewing summary of assignments then he should see all assignments and can perform add/edit/delete on all assignments.
I am already aware I can do this by putting group conditions in find and then appropriate in code in view/edit/delete actions also.
My Question is - what is the best ways to handle scenarios like this in cakephp? Putting conditions everywhere don't seems good way to me.
Consider as two separate problems to solve
Access control
The first is how to deny students who e.g. just manipulate the url to attempt to view/edit/delete things they don't own. For that use isAuthorized, there's an example in the book, adapted to the info in the question that'd be:
// app/Controller/AppController.php
public $components = array(
'Session',
'Auth' => array(
'authorize' => array('Controller') // Added this line
)
);
public function isAuthorized($user) {
// Teachers can access/do everything - adapt to whatever identifies a teacher
if ($user['is_teacher'])) {
return true;
}
// Anyone logged in can access the index
if ($this->action === 'index') {
return true;
}
// The owner of a whatever can view, edit and delete it
$id = $this->request->params['pass'][0];
$owner = $this->{$this->modelClass}->field('user_id', array('id' => $id));
if ($owner === $user['id'])) {
return true;
}
// Default deny
return false;
}
Restrict student data
The event system, available since 2.1, is an easy way to enforce the data restriction mentioned. Again there's a relevant example in the book, adapted to the information in the question, that'd be:
// app/Controller/AssignmentsController.php
public function beforeFilter() {
if (!$this->Auth->user('is_teacher')) {
$currentUser = $this->Auth->user('id');
$this->Assignment->getEventManager()->attach(
function(CakeEvent $e) use ($currentUser) {
$e->data[0]['conditions']['user_id'] = $currentUser;
return $e->data[0];
},
'Model.beforeFind'
);
}
}
This will add a user_id condition to all finds, therefore the index listing will show only their own assignments, whereas for a teacher it will show all assignments.
I would create 2 separate models - StudentAssignments and TeacherAssignments. both models can extend Assignments model. you can then filter your conditions in the beforeFind of each model.
This way, you have decoupled models and you can manipulate them appropriately.
Example:
App::uses('Assignment', 'Model');
class StudentAssignments extends Assignment
{
function beforeFind( $queryData ) {
$queryData['conditions'] = array_merge( (array)$queryData['conditions'],
array( $this->alias.'.user_id' => CakeSession::read('User.id') ) );
return parent::beforeFind($queryData);
}
}
Then you can call $this->StudentAssignments->find('all'); to pull all assignments for a single user

There are <select> populated with (key->value), and field "key" in a model. How I can display it's "value" in a view, instead of "key"?

In model:
public function getOptionsGender()
{
array(0=>'Any', 1=>Male', 2=>'Female');
}
In view (edit):
echo $form->dropDownList($model, 'gender', $model->optionsGender);
but I have a CDetailView with "raw" attributes, and it displays numbers instead of genders.
$attributes = array(
...
'gender',
)
What is appropriate way to convert these numbers back to genders? Should I do it in a model, replacing fields such as $this->gender = getOptionsGender($this->gender)? Any github examples will be very appreciated.
I had to choose gender, age, city, country etc. in a few views that are not related to this one. Where should I place my getOptionsGender function definitions?
Thank for your help, the problem is solved.
In model:
public function getGenderOptions() { ... }
public function genderText($key)
{
$options = $this->getGenderOptions();
return $options[$key];
}
In view:
$attributes = array(
array (
'name'=>'gender',
'type'=>'raw',
'value'=>$model->genderText($model->gender), //or $this->genderText(...)
),
);
$this->widget('zii.widgets.CDetailView', array(
'data'=>$model,
'attributes'=>$attributes,
));
The working example can be found here:
https://github.com/cdcchen/e23passport/blob/c64f50f9395185001d8dd60285b0798098049720/protected/controllers/UserController.php
In Jeffery Winsett's book "Agile Web Application Development with Yii 1.1", he deals with the issue using class constants in the model you are using. In your case:
class Model extends CActiveRecord
{
const GENDER_ANY=0;
const GENDER_MALE=1;
const GENDER_FEMALE=2;
public function getGenderOptions(){
return array(
self::GENDER_ANY=>'Any',
self::GENDER_MALE=>'Male',
self::GENDER_FEMALE=>'Female',
);
}
public function getGenderText(){
$genderOptions=$this->genderOptions();
return isset($genderOptions[$this->gender]) ? $genderOptions[$this->gender] : "unkown gender({$this->gender})";
}
}
Then in your CDetailView you would have to alter it from gender to:
array(
'name'=>'gender',
'value'=>CHtml::encode($model->genderText()),
),
If several models have the same data, you may want to create a base model that extends CActiveRecord and then extend the new model instead of CActiveRecord. If this model is the only one with that data (ie User model only has gender), but other views use that model to display data, then I would leave it just in the single model class. Also keep in mind that if you place getGenderOptions in the extended class, and you extend ALL your models, they will all have that option available, but may not have the attributes needed and will throw an error if you aren't checking for it.
All this being said, I still think it is a matter or preference. You can handle it however you want, wherever you want. This is just one example from a book I have specifically on Yii.

Categories