feat: Implement module permissions system with database-driven access control

- Added `users_permissions` table for managing user permissions.
- Created `PermissionRepository` for handling permission logic.
- Refactored `controls\Users::permissions()` to utilize the new database structure.
- Introduced AJAX endpoint for saving user permissions.
- Enhanced user management UI with permission checkboxes.
- Added vacation management template for handling employee absences.
- Implemented tests for `PermissionRepository`.
This commit is contained in:
2026-02-26 20:17:03 +01:00
parent 76d3ac33a8
commit a4a35c8d62
35 changed files with 2654 additions and 901 deletions

View File

@@ -36,85 +36,79 @@
<link rel="stylesheet" type="text/css" href="/layout/style.css">
</head>
<body class="logged">
<div class="top">
<div class="logo">
<a href="/">crm<span>Pro</span></a>
<div class="sidebar" id="sidebar">
<div class="sidebar-header">
<a href="/" class="sidebar-logo">crm<span>Pro</span></a>
<div class="sidebar-toggle" id="sidebar-toggle"><i class="fa fa-angle-double-left"></i></div>
</div>
<div class="user-nav">
<div class="trigger">
<i class="fa fa-user"></i> <span><?= $this -> user[ 'email' ];?></span>
</div>
<nav class="sidebar-nav">
<ul>
<? $impersonator_user = \S::get_session( 'impersonator_user' );?>
<? if ( is_array( $impersonator_user ) and isset( $impersonator_user['id'] ) and (int)$impersonator_user['id'] === 1 ):?>
<? $can_manage_users = (int)$this -> user['id'] === 1;?>
<? if ( \controls\Users::permissions( $this -> user[ 'id' ], 'tasks' ) ):?>
<li>
<a href="/users/back_to_admin/">Powrot do admina</a>
<a href="/tasks/main_view/"><i class="fa fa-check-square-o"></i> <span>Zadania</span></a>
</li>
<? endif;?>
<? if ( \controls\Users::permissions( $this -> user[ 'id' ], 'projects' ) ):?>
<li>
<a href="/projects/main_view/"><i class="fa fa-folder-open"></i> <span>Projekty</span></a>
</li>
<? endif;?>
<? if ( \controls\Users::permissions( $this -> user[ 'id' ], 'work_time' ) ):?>
<li>
<a href="/tasks/work_time/"><i class="fa fa-clock-o"></i> <span>Czas pracy</span></a>
</li>
<? endif;?>
<? if ( \controls\Users::permissions( $this -> user[ 'id' ], 'finances' ) ):?>
<li>
<a href="/finances/main_view/"><i class="fa fa-money"></i> <span>Finanse</span></a>
</li>
<? endif;?>
<? if ( \controls\Users::permissions( $this -> user[ 'id' ], 'crm' ) ):?>
<li>
<a href="/crm/main_view/"><i class="fa fa-address-book"></i> <span>CRM</span></a>
</li>
<? endif;?>
<? if ( \controls\Users::permissions( $this -> user[ 'id' ], 'wiki' ) ):?>
<li>
<a href="/wiki/main_view/"><i class="fa fa-book"></i> <span>Wiki</span></a>
</li>
<? endif;?>
<? if ( $can_manage_users ):?>
<li class="has-submenu">
<a href="#" class="submenu-toggle"><i class="fa fa-users"></i> <span>U&#380;ytkownicy</span> <i class="fa fa-angle-down submenu-arrow"></i></a>
<ul class="sidebar-submenu">
<li><a href="/users/main_view/"><i class="fa fa-list"></i> <span>Lista</span></a></li>
<li><a href="/users/vacations/"><i class="fa fa-calendar-times-o"></i> <span>Urlopy</span></a></li>
</ul>
</li>
<li id="divider"></li>
<? endif;?>
<li>
<a href="/users/settings/">Ustawienia</a>
</li>
<li id="divider"></li>
<li>
<a href="/users/logout/">Wyloguj się</a>
</li>
</ul>
</nav>
<div class="sidebar-footer">
<? $impersonator_user = \S::get_session( 'impersonator_user' );?>
<? if ( is_array( $impersonator_user ) and isset( $impersonator_user['id'] ) and (int)$impersonator_user['id'] === 1 ):?>
<a href="/users/back_to_admin/" class="sidebar-footer-link"><i class="fa fa-sign-in"></i> <span>Powrót do admina</span></a>
<? endif;?>
<div class="sidebar-user">
<i class="fa fa-user"></i> <span><?= htmlspecialchars( $this -> user[ 'email' ] );?></span>
</div>
<a href="/users/settings/" class="sidebar-footer-link"><i class="fa fa-cog"></i> <span>Ustawienia</span></a>
<a href="/users/logout/" class="sidebar-footer-link logout"><i class="fa fa-sign-out"></i> <span>Wyloguj się</span></a>
</div>
</div>
<div class="main-menu">
<ul>
<? $can_manage_users = (int)$this -> user['id'] === 1;?>
<? if ( \controls\Users::permissions( $this -> user[ 'id' ], 'tasks' ) ):?>
<li>
<a href="/tasks/main_view/">Zadania</a>
</li>
<div class="content-wrapper" id="content-wrapper">
<div class="top-bar">
<div class="top-bar-left">
<i class="fa fa-bars" id="mobile-toggle"></i>
</div>
</div>
<div class="main">
<? if ( $this -> alert ):?>
<div class="alert"><?= htmlspecialchars( $this -> alert );?></div>
<? endif;?>
<? if ( \controls\Users::permissions( $this -> user[ 'id' ], 'projects' ) ):?>
<li>
<a href="/projects/main_view/">Projekty</a>
</li>
<? endif;?>
<? if ( \controls\Users::permissions( $this -> user[ 'id' ], 'work_time' ) ):?>
<li>
<a href="/tasks/work_time/">Czas pracy</a>
</li>
<? endif;?>
<? if ( \controls\Users::permissions( $this -> user[ 'id' ], 'finances' ) ):?>
<li>
<a href="/finances/main_view/">Finanse</a>
</li>
<? endif;?>
<? if ( \controls\Users::permissions( $this -> user[ 'id' ], 'crm' ) ):?>
<li>
<a href="/crm/main_view/">CRM</a>
</li>
<? endif;?>
<? if ( \controls\Users::permissions( $this -> user[ 'id' ], 'wiki' ) ):?>
<li>
<a href="/wiki/main_view/">Wiki</a>
</li>
<? endif;?>
<? if ( $can_manage_users ):?>
<li>
<a href="/users/main_view/">U&#380;ytkownicy</a>
</li>
<? endif;?>
<? if ( \controls\Users::permissions( $this -> user[ 'id' ], 'zaplecze' ) ):?>
<li>
<a href="/backend_sites/collective_topics/">Zaplecze - tematy zbiorcze</a>
</li>
<li>
<a href="/backend_sites/topics/">Zaplecze - tematy</a>
</li>
<? endif;?>
</ul>
</div>
<div class="main">
<? if ( $this -> alert ):?>
<div class="alert"><?= $this -> alert;?></div>
<? endif;?>
<?= $this -> content;?>
<?= $this -> content;?>
</div>
</div>
<div class="default_popup">
<div class="popup_content">
@@ -125,7 +119,7 @@
<div class="popup_body"></div>
</div>
</div>
<script type="text/javascript">$
<script type="text/javascript">
$( function() {
$( 'input.date' ).datepicker({
language: 'pl',
@@ -144,6 +138,33 @@
$( '.default_popup .popup_body' ).html( content );
$( '.default_popup' ).fadeIn();
}
// Sidebar toggle
$( '#sidebar-toggle, #mobile-toggle' ).on( 'click', function() {
$( '#sidebar' ).toggleClass( 'collapsed' );
$( '#content-wrapper' ).toggleClass( 'expanded' );
var icon = $( '#sidebar-toggle i' );
if ( $( '#sidebar' ).hasClass( 'collapsed' ) ) {
icon.removeClass( 'fa-angle-double-left' ).addClass( 'fa-angle-double-right' );
} else {
icon.removeClass( 'fa-angle-double-right' ).addClass( 'fa-angle-double-left' );
}
});
// Submenu toggle
$( '.submenu-toggle' ).on( 'click', function(e) {
e.preventDefault();
var $li = $( this ).closest( '.has-submenu' );
$li.toggleClass( 'open' );
});
// Auto-open submenu if current page matches
$( '.sidebar-submenu a' ).each( function() {
if ( window.location.pathname.indexOf( $( this ).attr( 'href' ) ) === 0 ) {
$( this ).closest( '.has-submenu' ).addClass( 'open' );
$( this ).closest( 'li' ).addClass( 'active' );
}
});
</script>
</body>
</html>