How to pull data for and display an Association as a multiple-select checkbox in CakePHP 3? - cakephp

I'm setting up a user access model with Roles in a separate table, and linked to Users by a UserRoles table.
I currently have the following in Model/Table/UsersTable.php:
$this->belongsToMany('Roles', [
'through' => 'UserRoles'
]);
and the following in Model/Table/RolesTable.php:
$this->belongsToMany('Users', [
'through' => 'UserRoles'
]);
and the following in Model/Table/UserRolesTable.php:
$this->belongsTo('Users', [
'foreignKey' => 'user_id'
]);
$this->belongsTo('Roles', [
'foreignKey' => 'role_id'
]);
I have 3 different roles created, 'viewer', 'creator', and 'administrator'. I've successfully set privileges based on user types. Where I am getting stuck is adding roles to a user via an association form.
Right now I have given Administrator users the ability to edit user information. This works for basic information that I have in the Users table, but I can't figure out how to set up the form field for the associated Role. I would like it to be a checkbox where the Administrator can select each privilege for the user.
I'm currently doing this, which is not giving me what I want:
echo $this->Form->input('Users.role', ['type' => 'checkbox']);
This is giving me a single checkbox with the label "Role". I want to pull each row from my Roles table and list them all as options.
I have a few questions relating to this:
1) This seems really elementary but I'm just not finding it clearly stated. What code do I need in my UsersController to pull the list of all Roles? (not just those associated with the current User, but all objects in the Roles table.)
2) What form input code do I need to display checkboxes with all possible Roles, and show the current user privileges (in my UserRoles table) as already checked off? I think I need something like this in my form:
echo $this->Form->select('User.Role', $options, ['multiple' => 'checkbox']);
...but I can't tell what $options should be, and how to set already-selected values.
I am currently pulling Roles with my User object to be edited:
$user = $this->Users->get($id, [
'contain' => ['Roles'],
'Users.id' => $this->Auth->user('id')
]);
...but I'm having trouble converting it into a checkbox form selection.
Thanks so much.

In order to create a list of checkbox out of some options, you first need to send the options from the controller:
$this->set('roles', $this->Users->Roles->find('list'));
Then, in your template add a multiple => checkbox input:
echo $this->Form->input('roles', ['multiple' => 'checkbox', 'options' => $roles]);
It is not necessary to prefix your input names with User. just name your inputs as the properties of a User entity.

Related

Cakephp4, how to load associated data in an Entity object?

I have a Users table and a Roles table.
A user has one role. So in UsersTable.php:
$this->belongsTo('Roles', [
'foreignKey' => 'role_id',
'joinType' => 'INNER',
]);
Now in User.php (Entity!!) I need the role name of the users role.
But I have only the User Entity in which is no associated data.
I now have:
public function getRole()
{
$q = TableRegistry::getTableLocator()->get('Roles');
$roles = $q->find('list')->toArray();
return $roles[$this->role_id];
}
This works, but TableRegistry is marked obsolete in cake4, and I can't find any other way the make this work. What is the propper way of doing this?
If you want to use both the User and Role data in a controller you can just do for example
$user = $this->Users->get($id, [
'contain' => ['Roles'],
]);
and the resulting object has $user->role defined as a Role entity.
However, I have a feeling you already know this and have another issue that may or may not overlap with a problem I was solving just recently. Check out ndm's answer to my question here and hopefully it helps you! CakePHP 4.1 User entity as authorization identity associated fields
Apologies if that's not your scenario, I just found this current thread while looking for an answer to my problem, and it was definitely the closest to my own issue.

CakePHP 3.5 Auth use multiple tables

