Search code examples
laravellaravel-backpacklaravel-permission

Laravel Backpack Permission Manager - Roles/Permissions do not load with custom UserCrudController?


I am using Laravel-permission ^5.10 and Laravel Backpack Permission Manager ^6.0 with Laravel ^10 and Backpack Pro ^1.6.

I am using a custom UserCrudController as per the PermissionManager docs where I have essentially copied and very slightly modified PermissionManager's UserCrudController.php and updated the requests to control for specific validation required in my model and to display some other columns in the list.

For some reason, when editing a user, the UI for the roles is being loaded but none of the values are populated on load nor saved when updated.

The roles are displaying properly in the list (I've added a column to show them there), and of course the User model is using CrudTrait and HasRoles accordingly. When using the original UserCrudController, the edit view is indeed working correctly (but failing to update due to validation on the name field which my User table does not have).

My custom UserCrudController.php is as follows (almost identical to the original):

<?php

namespace App\Http\Controllers\Admin;

use Backpack\CRUD\app\Http\Controllers\CrudController;
use Backpack\PermissionManager\app\Http\Requests\UserStoreCrudRequest as StoreRequest;
use App\Http\Requests\UserUpdateCrudRequest as UpdateRequest;
use Illuminate\Support\Facades\Hash;

/**
 * Class UserCrudController
 * @package App\Http\Controllers\Admin
 * @property-read \Backpack\CRUD\app\Library\CrudPanel\CrudPanel $crud
 */
class UserCrudController extends CrudController
{
    use \Backpack\CRUD\app\Http\Controllers\Operations\ListOperation;
    use \Backpack\CRUD\app\Http\Controllers\Operations\CreateOperation {
        store as traitStore;
    }
    use \Backpack\CRUD\app\Http\Controllers\Operations\UpdateOperation {
        update as traitUpdate;
    }
    use \Backpack\CRUD\app\Http\Controllers\Operations\DeleteOperation;

    /**
     * Configure the CrudPanel object. Apply settings to all operations.
     *
     * @return void
     */
    public function setup()
    {
        $this->crud->setModel(config('backpack.permissionmanager.models.user'));
        $this->crud->setEntityNameStrings(
            trans('backpack::permissionmanager.user'),
            trans('backpack::permissionmanager.users')
        );
        $this->crud->setRoute(backpack_url('user'));
    }

    /**
     * Define what happens when the List operation is loaded.
     *
     * @see  https://backpackforlaravel.com/docs/crud-operation-list-entries
     * @return void
     */
    protected function setupListOperation()
    {
        $this->crud->addColumns([
            [
                'name' => 'username',
                'type' => 'text',
            ],
            [
                'name' => 'name',
                'type' => 'text',
                'value' => function ($entry) {
                    return $entry->profileName();
                },
                'searchLogic' => function ($query, $column, $searchTerm) {
                    $query->orWhereHas('profile', function ($q) use (
                        $column,
                        $searchTerm
                    ) {
                        $q->where('first_name', 'like', '%' . $searchTerm . '%')
                            ->orWhere(
                                'last_name',
                                'like',
                                '%' . $searchTerm . '%'
                            )
                            ->orWhere(
                                'middle_name',
                                'like',
                                '%' . $searchTerm . '%'
                            );
                    });
                },
            ],
            [
                'name' => 'email',
                'label' => trans('backpack::permissionmanager.email'),
                'type' => 'email',
            ],
            [
                // n-n relationship (with pivot table)
                'label' => trans('backpack::permissionmanager.roles'), // Table column heading
                'type' => 'select_multiple',
                'name' => 'roles', // the method that defines the relationship in your Model
                'entity' => 'roles', // the method that defines the relationship in your Model
                'attribute' => 'name', // foreign key attribute that is shown to user
                'model' => config('permission.models.role'), // foreign key model
            ],
            [
                // n-n relationship (with pivot table)
                'label' => trans(
                    'backpack::permissionmanager.extra_permissions'
                ), // Table column heading
                'type' => 'select_multiple',
                'name' => 'permissions', // the method that defines the relationship in your Model
                'entity' => 'permissions', // the method that defines the relationship in your Model
                'attribute' => 'name', // foreign key attribute that is shown to user
                'model' => config('permission.models.permission'), // foreign key model
            ],
        ]);

        if (backpack_pro()) {
            // Role Filter
            $this->crud->addFilter(
                [
                    'name' => 'role',
                    'type' => 'dropdown',
                    'label' => trans('backpack::permissionmanager.role'),
                ],
                config('permission.models.role')
                    ::all()
                    ->pluck('name', 'id')
                    ->toArray(),
                function ($value) {
                    // if the filter is active
                    $this->crud->addClause('whereHas', 'roles', function (
                        $query
                    ) use ($value) {
                        $query->where('role_id', '=', $value);
                    });
                }
            );

            // Extra Permission Filter
            $this->crud->addFilter(
                [
                    'name' => 'permissions',
                    'type' => 'select2',
                    'label' => trans(
                        'backpack::permissionmanager.extra_permissions'
                    ),
                ],
                config('permission.models.permission')
                    ::all()
                    ->pluck('name', 'id')
                    ->toArray(),
                function ($value) {
                    // if the filter is active
                    $this->crud->addClause('whereHas', 'permissions', function (
                        $query
                    ) use ($value) {
                        $query->where('permission_id', '=', $value);
                    });
                }
            );
        }
    }

    public function setupCreateOperation()
    {
        $this->addUserFields();
        $this->crud->setValidation(StoreRequest::class);
    }

    public function setupUpdateOperation()
    {
        $this->addUserFields();
        $this->crud->setValidation(UpdateRequest::class);
    }

    /**
     * Store a newly created resource in the database.
     *
     * @return \Illuminate\Http\RedirectResponse
     */
    public function store()
    {
        $this->crud->setRequest($this->crud->validateRequest());
        $this->crud->setRequest(
            $this->handlePasswordInput($this->crud->getRequest())
        );
        $this->crud->unsetValidation(); // validation has already been run

        return $this->traitStore();
    }

    /**
     * Update the specified resource in the database.
     *
     * @return \Illuminate\Http\RedirectResponse
     */
    public function update()
    {
        $this->crud->setRequest($this->crud->validateRequest());
        $this->crud->setRequest(
            $this->handlePasswordInput($this->crud->getRequest())
        );
        $this->crud->unsetValidation(); // validation has already been run

        return $this->traitUpdate();
    }

    /**
     * Handle password input fields.
     */
    protected function handlePasswordInput($request)
    {
        // Remove fields not present on the user.
        $request->request->remove('password_confirmation');
        $request->request->remove('roles_show');
        $request->request->remove('permissions_show');

        // Encrypt password if specified.
        if ($request->input('password')) {
            $request->request->set(
                'password',
                Hash::make($request->input('password'))
            );
        } else {
            $request->request->remove('password');
        }

        return $request;
    }

    protected function addUserFields()
    {
        $this->crud->addFields([
            [
                'name' => 'username',
                'type' => 'text',
            ],
            [
                'name' => 'email',
                'label' => trans('backpack::permissionmanager.email'),
                'type' => 'email',
            ],
            [
                'name' => 'password',
                'label' => trans('backpack::permissionmanager.password'),
                'type' => 'password',
            ],
            [
                'name' => 'password_confirmation',
                'label' => trans(
                    'backpack::permissionmanager.password_confirmation'
                ),
                'type' => 'password',
            ],
            [
                // two interconnected entities
                'label' => trans(
                    'backpack::permissionmanager.user_role_permission'
                ),
                'field_unique_name' => 'user_role_permission',
                'type' => 'checklist_dependency',
                'name' => 'roles,permissions',
                'subfields' => [
                    'primary' => [
                        'label' => trans('backpack::permissionmanager.roles'),
                        'name' => 'roles', // the method that defines the relationship in your Model
                        'entity' => 'roles', // the method that defines the relationship in your Model
                        'entity_secondary' => 'permissions', // the method that defines the relationship in your Model
                        'attribute' => 'name', // foreign key attribute that is shown to user
                        'model' => config('permission.models.role'), // foreign key model
                        'pivot' => true, // on create&update, do you need to add/delete pivot table entries?]
                        'number_columns' => 3, //can be 1,2,3,4,6
                    ],
                    'secondary' => [
                        'label' => mb_ucfirst(
                            trans(
                                'backpack::permissionmanager.permission_plural'
                            )
                        ),
                        'name' => 'permissions', // the method that defines the relationship in your Model
                        'entity' => 'permissions', // the method that defines the relationship in your Model
                        'entity_primary' => 'roles', // the method that defines the relationship in your Model
                        'attribute' => 'name', // foreign key attribute that is shown to user
                        'model' => config('permission.models.permission'), // foreign key model
                        'pivot' => true, // on create&update, do you need to add/delete pivot table entries?]
                        'number_columns' => 3, //can be 1,2,3,4,6
                    ],
                ],
            ],
        ]);
    }
}

Solution

  • Nothing stands out to me in in your code that could cause any problems.

    But I think you would have an easier time debugging this if you extended the CrudController provided by PermissionManager. It should look something like this:

    <?php
    
    namespace App\Http\Controllers\Admin;
    
    use Backpack\CRUD\app\Http\Controllers\CrudController;
    use use Backpack\PermissionManager\app\Http\Controllers\UserCrudController as OriginalUserCrudController;
    use Backpack\PermissionManager\app\Http\Requests\UserStoreCrudRequest as StoreRequest;
    use App\Http\Requests\UserUpdateCrudRequest as UpdateRequest;
    use Illuminate\Support\Facades\Hash;
    
    /**
     * Class UserCrudController
     * @package App\Http\Controllers\Admin
     * @property-read \Backpack\CRUD\app\Library\CrudPanel\CrudPanel $crud
     */
    class UserCrudController extends OriginalUserCrudController
    {
        use \Backpack\CRUD\app\Http\Controllers\Operations\ListOperation;
        use \Backpack\CRUD\app\Http\Controllers\Operations\CreateOperation {
            store as traitStore;
        }
        use \Backpack\CRUD\app\Http\Controllers\Operations\UpdateOperation {
            update as traitUpdate;
        }
        use \Backpack\CRUD\app\Http\Controllers\Operations\DeleteOperation;
    
    
        /**
         * Define what happens when the List operation is loaded.
         *
         * @see  https://backpackforlaravel.com/docs/crud-operation-list-entries
         * @return void
         */
        protected function setupListOperation()
        {
            parent::setupListOperation();
    
            // TODO: add more columns if you want
        }
    
    }
    

    Having less code should make it easier to debug. You're just debugging your extra code, not the one in the original UserCrudController.