CakePHP - Add custom values to User Object - cakephp

I have added 4 more columns to my CakePHP Users table and I am trying to figure out how I can include these columns in the $this->Auth->user() object.
I've added the column information to my Users Model and Entity but still no joy. Currently my user object looks like this;
[
'id' => (int) 1,
'username' => 'admin',
'name' => 'Web Admin',
'email' => 'webteam#',
'role' => 'admin',
'created' => object(Cake\I18n\Time) {
'time' => '2016-02-09T16:04:46+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'modified' => object(Cake\I18n\Time) {
'time' => '2016-02-12T08:53:16+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
}
]
Where is this object created and is there a way I can add my custom values to it, without editing core CakePHP files?
Thanks for your help .

By default the built-in authenticators will fetch all fields in the tables schema.
You most probably just forgot to clear your cache (tmp/cache/models), which you should do whenever you make changes to your schemas.
In case one would want to specify what fields are being fetched, a custom finder would be needed.
See Cookbook > Controllers > Components > Authentication > Customizing Find Query
$this->loadComponent('Auth', [
'authenticate' => [
'Form' => [
'finder' => 'auth'
]
],
]);
In your UsersTable class
public function findAuth(\Cake\ORM\Query $query, array $options)
{
return $query
->select(['id', 'username', 'password', 'column_x', 'column_y']);
}
It should be noted that the fields required for authentication must always be included, ie like username and password!

Related

Insert/Delete methods for self-referencing belongsToMany association