I have an Auth process which works fine with one userModel. But not only because of my DB schema I need to have one login method/action which works with multiple models.
So far I've tried everything I was able to think of or find online - for example editing this Cake 1.3 solution into Cake 3 and a few more hints I was able to find.
However, I'm not able to figure it out.
Thank you for any answer.
My AppController component load:
$this->loadComponent('ExtendedAuth', [
'authenticate' => [
'Form' => [
//'userModel' => 'Admins',
'fields' => [
'username' => 'email',
'password' => 'password'
]
]
],
'loginAction' => [
'controller' => 'Admins',
'action' => 'login'
],
// If unauthorized, return them to page they were just on
'unauthorizedRedirect' => $this->referer(),
]);
My ExtendedAuthComponent:
class ExtendedAuthComponent extends AuthComponent
{
function identify($user = null, $conditions = null) {
$models = array('Admins', 'Users');
foreach ($models as $model) {
//$this->userModel = $model; // switch model
parent::setConfig('authenticate', [
AuthComponent::ALL => [
'userModel' => $model
]
]);
$result = parent::identify(); // let cake do its thing
if ($result) {
return $result; // login success
}
}
return null; // login failure
}
}
EDIT1: Description of situation
I have two separate tables (Admins, Users). I need just one login action which tries to use Admins table prior to Users. Because of the application logic I can't combine them to one table with something like 'is_admin' flag. So basically what I need is instead of one specific userModel set in Auth config, I need a set of models. Sounds simple and yet I'm not able to achieve it.
EDIT2: Chosen solution
Based on the answer below, I decided to update my schema. Auth users table is just simplified table with login credentials and role and other role-specific fields are then in separate tables which are used as a connection for other role-specific tables. Even though the answer is not exactly a solution for the asked question, it made me think more about any possible changes of the schema and I found this solution because of it so I'm marking it as a solution. I appreciate all comments as well.
As Mark already said in a comment: Don't use two users tables. Add a type field or role or whatever else and associated data in separate tables if it's different like admin_profiles and user_profiles.
Don't extend the Auth component. I wouldn't recommend to use it anymore any way because it's going to get deprecated in the upcoming 3.7 / 4.0 release. Use the new official authentication and authorization plugins instead.
If you insist on the rocky path and want to make your life harder, well go for it but then you should still not extend the auth component but instead write a custom authentication adapter. This is the right place to implement your custom 2-table-weirdness. Read this section of the manual on how to do it.

Simplified and manageable ACL implementation in cakephp

I went through complete lesson on cakephp's ACL component, but gigantic ACL component do not seem to meet my very simple requirements.
I have only group based access control, three groups are users, managers and administrators the fourth is a anonymous users without logins for which I am not creating any group.
from acl concept it creates three table
aros -> this looks somewhat redundant data copied from groups table, I dont even need to have a group table but just field group_id in users table.
acos -> this is a list of public methods in controllers, I had to use AclExtra plugin to populate over 250+ actions in table, now this is the part which I think un-manageable, I noticed that tool used to populate acos table cannot reliably sync everytime when I do changes in controllers, the same work must be done at remote site for each changes that means terrible thing! this also mean i have to have a database backup during updates and migration.
Other side if I use php file based acos that is again un-manageable because we have to make sure syncing between controller and acl file.
aros_acos -> obviously
can we have a simpler mechanism something like i deny all actions using Auth component and then inside each action or maybe in beforeRender method i can specify what methods are open to what group ?
Thanks
There is an undocumented acl class PhpAcl it is much simpler to use than then the database driven ACL and more dynamic than the ini bassed ACL.
In Config/core.php
/**
* The class name and database used in CakePHP's
* access control lists.
*/
Configure::write('Acl.classname', 'PhpAcl');
// Configure::write('Acl.database', 'default');
This tells your ACL to use the PhpAcl
Then open up Config/acl.php
There are some good instructions there
Assumptions:
In your application you created a User model with the following properties: username, group_id, password, email, firstname,
lastname and so on.
You configured AuthComponent to authorize actions via $this->Auth->authorize = array('Actions' => array('actionPath' =>
'controllers/'),...)
Now, when a user (i.e. jeff) authenticates successfully and requests a
controller action (i.e. /invoices/delete) that is not allowed by
default (e.g. via $this->Auth->allow('edit') in the Invoices
controller) then AuthComponent will ask the configured ACL interface
if access is granted. Under the assumptions 1. and 2. this will be
done via a call to Acl->check() with
array('User' => array('username' => 'jeff', 'group_id' => 4, ...))
as ARO and
'/controllers/invoices/delete'
as ACO.
I wanted to use static names for Groups or Roles so you can add a role field to your user table, and then set up the $map like this:
**
* The role map defines how to resolve the user record from your application
* to the roles you defined in the roles configuration.
*/
$config['map'] = array(
'User' => 'User/username',
'Role' => 'User/role',
);
For my app we aren't using user based permissions only role, so we could remove the User from the $map.
Then you need to set up some roles:
/**
* role configuration
*/
$config['roles'] = array(
'Role/admin' => null,
);
Any role not in this array will get 'Role/default'
Now just set up your permissions, they are pretty self explanatory.
/**
* rule configuration
*/
$config['rules'] = array(
'allow' => array(
'*' => 'Role/admin',
'controllers/Reports/*' => 'Role/default',
'controllers/EurRates/*' => 'Role/default',
'controllers/Posts/index' => 'Role/default',
'controllers/Users/(edit|index)' => 'Role/default',
),
'deny' => array(
'controllers/ProtectedController/*' => 'Role/default',
'controllers/EurRates/(edit|add|delete)' => 'Role/default',
'controllers/Reports/(edit|add|delete)' => 'Role/default',
),
);
That's it, now you can allow or deny permission to actions based on role.

