order user roles and permissions (#1372)
This commit is contained in:
@@ -11,12 +11,15 @@ namespace App\Controller;
|
|||||||
|
|
||||||
use App\Entity\Role;
|
use App\Entity\Role;
|
||||||
use App\Entity\RolePermission;
|
use App\Entity\RolePermission;
|
||||||
|
use App\Event\PermissionSectionsEvent;
|
||||||
use App\Form\RoleType;
|
use App\Form\RoleType;
|
||||||
|
use App\Model\PermissionSection;
|
||||||
use App\Repository\RolePermissionRepository;
|
use App\Repository\RolePermissionRepository;
|
||||||
use App\Repository\RoleRepository;
|
use App\Repository\RoleRepository;
|
||||||
use App\Security\RolePermissionManager;
|
use App\Security\RolePermissionManager;
|
||||||
use App\Security\RoleService;
|
use App\Security\RoleService;
|
||||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||||
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
@@ -53,7 +56,7 @@ final class PermissionController extends AbstractController
|
|||||||
* @Route(path="", name="admin_user_permissions", methods={"GET", "POST"})
|
* @Route(path="", name="admin_user_permissions", methods={"GET", "POST"})
|
||||||
* @Security("is_granted('role_permissions')")
|
* @Security("is_granted('role_permissions')")
|
||||||
*/
|
*/
|
||||||
public function permissions()
|
public function permissions(EventDispatcherInterface $dispatcher)
|
||||||
{
|
{
|
||||||
$all = $this->roleRepository->findAll();
|
$all = $this->roleRepository->findAll();
|
||||||
$existing = [];
|
$existing = [];
|
||||||
@@ -75,9 +78,75 @@ final class PermissionController extends AbstractController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// be careful, the order of the search keys is important!
|
||||||
|
$permissionOrder = [
|
||||||
|
new PermissionSection('User', '_user'),
|
||||||
|
new PermissionSection('User profile (own)', '_own_profile'),
|
||||||
|
new PermissionSection('User profile (other)', '_other_profile'),
|
||||||
|
new PermissionSection('Customer (Teamlead)', '_teamlead_customer'),
|
||||||
|
new PermissionSection('Customer (Team member)', '_team_customer'),
|
||||||
|
new PermissionSection('Customer (Admin)', '_customer'),
|
||||||
|
new PermissionSection('Project (Teamlead)', '_teamlead_project'),
|
||||||
|
new PermissionSection('Project (Team member)', '_team_project'),
|
||||||
|
new PermissionSection('Project (Admin)', '_project'),
|
||||||
|
new PermissionSection('Activity', '_activity'),
|
||||||
|
new PermissionSection('Timesheet (own)', '_own_timesheet'),
|
||||||
|
new PermissionSection('Timesheet (other)', '_other_timesheet'),
|
||||||
|
new PermissionSection('Timesheet', '_timesheet'),
|
||||||
|
new PermissionSection('Export', '_export'),
|
||||||
|
new PermissionSection('Invoice', '_invoice'),
|
||||||
|
new PermissionSection('Teams', '_team'),
|
||||||
|
new PermissionSection('Tags', '_tag'),
|
||||||
|
];
|
||||||
|
|
||||||
|
$event = new PermissionSectionsEvent();
|
||||||
|
foreach ($permissionOrder as $section) {
|
||||||
|
$event->addSection($section);
|
||||||
|
}
|
||||||
|
$dispatcher->dispatch($event);
|
||||||
|
|
||||||
|
$permissionSorted = [];
|
||||||
|
$other = [];
|
||||||
|
|
||||||
|
foreach ($event->getSections() as $section) {
|
||||||
|
$permissionSorted[$section->getTitle()] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->manager->getPermissions() as $permission) {
|
||||||
|
$found = false;
|
||||||
|
|
||||||
|
foreach ($event->getSections() as $section) {
|
||||||
|
if ($section->filter($permission)) {
|
||||||
|
$permissionSorted[$section->getTitle()][] = $permission;
|
||||||
|
$found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$found) {
|
||||||
|
$other[] = $permission;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ksort($permissionSorted);
|
||||||
|
|
||||||
|
$permissionSorted['Other'] = $other;
|
||||||
|
|
||||||
|
// order the roles from most powerful to least powerful, custom roles at the end
|
||||||
|
$roles = [
|
||||||
|
'ROLE_SUPER_ADMIN' => null,
|
||||||
|
'ROLE_ADMIN' => null,
|
||||||
|
'ROLE_TEAMLEAD' => null,
|
||||||
|
'ROLE_USER' => null,
|
||||||
|
];
|
||||||
|
foreach ($this->roleRepository->findAll() as $role) {
|
||||||
|
$roles[$role->getName()] = $role;
|
||||||
|
}
|
||||||
|
|
||||||
return $this->render('user/permissions.html.twig', [
|
return $this->render('user/permissions.html.twig', [
|
||||||
'roles' => $this->roleRepository->findAll(),
|
'roles' => array_values($roles),
|
||||||
'permissions' => $this->manager->getPermissions(),
|
'permissions' => $this->manager->getPermissions(),
|
||||||
|
'sorted' => $permissionSorted,
|
||||||
'manager' => $this->manager,
|
'manager' => $this->manager,
|
||||||
'system_roles' => $this->roleService->getSystemRoles(),
|
'system_roles' => $this->roleService->getSystemRoles(),
|
||||||
]);
|
]);
|
||||||
|
|||||||
39
src/Event/PermissionSectionsEvent.php
Normal file
39
src/Event/PermissionSectionsEvent.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Kimai time-tracking app.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Event;
|
||||||
|
|
||||||
|
use App\Model\PermissionSectionInterface;
|
||||||
|
use Symfony\Contracts\EventDispatcher\Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This event can be used, to dynamically add sections to the permission screen.
|
||||||
|
*/
|
||||||
|
final class PermissionSectionsEvent extends Event
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $sections;
|
||||||
|
|
||||||
|
public function addSection(PermissionSectionInterface $section): PermissionSectionsEvent
|
||||||
|
{
|
||||||
|
$this->sections[] = $section;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return PermissionSectionInterface[]
|
||||||
|
*/
|
||||||
|
public function getSections(): array
|
||||||
|
{
|
||||||
|
return $this->sections;
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/Model/PermissionSection.php
Normal file
38
src/Model/PermissionSection.php
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Kimai time-tracking app.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Model;
|
||||||
|
|
||||||
|
class PermissionSection implements PermissionSectionInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $title;
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $filter;
|
||||||
|
|
||||||
|
public function __construct(string $title, string $strposFilter)
|
||||||
|
{
|
||||||
|
$this->title = $title;
|
||||||
|
$this->filter = $strposFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle(): string
|
||||||
|
{
|
||||||
|
return $this->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function filter(string $permission): bool
|
||||||
|
{
|
||||||
|
return strpos($permission, $this->filter) !== false;
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/Model/PermissionSectionInterface.php
Normal file
28
src/Model/PermissionSectionInterface.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Kimai time-tracking app.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Model;
|
||||||
|
|
||||||
|
interface PermissionSectionInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns the section title (which will be translated).
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getTitle(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the given permission is part of this section
|
||||||
|
*
|
||||||
|
* @param string $permission
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function filter(string $permission): bool;
|
||||||
|
}
|
||||||
@@ -7,10 +7,10 @@
|
|||||||
{% set columns = {
|
{% set columns = {
|
||||||
'label.name': 'alwaysVisible',
|
'label.name': 'alwaysVisible',
|
||||||
} %}
|
} %}
|
||||||
{% set canEditPedrmissions = is_granted('role_permissions') %}
|
{% set canEditPermissions = is_granted('role_permissions') %}
|
||||||
{% for role in roles %}
|
{% for role in roles %}
|
||||||
{% set options = {'class': 'alwaysVisible text-center'} %}
|
{% set options = {'class': 'alwaysVisible text-center'} %}
|
||||||
{% if canEditPedrmissions and role.name not in system_roles|keys %}
|
{% if canEditPermissions and role.name not in system_roles|keys %}
|
||||||
{% set widget %}
|
{% set widget %}
|
||||||
<a href="{{ path('admin_user_role_delete', {'id': role.id}) }}" class="confirmation-link" data-question="confirm.delete" data-msg-error="action.delete.error" data-msg-success="action.delete.success">{{ widgets.icon('trash') }}</a>
|
<a href="{{ path('admin_user_role_delete', {'id': role.id}) }}" class="confirmation-link" data-question="confirm.delete" data-msg-error="action.delete.error" data-msg-success="action.delete.success">{{ widgets.icon('trash') }}</a>
|
||||||
{% endset %}
|
{% endset %}
|
||||||
@@ -29,7 +29,14 @@
|
|||||||
{% block main %}
|
{% block main %}
|
||||||
{{ tables.datatable_header(tableName, columns, null, {'translationPrefix': ''}) }}
|
{{ tables.datatable_header(tableName, columns, null, {'translationPrefix': ''}) }}
|
||||||
|
|
||||||
{% for permission in permissions|sort %}
|
{% set colspan = 1 + (roles|length) %}
|
||||||
|
|
||||||
|
{% for title, perms in sorted %}
|
||||||
|
{% if perms|length > 0 %}
|
||||||
|
<tr class="summary info">
|
||||||
|
<td colspan="{{ colspan }}">{{ title }}</td>
|
||||||
|
</tr>
|
||||||
|
{% for permission in perms|sort %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ permission }}</td>
|
<td>{{ permission }}</td>
|
||||||
{% for role in roles %}
|
{% for role in roles %}
|
||||||
@@ -45,6 +52,8 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
{{ tables.data_table_footer(permissions) }}
|
{{ tables.data_table_footer(permissions) }}
|
||||||
|
|
||||||
@@ -53,6 +62,8 @@
|
|||||||
{% block javascripts %}
|
{% block javascripts %}
|
||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
document.addEventListener('kimai.initialized', function() {
|
||||||
KimaiReloadPageWidget.create('kimai.userRoleUpdate');
|
KimaiReloadPageWidget.create('kimai.userRoleUpdate');
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ abstract class ControllerBaseTest extends WebTestCase
|
|||||||
*/
|
*/
|
||||||
protected function assertDataTableRowCount(Client $client, string $id, int $count)
|
protected function assertDataTableRowCount(Client $client, string $id, int $count)
|
||||||
{
|
{
|
||||||
$node = $client->getCrawler()->filter('section.content div#' . $id . ' table.table-striped tbody tr');
|
$node = $client->getCrawler()->filter('section.content div#' . $id . ' table.table-striped tbody tr:not(.summary)');
|
||||||
self::assertEquals($count, $node->count());
|
self::assertEquals($count, $node->count());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user