I’m struggling with a self-referencing belongsToMany association. To be clear I have a Models table and each model can have multiple accessories, which are also models. So I have a linking table, Accessories, with a model_id (the “parent” model) and an accessory_id (the “child” model).
I finally found how to declare this in the ModelsTable :
$this->belongsToMany('AccessoryModels', [
'className' => 'Models',
'through' => 'Accessories',
'foreignKey' => 'model_id',
'targetForeignKey' => 'accessory_id'
]);
$this->belongsToMany('ParentAccessoryModels', [
'className' => 'Models',
'through' => 'Accessories',
'foreignKey' => 'accessory_id',
'targetForeignKey' => 'model_id'
]);
I also got it working to retrieve these data in the Models view.
But I now have some issues for the addAccessory (and deleteAccessory) method in the Models controller and views.
Here is it in the controller :
public function addAccessory($id = null)
{
$model = $this->Models->get($id, [
'contain' => []
]);
if ($this->getRequest()->is(['patch', 'post', 'put'])) {
$accessory = $this->getRequest()->getData();
if ($this->Models->link($model, [$accessory])) {
return $this->redirect(['action' => 'view', $model->id]);
}
}
$models = $this->Models
->find('list', ['groupField' => 'brand', 'valueField' => 'reference'])
->order(['brand' => 'ASC', 'reference' => 'ASC']);
$this->set(compact('model', 'models'));
}
The view is only a select dropdown with the list of all available models (I'm using a plugin, AlaxosForm, but it takes the original CakePHP control() function behaviour) :
echo $this->AlaxosForm->label('accessory_id', __('Accessory'), ['class' => 'col-sm-2 control-label']);
echo $this->AlaxosForm->control('accessory_id', ['options' => $models, 'empty' => true, 'label' => false, 'class' => 'form-control']);
echo $this->AlaxosForm->button(___('Submit'), ['class' => 'btn btn-default']);
The problem is that the addAccessory() function won't work when getting the submitted data from the form. I see the problem, as when posting the inserted values, only an array with one accessory_id is given (for example ['accessory_id' => 1] and the link() doesn't know what to do with it. So I think it’s an issue about data formatting but don’t see how to get it correctly.
Saving links (like any other ORM saving methods) requires to pass entities, anything else will either be ignored, or trigger errors.
So you have to use the accessory_id to obtain an entity first, something like:
$accessoryId = $this->getRequest()->getData('accessory_id');
$accessory = $this->Models->AccessoryModels->get($accessoryId);
Furthermore you need to use the model/table that corresponds to the target entities (second argument) that you want to link (to the first argument), ie you'd have to use AccessoryModels, like:
$this->Models->AccessoryModels->link($model, [$accessory])
See also
Coobook > Database Access & ORM > Saving Data > Associate Many To Many Records

how to create user role wise access control in user and role table joining in cakephp 3?

user table
role table
I just want to allow access control to role table set like: ctrl_view = 1 means this role can view any controller view.
How can I set different action in different role?
Follow conventions, user_role_id should be named "role_id", role_id only "id" and user_name should be "username" or inside your Auth configuration change the default fields name use for your connection form.
public function initialize()
{
//...
$this->loadComponent('Auth', [
'loginRedirect' => [
'controller' => 'Pages',
'action' => 'welcome',
'prefix' => 'admin'
],
'logoutRedirect' => [
'controller' => 'Users',
'action' => 'login',
'prefix' => false
],
'authError' => 'Unauthorized access...',
'authenticate' => [
'Form' => [
'fields' => ['username' => 'user_name', 'password' => 'password']
]
],
'authorize' => 'Controller',
'unauthorizedRedirect' => [
'controller' => 'Pages',
'action' => 'unauthorized'
],
]);
// ...
}
and inside your Appcontroller make somtehing like this
public function isAuthorized($user)
{
if(!is_null($this->Auth->user())): // if user is logged
$action = $this->request->getParam('action'); // get name action
$this->loadModel('Roles'); // load your model Roles
$query = $this->Authorizations->find() // find inside Roles
->where([
'Roles.role_id IN' => $user['user_role_id'], // where role_id is like user_role_id of current user
'Roles.ctl_'.$action => 1 // and where ctl_[action] is set to 1
])->toArray();
if (!empty($query)): // if we find an occurence, we allow the action
return true;
else: // else we don't authorize
return false,
endif;
/* previous lines can be change with this ----> return (!empty($query)); */
else: // if user is not connected we don't allow action
return false
endif;
}
and to finish, i think it's better to use "prefix", with prefix u can simplify your authorisation process (will no prefix i allow, with prefix i check my role table), for this you have to simply add these line in the beginin of your isAuthorized function:
if (!$this->request->getParam('prefix')) {
return true;
}
Hope it helps

CakePHP 3: How to update foreignKey?

I use BlueImp jQuery plugin to upload multiple images in follow scenario:
User open add or edit Albums page, select images and procces upload via ajax method. This work fine, image data (name, size, model, field,..) are stored in related images table without foreign key.
Ajax return id of recent uploaded image, and js create (captions, position) inputs for that file.
User fill other form fields, and click submit.
Problem, if user create new album or edit/update exist one wich not contain images, application does not update foriegn key for new images. But if album contain images, and user add new one to album, after save post cakephp add / update foreign key to new images records.
Debug output:
// first time add images, does not update FK
[
'name' => 'A4',
'images' => [
(int) 116 => [
'caption' => '',
'position' => '1',
'id' => '116'
]
],
'id' => '4',
'user_id' => '1',
'active' => '1'
]
// album has one image, we add new one, CakePHP Update FK for new images
[
'name' => 'A4',
'images' => [
(int) 117 => [
'caption' => '',
'position' => '1',
'id' => '117'
],
(int) 118 => [
'caption' => '',
'position' => '2',
'id' => '118'
]
],
'id' => '4',
'user_id' => '1',
'active' => '1'
]
AlbumsController add method:
public function add()
{
$album = $this->Albums->newEntity();
if ($this->request->is('post')) {
$album = $this->Albums->patchEntity($album, $this->request->data,['associated' => ['Images']]);
if ($this->Albums->save($album)) {
$this->Flash->success(__('The album has been saved.'));
return $this->redirect(['action' => 'index']);
} else {
$this->Flash->error(__('The album could not be saved. Please, try again.'));
}
}
$users = $this->Albums->Users->find('list', ['limit' => 200]);
$this->set(compact('album', 'users'));
$this->set('_serialize', ['album']);
}
Albums Table:
$this->hasMany('Images', [
'foreignKey' => 'foreign_key',
'conditions' => [
'Images.model' => 'Albums',
'Images.field' => 'upload'
],
'sort' => ['Images.position' => 'ASC']
]);
Images Table:
$this->belongsTo('Albums', [
'foreignKey' => 'foreign_key',
'joinType' => 'INNER'
]);
I use same approach in the cakephp 2 apps, and work without problems.
Video: http://screencast-o-matic.com/watch/cDhYYOinCX
Q: How to update foreignKey?
I'm not sure is this correct way, but it's work for me.
AlbumsTable
public function afterSave(Event $event, Entity $entity, ArrayObject $options)
{
if (isset($entity)) {
$images = TableRegistry::get('Images');
$images->query()
->update()
->set(['foreign_key' => $entity->id])
->where([
'foreign_key IS NULL',
'model' => 'Albums'
])
->execute();// conditions
}
}

Pagination sort link on a virtual field/entity property in CakePHP 3.0

I want to create a pagination sort link on a virtual field/entity property in CakePHP 3.0.
In CakePHP 2.x I used to create a virtual field, and then create a pagination sort link on that field. However, in CakePHP 3.0, virtual fields have been replaced by virtual entity properties.
Is there any way I can get this working in CakePHP 3.0?
In my situation, I have a first_name and last_name column, which are combined as full_name in a virtual entity property. I want to sort on the full_name.
As stated in the linked docs, virtual properties cannot be used in finds. That's by design, virtual properties only live in entities, they are built in PHP after data has been retrieved from the database.
So let's forget about virtual properties for a moment, and concentrate on queries and computed columns.
Computed columns need to be specified via sortWhitelist
Just like columns of associated models, computed columns need to be specified in the sortWhitelist option in order to be useable for sorting!
Cookbook > Controllers > Components > Pagination > Control which Fields Used for Ordering
Via pagination options
You have some options here, for example you could define computed columns in the pagination options:
$this->paginate = [
// ...
'sortWhitelist' => [
'id',
'first_name',
'last_name',
'full_name',
// ...
],
'fields' => [
'id',
'first_name',
'last_name',
'full_name' => $this->Table->query()->func()->concat([
'first_name' => 'literal',
'last_name' => 'literal'
]),
// ...
],
'order' => [
'full_name' => 'DESC'
]
];
A custom finder
Another, more reusable option would be to use a custom finder:
$this->paginate = [
// ...
'sortWhitelist' => [
'id',
'first_name',
'last_name',
'full_name',
// ...
],
'finder' => 'withFullName',
'order' => [
'full_name' => 'DESC'
]
];
public function findWithFullName(\Cake\ORM\Query $query, array $options)
{
return $query->select([
'id',
'first_name',
'last_name',
'full_name' => $query->func()->concat([
'first_name' => 'literal',
'last_name' => 'literal'
]),
// ...
]);
}
Cookbook > Controllers > Components > Pagination > Using Controller::paginate()
Cookbook > ... ORM > Retrieving Data & Results Sets > Custom Finder Methods
Separate custom query
It's also possible to directly pass query objects to Controller::paginate():
$this->paginate = [
// ...
'sortWhitelist' => [
'id',
'first_name',
'last_name',
'full_name',
// ...
],
'order' => [
'full_name' => 'DESC'
]
];
$query = $this->Table
->find()
->select(function (\Cake\ORM\Query $query) {
return [
'id',
'first_name',
'last_name',
'full_name' => $query->func()->concat([
'first_name' => 'literal',
'last_name' => 'literal'
]),
// ...
];
});
$results = $this->paginate($query);
Set your default sort order to be the same as your virtual field:
public $paginate = [
'order' => [
'first_name' => 'ASC',
'last_name' => 'ASC',
]
];
Then just add the following to your View to prevent the paginator from overriding the default order unless specified by the user:
if (empty($_GET['direction'])) { $this->Paginator->options(['url' => ['direction' => null, 'sort' => null]]); }

Include a subset of fields in the Auth User session

My Users table has a whole bunch of fields, most of which I don't need/want stored in the Auth User session. How do you restrict which fields are stored in the session for the logged in user?
I know you can choose fields of associated models with the 'contain' key, but normally to select fields of the top-level model, you'd use the 'fields' key. But in the case of Auth, the 'fields' key is used to choose which fields to authenticate the user by, not which fields to include in the session.
To give some context, here's my code so far... what would I do to make it so that only the email and firstname fields are stored in the Auth session, as opposed to all fields in the Users table.
$this->Auth->authenticate = array(
'Blowfish' => array(
'fields' => array(
'username' => 'email',
'password' => 'password',
)
)
);
I've upvoted the answers which were useful, albeit work-around solutions - thanks.
I think the "correct" answer is that there's no way to do this with CakePHP Auth component out of the box, and you have to hack it (eg, using one of the solutions below). I took a look at the _findUser method in BaseAuthenticate.php and it confirms this.
In case a CakePHP core dev is reading (DeEuroMarK?), this is probably a pretty common requirement, and I think it's a feature worth having built in.
Suggested implementation: just include the fields you want as extra fields in the 'fields' array - and just assume that every key other than 'username' and 'password' is an extra field that should be included in the auth session. That way it's consistent with other Model find syntax.
Example:
$this->Auth->authenticate = array(
'Blowfish' => array(
'fields' => array(
'username' => 'email',
'password' => 'password',
'another_field',
'yet_another_field'
)
)
);
in the beforeFilter of my UsersController I have something similar as your login.
Then I set a afterLogin function as the redirect
$this->Auth->loginRedirect = array('controller' => 'users', 'action' => 'afterLogin');
$this->Auth->loginRedirectTrue = array('controller' => 'users', 'action' => 'index');
$this->Auth->logoutRedirect = array('controller' => 'pages', 'action' => 'display');
the login function dus some checks and afterwards redirects to
if ($this->Auth->login()){
// code here
$this->redirect($this->Auth->redirect());
}
and afterLogin function like this
function afterLogin(){
$session = $this->Session->read('Auth');
$user_id = $session['User']['id'];
// change this to find only the fields you need and then override the Auth.User...
$user = $this->User->findById($user_id);
if (!empty($user)){
$this->Session->write('Auth.UserProfile', $user['UserProfile']);
}
$this->redirect($this->Auth->loginRedirectTrue);
}
You should change the findById to suit your needs and then override the Auth.User fields in the session.
Good Luck!
I think the simplest is to add something like this:
add a contain to your Auth-Component configuration
$this->loadComponent('Auth', [
'authorize' => 'Controller',
'loginRedirect' => [
'controller' => 'Users',
'action' => 'index'
],
'logoutRedirect' => [
'controller' => 'Users',
'action' => 'login'
],
'authenticate' => [
'Form' => [
'fields' => ['username' => 'email'],
'contain' => ['Groups']
]
],
'unauthorizedRedirect' => $this->referer()
]);
...
And in you login-action save the user in session:
$foundUser = $this->Auth->identify();
if ($foundUser) {
$this->Auth->setUser($foundUser);
}
...
this will add the containing groups to the Auth.User
The is for CakePhp3 - in older versions it may be different.

Resources