how to save one-to-many relationships to the database using zend form and doctrine

I'm writing an application that uses Zend Framework 2 and Doctrine (both the latest stable version).
There is much documenation (mainly tutorials and blog posts) that deal with saving doctrine entities to the database in combination with Zend Form. Unfortunately they only deal with simple entities that do not have one-to-many or many-to-many relationships.
This is one of those examples that i have adopted into my own code.
http://www.jasongrimes.org/2012/01/using-doctrine-2-in-zend-framework-2/
I understand that in the Album Entity of this example, the artist is a string to keep the (already lengthy) tutorial as simple as possible. But in a real world situation this would of course be a one-to-many releationship with an Artist Entity (or even a many-to-many). In the view, a select-box could be displayed where the artist can be selected, listing all the artist-entities that could be found in the database, so the right one can be selected.
Following the example with the album, this is how i've set up an 'edit' Action in my controller:
public function editAction()
{
// determine the id of the album we're editing
$id = $this->params()->fromRoute("id", false);
$request = $this->getRequest();
// load and set up form
$form = new AlbumForm();
$form->prepareElements();
$form->get("submit")->setAttribute("label", "Edit");
// retrieve album from the service layer
$album = $this->getSl()->get("Application\Service\AlbumService")->findOneByAlbumId($id);
$form->setBindOnValidate(false);
$form->bind($album);
if ($request->isPost()) {
$form->setData($request->getPost());
if ($form->isValid()) {
// bind formvalues to entity and save it
$form->bindValues();
$this->getEm()->flush();
// redirect to album
return $this->redirect()->toRoute("index/album/view", array("id"=>$id));
}
}
$data = array(
"album" => $album,
"form" => $form
);
return new ViewModel($data);
}
How would this example need to be altered if the artist wasn't a string, but an Artist Entity?
And suppose the album also has multiple Track Entities, how would those be processed?
The example would not need to be changed at all, the changes will happen with your entity and your form.
This is a good reference: Doctrine Orm Mapping
So to save yourself a lot of extra work, your OnToMany relationship would use: cascade = persist:
/**
* #ORM\OneToMany(targetEntity="Artist" , mappedBy="album" , cascade={"persist"})
*/
private $artist;
When it comes to persisting the form object, the entity knows it must save the associated entity as well. If you did not include this, then you would have to do it manually using a collection.
To make like easier with your form, you can use Doctrines Object Select like this:
$this->add(
[
'type' => 'DoctrineModule\Form\Element\ObjectSelect',
'name' => 'artist',
'options' => [
'object_manager' => $this->objectManager,
'target_class' => 'Artist\Entity\Artist',
'property' => 'name', //the name field in Artist, can be any field
'label' => 'Artist',
'instructions' => 'Artists connected to this album'
],
'attributes' => [
'class' => '', //Add any classes you want in your form here
'multiple' => true, //You can select more than one artist
'required' => 'required',
]
]
);
So now your form takes care of the collection for you, the controller as per your example does not need to change since the entity will take care of the persisting...
Hope this gets you on track.

Using Drupal7 custom entities in views

I have created two custom entities using hook_entity_info in drupal 7.The entities is created for the given database tables.
I am able to create a view for each of the entities separately.But want to create a view of both entities together.The "relationship" option in view displays "no relationship available". And add fields option displays only the field of the secleted entity.
How do I relate both entities?
I was able to come with two solutions:
1)using relation ,relation end field,relation UI
2)using hook_views_data_alter example from commerce module:
Function hook_views_data_alter(){
// Expose the uid as a relationship to users.
$data['users']['uc_orders'] = array(
'title' => t('Orders'),
'help' => t('Relate a user to the orders they have placed. This relationship will create one record for each order placed by the user.'),
'relationship' => array(
'base' => 'uc_orders',
'base field' => 'uid',
'relationship field' => 'uid',
'handler' => 'views_handler_relationship',
'label' => t('orders'),
),
);
}

Resources