update
This commit is contained in:
14
.vscode/ftp-kr.sync.cache.json
vendored
14
.vscode/ftp-kr.sync.cache.json
vendored
@@ -127,9 +127,9 @@
|
|||||||
},
|
},
|
||||||
"class.Users.php": {
|
"class.Users.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 4291,
|
"size": 4974,
|
||||||
"lmtime": 1770653518273,
|
"lmtime": 1772141683315,
|
||||||
"modified": true
|
"modified": false
|
||||||
},
|
},
|
||||||
"class.Wiki.php": {
|
"class.Wiki.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
@@ -319,8 +319,8 @@
|
|||||||
},
|
},
|
||||||
"index.php": {
|
"index.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 3592,
|
"size": 3935,
|
||||||
"lmtime": 1772131174733,
|
"lmtime": 1772141695415,
|
||||||
"modified": false
|
"modified": false
|
||||||
},
|
},
|
||||||
"layout": {
|
"layout": {
|
||||||
@@ -597,8 +597,8 @@
|
|||||||
"users": {
|
"users": {
|
||||||
"login-form.php": {
|
"login-form.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 3336,
|
"size": 3355,
|
||||||
"lmtime": 0,
|
"lmtime": 1772141702952,
|
||||||
"modified": false
|
"modified": false
|
||||||
},
|
},
|
||||||
"main-view.php": {
|
"main-view.php": {
|
||||||
|
|||||||
@@ -373,8 +373,119 @@ class UsersController
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function permissionPopup()
|
||||||
|
{
|
||||||
|
global $user, $mdb;
|
||||||
|
header( 'Content-Type: application/json; charset=utf-8' );
|
||||||
|
|
||||||
|
$response = [ 'status' => 'error', 'msg' => 'Nie mozna otworzyc ustawien uprawnien.' ];
|
||||||
|
|
||||||
|
if ( !$user || !self::canManageUsers( $user, self::getImpersonatorUser() ) )
|
||||||
|
{
|
||||||
|
$response['msg'] = 'Brak uprawnien.';
|
||||||
|
echo json_encode( $response );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !\S::csrf_verify() )
|
||||||
|
{
|
||||||
|
$response['msg'] = 'Nieprawidlowy token bezpieczenstwa. Odswiez strone.';
|
||||||
|
echo json_encode( $response );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$target_user_id = (int)\S::get( 'user_id' );
|
||||||
|
if ( !$target_user_id )
|
||||||
|
{
|
||||||
|
echo json_encode( $response );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$users_repository = new \Domain\Users\UserRepository();
|
||||||
|
$target_user = $users_repository -> byId( $target_user_id );
|
||||||
|
if ( !$target_user )
|
||||||
|
{
|
||||||
|
$response['msg'] = 'Nie znaleziono uzytkownika.';
|
||||||
|
echo json_encode( $response );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$permission_repo = new \Domain\Users\PermissionRepository( $mdb );
|
||||||
|
$permissions = (int)$target_user['id'] === self::ADMIN_USER_ID
|
||||||
|
? \Domain\Users\PermissionRepository::defaults()
|
||||||
|
: $permission_repo -> byUserId( (int)$target_user['id'] );
|
||||||
|
|
||||||
|
$defs = self::permissionDefinitions();
|
||||||
|
|
||||||
|
$response = [
|
||||||
|
'status' => 'success',
|
||||||
|
'popup_content' => \Tpl::view( 'users/permissions-popup', [
|
||||||
|
'target_user' => $target_user,
|
||||||
|
'permissions' => $permissions,
|
||||||
|
'module_labels' => $defs['module_labels'],
|
||||||
|
'permission_groups' => $defs['permission_groups']
|
||||||
|
] )
|
||||||
|
];
|
||||||
|
|
||||||
|
echo json_encode( $response );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function permissionSaveBulk()
|
||||||
|
{
|
||||||
|
global $user, $mdb;
|
||||||
|
header( 'Content-Type: application/json; charset=utf-8' );
|
||||||
|
|
||||||
|
$response = [ 'status' => 'error', 'msg' => 'Wystapil blad podczas zapisywania uprawnien.' ];
|
||||||
|
|
||||||
|
if ( !$user || !self::canManageUsers( $user, self::getImpersonatorUser() ) )
|
||||||
|
{
|
||||||
|
$response['msg'] = 'Brak uprawnien.';
|
||||||
|
echo json_encode( $response );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !\S::csrf_verify() )
|
||||||
|
{
|
||||||
|
$response['msg'] = 'Nieprawidlowy token bezpieczenstwa. Odswiez strone.';
|
||||||
|
echo json_encode( $response );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$target_user_id = (int)\S::get( 'user_id' );
|
||||||
|
$selected_modules_raw = (string)\S::get( 'selected_modules' );
|
||||||
|
|
||||||
|
if ( !$target_user_id )
|
||||||
|
{
|
||||||
|
echo json_encode( $response );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $target_user_id === self::ADMIN_USER_ID )
|
||||||
|
{
|
||||||
|
$response['msg'] = 'Nie mozna zmieniac uprawnien administratora.';
|
||||||
|
echo json_encode( $response );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$selected_modules = array_filter( array_map( 'trim', explode( ',', $selected_modules_raw ) ) );
|
||||||
|
$selected_modules = array_values( array_unique( $selected_modules ) );
|
||||||
|
|
||||||
|
$payload = [];
|
||||||
|
foreach ( \Domain\Users\PermissionRepository::MODULES as $module )
|
||||||
|
$payload[ $module ] = in_array( $module, $selected_modules, true ) ? 1 : 0;
|
||||||
|
|
||||||
|
$repo = new \Domain\Users\PermissionRepository( $mdb );
|
||||||
|
$repo -> save( $target_user_id, $payload );
|
||||||
|
|
||||||
|
echo json_encode( [ 'status' => 'success', 'msg' => 'Uprawnienia zostaly zapisane.' ] );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
public static function buildMainViewModel( $current_user, $impersonator_user, array $users, array $permissions_map = [] )
|
public static function buildMainViewModel( $current_user, $impersonator_user, array $users, array $permissions_map = [] )
|
||||||
{
|
{
|
||||||
|
$defs = self::permissionDefinitions();
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'current_user' => $current_user,
|
'current_user' => $current_user,
|
||||||
'impersonator_user' => $impersonator_user,
|
'impersonator_user' => $impersonator_user,
|
||||||
@@ -383,14 +494,8 @@ class UsersController
|
|||||||
'can_switch_back' => is_array( $impersonator_user ) and isset( $impersonator_user['id'] ) and (int)$impersonator_user['id'] === self::ADMIN_USER_ID,
|
'can_switch_back' => is_array( $impersonator_user ) and isset( $impersonator_user['id'] ) and (int)$impersonator_user['id'] === self::ADMIN_USER_ID,
|
||||||
'permissions_map' => $permissions_map,
|
'permissions_map' => $permissions_map,
|
||||||
'modules' => \Domain\Users\PermissionRepository::MODULES,
|
'modules' => \Domain\Users\PermissionRepository::MODULES,
|
||||||
'module_labels' => [
|
'module_labels' => $defs['module_labels'],
|
||||||
'tasks' => 'Zadania',
|
'permission_groups' => $defs['permission_groups']
|
||||||
'projects' => 'Projekty',
|
|
||||||
'work_time' => 'Czas pracy',
|
|
||||||
'finances' => 'Finanse',
|
|
||||||
'crm' => 'CRM',
|
|
||||||
'wiki' => 'Wiki'
|
|
||||||
]
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,4 +528,26 @@ class UsersController
|
|||||||
header( 'Location: /' );
|
header( 'Location: /' );
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function permissionDefinitions()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'module_labels' => [
|
||||||
|
'tasks' => 'Zadania',
|
||||||
|
'projects_view' => 'Projekty: przegladanie',
|
||||||
|
'projects_add' => 'Projekty: dodawanie',
|
||||||
|
'projects_edit' => 'Projekty: edycja',
|
||||||
|
'projects_delete' => 'Projekty: usuwanie',
|
||||||
|
'work_time' => 'Czas pracy',
|
||||||
|
'finances' => 'Finanse',
|
||||||
|
'crm' => 'CRM',
|
||||||
|
'wiki' => 'Wiki'
|
||||||
|
],
|
||||||
|
'permission_groups' => [
|
||||||
|
'Podstawowe' => [ 'tasks', 'work_time', 'wiki' ],
|
||||||
|
'Projekty' => [ 'projects_view', 'projects_add', 'projects_edit', 'projects_delete' ],
|
||||||
|
'Pozostale' => [ 'finances', 'crm' ]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,14 @@ namespace Domain\Users;
|
|||||||
|
|
||||||
class PermissionRepository
|
class PermissionRepository
|
||||||
{
|
{
|
||||||
const MODULES = [ 'tasks', 'projects', 'finances', 'wiki', 'crm', 'work_time' ];
|
const MODULES = [ 'tasks', 'projects_view', 'projects_add', 'projects_edit', 'projects_delete', 'finances', 'wiki', 'crm', 'work_time' ];
|
||||||
|
|
||||||
const DEFAULTS = [
|
const DEFAULTS = [
|
||||||
'tasks' => 1,
|
'tasks' => 1,
|
||||||
'projects' => 1,
|
'projects_view' => 1,
|
||||||
|
'projects_add' => 1,
|
||||||
|
'projects_edit' => 1,
|
||||||
|
'projects_delete' => 1,
|
||||||
'finances' => 0,
|
'finances' => 0,
|
||||||
'wiki' => 1,
|
'wiki' => 1,
|
||||||
'crm' => 0,
|
'crm' => 0,
|
||||||
@@ -42,8 +45,16 @@ class PermissionRepository
|
|||||||
return self::defaults();
|
return self::defaults();
|
||||||
|
|
||||||
$result = [];
|
$result = [];
|
||||||
|
$legacy_projects_value = isset( $row['projects'] ) ? (int)$row['projects'] : 1;
|
||||||
foreach ( self::MODULES as $module )
|
foreach ( self::MODULES as $module )
|
||||||
$result[ $module ] = isset( $row[ $module ] ) ? (int)$row[ $module ] : 0;
|
{
|
||||||
|
if ( isset( $row[ $module ] ) )
|
||||||
|
$result[ $module ] = (int)$row[ $module ];
|
||||||
|
else if ( strpos( $module, 'projects_' ) === 0 )
|
||||||
|
$result[ $module ] = $legacy_projects_value;
|
||||||
|
else
|
||||||
|
$result[ $module ] = isset( self::DEFAULTS[ $module ] ) ? (int)self::DEFAULTS[ $module ] : 0;
|
||||||
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class Site
|
|||||||
$module = \S::get( 'module' );
|
$module = \S::get( 'module' );
|
||||||
$action = \S::get( 'action' );
|
$action = \S::get( 'action' );
|
||||||
|
|
||||||
if ( !\controls\Users::permissions( $user['id'], $module ) )
|
if ( !\controls\Users::permissions( $user['id'], $module, $action ) )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// New Controllers namespace (camelCase methods)
|
// New Controllers namespace (camelCase methods)
|
||||||
|
|||||||
@@ -353,6 +353,17 @@ class Tasks
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static public function task_change_text() {
|
||||||
|
global $mdb;
|
||||||
|
|
||||||
|
if ( $mdb -> update( 'tasks', [ 'text' => \S::get( 'text' ) ], [ 'id' => \S::get( 'task_id' ) ] ) ) {
|
||||||
|
echo json_encode( [ 'status' => 'success' ] );
|
||||||
|
} else {
|
||||||
|
echo json_encode( [ 'status' => 'error' ] );
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
static public function task_change_users() {
|
static public function task_change_users() {
|
||||||
global $mdb, $user;
|
global $mdb, $user;
|
||||||
|
|
||||||
@@ -477,12 +488,15 @@ class Tasks
|
|||||||
$values['status_change_mail'] = isset( $values['status_change_mail'] ) ? $values['status_change_mail'] : 'off';
|
$values['status_change_mail'] = isset( $values['status_change_mail'] ) ? $values['status_change_mail'] : 'off';
|
||||||
$values['send_email_notification'] = isset( $values['send_email_notification'] ) ? $values['send_email_notification'] : 'off';
|
$values['send_email_notification'] = isset( $values['send_email_notification'] ) ? $values['send_email_notification'] : 'off';
|
||||||
$values['users'] = isset( $values['users'] ) ? $values['users'] : [];
|
$values['users'] = isset( $values['users'] ) ? $values['users'] : [];
|
||||||
$values['priority'] = isset( $values['priority'] ) && $values['priority'] !== '' ? $values['priority'] : 0;
|
$values['parent_id'] = isset( $values['parent_id'] ) && $values['parent_id'] !== '' ? (int)$values['parent_id'] : null;
|
||||||
|
if ( !empty( $values['id'] ) and (int)$values['id'] === (int)$values['parent_id'] )
|
||||||
|
$values['parent_id'] = null;
|
||||||
|
$values['priority'] = isset( $values['priority'] ) && $values['priority'] !== '' ? $values['priority'] : ( empty( $values['id'] ) ? 1 : 0 );
|
||||||
$values['recursive_last_date'] = isset( $values['recursive_last_date'] ) ? $values['recursive_last_date'] : null;
|
$values['recursive_last_date'] = isset( $values['recursive_last_date'] ) ? $values['recursive_last_date'] : null;
|
||||||
$status = \Controllers\TasksController::resolveTaskStatusForSave( $values );
|
$status = \Controllers\TasksController::resolveTaskStatusForSave( $values );
|
||||||
|
|
||||||
if ( $id = \factory\Tasks::task_save(
|
if ( $id = \factory\Tasks::task_save(
|
||||||
$values['id'], null, $user['id'], $values['name'], $values['text'], $values['date_start'], $values['date_end'], $values['project_id'], $values['client_id'], $values['pay_rate'], $values['reminders_interval'], $values['recursively'], $values['frequency'], $values['period'], $values['users'], null, null, $values['send_email_notification'], $values['status_change_mail'], false, $status, $values['show_in_calendar'], $values['priority'], $values['recursive_last_date']
|
$values['id'], $values['parent_id'], $user['id'], $values['name'], $values['text'], $values['date_start'], $values['date_end'], $values['project_id'], $values['client_id'], $values['pay_rate'], $values['reminders_interval'], $values['recursively'], $values['frequency'], $values['period'], $values['users'], null, null, $values['send_email_notification'], $values['status_change_mail'], false, $status, $values['show_in_calendar'], $values['priority'], $values['recursive_last_date']
|
||||||
) )
|
) )
|
||||||
{
|
{
|
||||||
\factory\Tasks::clear_task_opened( $id );
|
\factory\Tasks::clear_task_opened( $id );
|
||||||
|
|||||||
@@ -19,6 +19,35 @@ class Users
|
|||||||
$cache[ $user_id ] = $repo -> byUserId( (int)$user_id );
|
$cache[ $user_id ] = $repo -> byUserId( (int)$user_id );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( $module === 'projects' )
|
||||||
|
{
|
||||||
|
$permissions = $cache[ $user_id ];
|
||||||
|
|
||||||
|
if ( !$action )
|
||||||
|
return isset( $permissions['projects_view'] ) ? (bool)$permissions['projects_view'] : true;
|
||||||
|
|
||||||
|
if ( $action === 'project_delete' )
|
||||||
|
return isset( $permissions['projects_delete'] ) ? (bool)$permissions['projects_delete'] : false;
|
||||||
|
|
||||||
|
if ( $action === 'project_edit' || $action === 'project_save' )
|
||||||
|
{
|
||||||
|
$project_id = (int)\S::get( 'project_id' );
|
||||||
|
$values = \S::json_to_array( \S::get( 'values' ) );
|
||||||
|
if ( is_array( $values ) && isset( $values['id'] ) )
|
||||||
|
$project_id = (int)$values['id'];
|
||||||
|
|
||||||
|
if ( $project_id > 0 )
|
||||||
|
return isset( $permissions['projects_edit'] ) ? (bool)$permissions['projects_edit'] : false;
|
||||||
|
|
||||||
|
return isset( $permissions['projects_add'] ) ? (bool)$permissions['projects_add'] : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( strpos( $action, 'project_' ) === 0 )
|
||||||
|
return isset( $permissions['projects_view'] ) ? (bool)$permissions['projects_view'] : false;
|
||||||
|
|
||||||
|
return isset( $permissions['projects_view'] ) ? (bool)$permissions['projects_view'] : false;
|
||||||
|
}
|
||||||
|
|
||||||
if ( $module && isset( $cache[ $user_id ][ $module ] ) )
|
if ( $module && isset( $cache[ $user_id ][ $module ] ) )
|
||||||
return (bool)$cache[ $user_id ][ $module ];
|
return (bool)$cache[ $user_id ][ $module ];
|
||||||
|
|
||||||
@@ -28,15 +57,18 @@ class Users
|
|||||||
|
|
||||||
public static function logout()
|
public static function logout()
|
||||||
{
|
{
|
||||||
global $mdb, $user;
|
global $mdb;
|
||||||
|
|
||||||
$domain = preg_replace( '#^(http(s)?://)?w{3}\.#', '$1', $_SERVER['SERVER_NAME'] );
|
$domain = preg_replace( '#^(http(s)?://)?w{3}\.#', '$1', $_SERVER['SERVER_NAME'] );
|
||||||
$cookie_name = str_replace( '.', '-', $domain );
|
$cookie_name = str_replace( '.', '-', $domain );
|
||||||
|
$remember_token = $_COOKIE[$cookie_name] ?? '';
|
||||||
|
|
||||||
if ( $user && isset( $user['id'] ) )
|
if ( is_string( $remember_token ) && strlen( $remember_token ) === 64 && ctype_xdigit( $remember_token ) )
|
||||||
$mdb -> update( 'users', [ 'remember_token' => null ], [ 'id' => $user['id'] ] );
|
{
|
||||||
|
$mdb -> delete( 'users_remember_tokens', [ 'token_hash' => hash( 'sha256', $remember_token ) ] );
|
||||||
|
}
|
||||||
|
|
||||||
setcookie( $cookie_name, "", [
|
setcookie( $cookie_name, '', [
|
||||||
'expires' => strtotime( "-1 year" ),
|
'expires' => strtotime( "-1 year" ),
|
||||||
'path' => '/',
|
'path' => '/',
|
||||||
'domain' => $domain,
|
'domain' => $domain,
|
||||||
@@ -122,11 +154,41 @@ class Users
|
|||||||
{
|
{
|
||||||
$domain = preg_replace( '#^(http(s)?://)?w{3}\.#', '$1', $_SERVER['SERVER_NAME'] );
|
$domain = preg_replace( '#^(http(s)?://)?w{3}\.#', '$1', $_SERVER['SERVER_NAME'] );
|
||||||
$cookie_name = str_replace( '.', '-', $domain );
|
$cookie_name = str_replace( '.', '-', $domain );
|
||||||
|
$remember_token = $_COOKIE[$cookie_name] ?? '';
|
||||||
|
|
||||||
|
$clear_remember_cookie = function() use ( $cookie_name, $domain )
|
||||||
|
{
|
||||||
|
setcookie( $cookie_name, '', [
|
||||||
|
'expires' => strtotime( '-1 year' ),
|
||||||
|
'path' => '/',
|
||||||
|
'domain' => $domain,
|
||||||
|
'secure' => true,
|
||||||
|
'httponly' => true,
|
||||||
|
'samesite' => 'Lax'
|
||||||
|
] );
|
||||||
|
};
|
||||||
|
$cleanup_remember_tokens = function() use ( $mdb )
|
||||||
|
{
|
||||||
|
$mdb -> query( 'DELETE FROM `users_remember_tokens` WHERE COALESCE(`last_used_at`, `created_at`) < DATE_SUB(NOW(), INTERVAL 6 MONTH)' );
|
||||||
|
};
|
||||||
|
$cleanup_remember_tokens();
|
||||||
|
|
||||||
|
if ( is_string( $remember_token ) && strlen( $remember_token ) === 64 && ctype_xdigit( $remember_token ) )
|
||||||
|
{
|
||||||
|
$mdb -> delete( 'users_remember_tokens', [ 'token_hash' => hash( 'sha256', $remember_token ) ] );
|
||||||
|
}
|
||||||
|
|
||||||
if ( \S::get( 'remember' ) === 'true' )
|
if ( \S::get( 'remember' ) === 'true' )
|
||||||
{
|
{
|
||||||
$token = bin2hex( random_bytes( 32 ) );
|
$token = bin2hex( random_bytes( 32 ) );
|
||||||
$mdb -> update( 'users', [ 'remember_token' => $token ], [ 'id' => $user['id'] ] );
|
$mdb -> insert( 'users_remember_tokens', [
|
||||||
|
'user_id' => (int)$user['id'],
|
||||||
|
'token_hash' => hash( 'sha256', $token ),
|
||||||
|
'created_at' => date( 'Y-m-d H:i:s' ),
|
||||||
|
'last_used_at' => date( 'Y-m-d H:i:s' ),
|
||||||
|
'user_agent' => substr( (string)($_SERVER['HTTP_USER_AGENT'] ?? ''), 0, 255 ),
|
||||||
|
'ip' => (string)($_SERVER['REMOTE_ADDR'] ?? '')
|
||||||
|
] );
|
||||||
setcookie( $cookie_name, $token, [
|
setcookie( $cookie_name, $token, [
|
||||||
'expires' => strtotime( "+1 year" ),
|
'expires' => strtotime( "+1 year" ),
|
||||||
'path' => '/',
|
'path' => '/',
|
||||||
@@ -138,15 +200,7 @@ class Users
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$mdb -> update( 'users', [ 'remember_token' => null ], [ 'id' => $user['id'] ] );
|
$clear_remember_cookie();
|
||||||
setcookie( $cookie_name, "", [
|
|
||||||
'expires' => strtotime( "-1 year" ),
|
|
||||||
'path' => '/',
|
|
||||||
'domain' => $domain,
|
|
||||||
'secure' => true,
|
|
||||||
'httponly' => true,
|
|
||||||
'samesite' => 'Lax'
|
|
||||||
] );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
\S::set_session( 'user', $user );
|
\S::set_session( 'user', $user );
|
||||||
|
|||||||
@@ -75,36 +75,218 @@ class Tasks
|
|||||||
$data = [];
|
$data = [];
|
||||||
|
|
||||||
if ( $users ) {
|
if ( $users ) {
|
||||||
$sql = ' AND id IN (SELECT task_id FROM task_user WHERE user_id IN (' . implode( ',', $users ) . ')) ';
|
$sql = ' AND t.id IN (SELECT task_id FROM task_user WHERE user_id IN (' . implode( ',', $users ) . ')) ';
|
||||||
} else {
|
} else {
|
||||||
$sql = '';
|
$sql = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( $projects ) {
|
if ( $projects ) {
|
||||||
$sql .= ' AND project_id IN (' . implode( ',', $projects ) . ') ';
|
$sql .= ' AND t.project_id IN (' . implode( ',', $projects ) . ') ';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( $user_id != 1 ) {
|
if ( $user_id != 1 ) {
|
||||||
$sql_query = 'SELECT '
|
$sql_query = 'SELECT '
|
||||||
. 't.id, t.name, t.date_start, t.date_end, t.status, t.client_id, parent_id, priority '
|
. 't.id, t.name, t.date_start, t.date_end, t.status, t.client_id, t.parent_id, t.priority, pt.date_start AS parent_date_start, pt.date_end AS parent_date_end '
|
||||||
. 'FROM tasks AS t '
|
. 'FROM tasks AS t '
|
||||||
|
. 'LEFT JOIN tasks AS pt ON t.parent_id = pt.id '
|
||||||
. 'LEFT JOIN task_user AS tu ON t.id = tu.task_id '
|
. 'LEFT JOIN task_user AS tu ON t.id = tu.task_id '
|
||||||
. 'WHERE tu.user_id = ' . $user_id . ' AND show_in_calendar = 1 AND status != 2 AND status != 3 AND status != 1 AND t.date_start <= DATE_ADD(NOW(), INTERVAL 1 MONTH) ' . $sql . ' ORDER BY priority DESC, date_start ASC, date_end ASC, o ASC';
|
. 'WHERE tu.user_id = ' . $user_id . ' AND t.status != 2 AND t.status != 3 AND t.status != 1 AND ((t.show_in_calendar = 1 AND t.date_start <= DATE_ADD(NOW(), INTERVAL 1 MONTH)) OR (t.parent_id IS NOT NULL AND COALESCE(t.date_start, pt.date_start) IS NOT NULL AND COALESCE(t.date_start, pt.date_start) <= DATE_ADD(NOW(), INTERVAL 1 MONTH))) ' . $sql . ' ORDER BY t.priority DESC, COALESCE(t.date_start, pt.date_start) ASC, COALESCE(t.date_end, pt.date_end) ASC, t.o ASC';
|
||||||
} else {
|
} else {
|
||||||
$sql_query = 'SELECT '
|
$sql_query = 'SELECT '
|
||||||
. 't.id, t.name, t.date_start, t.date_end, t.status, t.client_id, parent_id, priority '
|
. 't.id, t.name, t.date_start, t.date_end, t.status, t.client_id, t.parent_id, t.priority, pt.date_start AS parent_date_start, pt.date_end AS parent_date_end '
|
||||||
. 'FROM tasks AS t '
|
. 'FROM tasks AS t '
|
||||||
. 'WHERE show_in_calendar = 1 AND status != 2 AND status != 3 AND status != 1 AND t.date_start <= DATE_ADD(NOW(), INTERVAL 1 MONTH) ' . $sql . ' ORDER BY priority DESC, date_start ASC, date_end ASC, o ASC';
|
. 'LEFT JOIN tasks AS pt ON t.parent_id = pt.id '
|
||||||
|
. 'WHERE t.status != 2 AND t.status != 3 AND t.status != 1 AND ((t.show_in_calendar = 1 AND t.date_start <= DATE_ADD(NOW(), INTERVAL 1 MONTH)) OR (t.parent_id IS NOT NULL AND COALESCE(t.date_start, pt.date_start) IS NOT NULL AND COALESCE(t.date_start, pt.date_start) <= DATE_ADD(NOW(), INTERVAL 1 MONTH))) ' . $sql . ' ORDER BY t.priority DESC, COALESCE(t.date_start, pt.date_start) ASC, COALESCE(t.date_end, pt.date_end) ASC, t.o ASC';
|
||||||
}
|
}
|
||||||
|
|
||||||
$tasks = $mdb -> query( $sql_query ) -> fetchAll( \PDO::FETCH_ASSOC );
|
$tasks = $mdb -> query( $sql_query ) -> fetchAll( \PDO::FETCH_ASSOC );
|
||||||
foreach ( $tasks as $task ) {
|
|
||||||
|
// If subtasks are present but their parent task is outside the base filter,
|
||||||
|
// force-load missing parents so dependency tree expand/collapse can work.
|
||||||
|
$tasks_by_id = [];
|
||||||
|
foreach ( $tasks as $tmp_task )
|
||||||
|
$tasks_by_id[ (int)$tmp_task['id'] ] = true;
|
||||||
|
|
||||||
|
$pending_parent_ids = [];
|
||||||
|
foreach ( $tasks as $tmp_task )
|
||||||
|
{
|
||||||
|
$parent_id = (int)$tmp_task['parent_id'];
|
||||||
|
if ( $parent_id > 0 and !isset( $tasks_by_id[ $parent_id ] ) )
|
||||||
|
$pending_parent_ids[ $parent_id ] = $parent_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
while ( !empty( $pending_parent_ids ) )
|
||||||
|
{
|
||||||
|
$parent_ids = array_map( 'intval', array_values( $pending_parent_ids ) );
|
||||||
|
$pending_parent_ids = [];
|
||||||
|
|
||||||
|
$parents_query = 'SELECT '
|
||||||
|
. 't.id, t.name, t.date_start, t.date_end, t.status, t.client_id, t.parent_id, t.priority, pt.date_start AS parent_date_start, pt.date_end AS parent_date_end '
|
||||||
|
. 'FROM tasks AS t '
|
||||||
|
. 'LEFT JOIN tasks AS pt ON t.parent_id = pt.id '
|
||||||
|
. 'WHERE t.id IN (' . implode( ',', $parent_ids ) . ')';
|
||||||
|
|
||||||
|
$parent_rows = $mdb -> query( $parents_query ) -> fetchAll( \PDO::FETCH_ASSOC );
|
||||||
|
foreach ( $parent_rows as $parent_row )
|
||||||
|
{
|
||||||
|
$parent_row_id = (int)$parent_row['id'];
|
||||||
|
if ( isset( $tasks_by_id[ $parent_row_id ] ) )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$tasks[] = $parent_row;
|
||||||
|
$tasks_by_id[ $parent_row_id ] = true;
|
||||||
|
|
||||||
|
$next_parent_id = (int)$parent_row['parent_id'];
|
||||||
|
if ( $next_parent_id > 0 and !isset( $tasks_by_id[ $next_parent_id ] ) )
|
||||||
|
$pending_parent_ids[ $next_parent_id ] = $next_parent_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a stable hierarchical order: parent task followed by its subtasks.
|
||||||
|
$task_rows_by_id = [];
|
||||||
|
$task_order_index = [];
|
||||||
|
$task_children_map = [];
|
||||||
|
|
||||||
|
foreach ( $tasks as $idx => $task_row )
|
||||||
|
{
|
||||||
|
$task_row_id = (int)$task_row['id'];
|
||||||
|
if ( !$task_row_id )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ( !isset( $task_rows_by_id[ $task_row_id ] ) )
|
||||||
|
{
|
||||||
|
$task_rows_by_id[ $task_row_id ] = $task_row;
|
||||||
|
$task_order_index[ $task_row_id ] = $idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ( $task_rows_by_id as $task_row_id => $task_row )
|
||||||
|
{
|
||||||
|
$task_parent_id = (int)$task_row['parent_id'];
|
||||||
|
if ( $task_parent_id > 0 )
|
||||||
|
{
|
||||||
|
if ( !isset( $task_children_map[ $task_parent_id ] ) )
|
||||||
|
$task_children_map[ $task_parent_id ] = [];
|
||||||
|
$task_children_map[ $task_parent_id ][] = $task_row_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$root_ids = [];
|
||||||
|
foreach ( $task_rows_by_id as $task_row_id => $task_row )
|
||||||
|
{
|
||||||
|
$task_parent_id = (int)$task_row['parent_id'];
|
||||||
|
if ( $task_parent_id <= 0 or !isset( $task_rows_by_id[ $task_parent_id ] ) )
|
||||||
|
$root_ids[] = $task_row_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Effective priority for display: parent inherits the highest priority found
|
||||||
|
// in its subtree (without changing DB value).
|
||||||
|
$task_effective_priority = [];
|
||||||
|
$effective_priority_stack = [];
|
||||||
|
$compute_effective_priority = null;
|
||||||
|
$compute_effective_priority = function( $task_id ) use ( &$compute_effective_priority, &$task_effective_priority, &$effective_priority_stack, $task_rows_by_id, $task_children_map ) {
|
||||||
|
if ( isset( $task_effective_priority[ $task_id ] ) )
|
||||||
|
return $task_effective_priority[ $task_id ];
|
||||||
|
|
||||||
|
if ( isset( $effective_priority_stack[ $task_id ] ) )
|
||||||
|
return isset( $task_rows_by_id[ $task_id ]['priority'] ) ? (int)$task_rows_by_id[ $task_id ]['priority'] : 0;
|
||||||
|
|
||||||
|
$effective_priority_stack[ $task_id ] = true;
|
||||||
|
|
||||||
|
$max_priority = isset( $task_rows_by_id[ $task_id ]['priority'] ) ? (int)$task_rows_by_id[ $task_id ]['priority'] : 0;
|
||||||
|
if ( isset( $task_children_map[ $task_id ] ) )
|
||||||
|
{
|
||||||
|
foreach ( $task_children_map[ $task_id ] as $child_id )
|
||||||
|
{
|
||||||
|
$child_priority = (int)$compute_effective_priority( $child_id );
|
||||||
|
if ( $child_priority > $max_priority )
|
||||||
|
$max_priority = $child_priority;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unset( $effective_priority_stack[ $task_id ] );
|
||||||
|
$task_effective_priority[ $task_id ] = $max_priority;
|
||||||
|
return $max_priority;
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach ( array_keys( $task_rows_by_id ) as $task_id )
|
||||||
|
$compute_effective_priority( $task_id );
|
||||||
|
|
||||||
|
// Sort by effective priority first (DESC), then keep previous visual stability
|
||||||
|
// by original SQL order (ASC) for equal priorities.
|
||||||
|
$sort_by_effective_priority = function( $a, $b ) use ( $task_order_index, $task_effective_priority ) {
|
||||||
|
$a_priority = isset( $task_effective_priority[ $a ] ) ? (int)$task_effective_priority[ $a ] : 0;
|
||||||
|
$b_priority = isset( $task_effective_priority[ $b ] ) ? (int)$task_effective_priority[ $b ] : 0;
|
||||||
|
if ( $a_priority !== $b_priority )
|
||||||
|
return $a_priority > $b_priority ? -1 : 1;
|
||||||
|
|
||||||
|
$a_idx = isset( $task_order_index[ $a ] ) ? $task_order_index[ $a ] : 999999;
|
||||||
|
$b_idx = isset( $task_order_index[ $b ] ) ? $task_order_index[ $b ] : 999999;
|
||||||
|
if ( $a_idx == $b_idx )
|
||||||
|
return 0;
|
||||||
|
return $a_idx < $b_idx ? -1 : 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
usort( $root_ids, $sort_by_effective_priority );
|
||||||
|
foreach ( $task_children_map as $parent_id => $child_ids )
|
||||||
|
{
|
||||||
|
usort( $child_ids, $sort_by_effective_priority );
|
||||||
|
$task_children_map[ $parent_id ] = $child_ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ordered_task_ids = [];
|
||||||
|
$ordered_visited = [];
|
||||||
|
$append_task_with_children = null;
|
||||||
|
$append_task_with_children = function( $task_id ) use ( &$append_task_with_children, &$ordered_task_ids, &$ordered_visited, $task_children_map ) {
|
||||||
|
if ( isset( $ordered_visited[ $task_id ] ) )
|
||||||
|
return;
|
||||||
|
|
||||||
|
$ordered_visited[ $task_id ] = true;
|
||||||
|
$ordered_task_ids[] = $task_id;
|
||||||
|
|
||||||
|
if ( isset( $task_children_map[ $task_id ] ) )
|
||||||
|
{
|
||||||
|
foreach ( $task_children_map[ $task_id ] as $child_id )
|
||||||
|
$append_task_with_children( $child_id );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach ( $root_ids as $root_id )
|
||||||
|
$append_task_with_children( $root_id );
|
||||||
|
|
||||||
|
foreach ( array_keys( $task_rows_by_id ) as $task_id )
|
||||||
|
$append_task_with_children( $task_id );
|
||||||
|
|
||||||
|
foreach ( $ordered_task_ids as $ordered_task_id ) {
|
||||||
|
$task = $task_rows_by_id[ $ordered_task_id ];
|
||||||
|
$effective_start = $task['date_start'] ? $task['date_start'] : $task['parent_date_start'];
|
||||||
|
$effective_end = $task['date_end'] ? $task['date_end'] : $task['parent_date_end'];
|
||||||
|
|
||||||
|
if ( !$effective_start and $effective_end )
|
||||||
|
$effective_start = $effective_end;
|
||||||
|
if ( !$effective_end and $effective_start )
|
||||||
|
$effective_end = $effective_start;
|
||||||
|
if ( !$effective_start or !$effective_end )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$task_depth = 0;
|
||||||
|
$depth_parent_id = (int)$task['parent_id'];
|
||||||
|
while ( $depth_parent_id > 0 and isset( $task_rows_by_id[ $depth_parent_id ] ) and $task_depth < 10 )
|
||||||
|
{
|
||||||
|
$task_depth++;
|
||||||
|
$depth_parent_id = (int)$task_rows_by_id[ $depth_parent_id ]['parent_id'];
|
||||||
|
}
|
||||||
|
|
||||||
$task_json = [];
|
$task_json = [];
|
||||||
$task_json['name'] = $task['client_id'] ? \factory\Crm::get_client_name( (int)$task['client_id'] ) . ' - ' . htmlspecialchars( $task['name'] ) : htmlspecialchars( $task['name'] );
|
$task_name = $task['client_id'] ? \factory\Crm::get_client_name( (int)$task['client_id'] ) . ' - ' . htmlspecialchars( $task['name'] ) : htmlspecialchars( $task['name'] );
|
||||||
|
if ( $task_depth > 0 )
|
||||||
|
$task_name = str_repeat( ' ', $task_depth ) . '-> ' . $task_name;
|
||||||
|
|
||||||
|
$task_json['name'] = $task_name;
|
||||||
// start
|
// start
|
||||||
$task_json['start'] = $task['date_start'];
|
$task_json['start'] = $effective_start;
|
||||||
// end
|
// end
|
||||||
$task_json['end'] = $task['date_end'];
|
$task_json['end'] = $effective_end;
|
||||||
// id
|
// id
|
||||||
$task_json['id'] = $task['id'];
|
$task_json['id'] = $task['id'];
|
||||||
// custom class
|
// custom class
|
||||||
@@ -119,7 +301,10 @@ class Tasks
|
|||||||
$task_json['custom_class'] = 'gantt-task-faktura';
|
$task_json['custom_class'] = 'gantt-task-faktura';
|
||||||
|
|
||||||
if ( !$task_json['custom_class'] )
|
if ( !$task_json['custom_class'] )
|
||||||
$task_json['custom_class'] = 'gantt-task-priority-' . $task['priority'];
|
{
|
||||||
|
$display_priority = isset( $task_effective_priority[ (int)$task['id'] ] ) ? (int)$task_effective_priority[ (int)$task['id'] ] : (int)$task['priority'];
|
||||||
|
$task_json['custom_class'] = 'gantt-task-priority-' . $display_priority;
|
||||||
|
}
|
||||||
|
|
||||||
// progress
|
// progress
|
||||||
$task_json['progress'] = 0;
|
$task_json['progress'] = 0;
|
||||||
@@ -503,6 +688,7 @@ class Tasks
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
$mdb -> update( 'tasks', [
|
$mdb -> update( 'tasks', [
|
||||||
|
'parent_id' => $parent_id,
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
'text' => htmlspecialchars( $text ),
|
'text' => htmlspecialchars( $text ),
|
||||||
'date_start' => $date_start != '' ? $date_start : null,
|
'date_start' => $date_start != '' ? $date_start : null,
|
||||||
|
|||||||
92
index.php
92
index.php
@@ -54,8 +54,23 @@ $mdb = new medoo([
|
|||||||
$domain = preg_replace( '#^(http(s)?://)?w{3}\.#', '$1', $_SERVER['SERVER_NAME'] );
|
$domain = preg_replace( '#^(http(s)?://)?w{3}\.#', '$1', $_SERVER['SERVER_NAME'] );
|
||||||
$cookie_name = str_replace( '.', '-', $domain );
|
$cookie_name = str_replace( '.', '-', $domain );
|
||||||
$settings = array_merge( $settings, \factory\Crm::settings());
|
$settings = array_merge( $settings, \factory\Crm::settings());
|
||||||
|
$clear_remember_cookie = function() use ( $cookie_name, $domain )
|
||||||
|
{
|
||||||
|
setcookie( $cookie_name, '', [
|
||||||
|
'expires' => strtotime( '-1 year' ),
|
||||||
|
'path' => '/',
|
||||||
|
'domain' => $domain,
|
||||||
|
'secure' => true,
|
||||||
|
'httponly' => true,
|
||||||
|
'samesite' => 'Lax'
|
||||||
|
] );
|
||||||
|
};
|
||||||
|
$cleanup_remember_tokens = function() use ( $mdb )
|
||||||
|
{
|
||||||
|
$mdb -> query( 'DELETE FROM `users_remember_tokens` WHERE COALESCE(`last_used_at`, `created_at`) < DATE_SUB(NOW(), INTERVAL 6 MONTH)' );
|
||||||
|
};
|
||||||
|
|
||||||
if ( empty( $_SESSION['_db_migrated_v3'] ) )
|
if ( empty( $_SESSION['_db_migrated_v5'] ) )
|
||||||
{
|
{
|
||||||
$col = $mdb -> query( "SHOW COLUMNS FROM `users` LIKE 'remember_token'" ) -> fetch();
|
$col = $mdb -> query( "SHOW COLUMNS FROM `users` LIKE 'remember_token'" ) -> fetch();
|
||||||
if ( !$col )
|
if ( !$col )
|
||||||
@@ -69,6 +84,10 @@ if ( empty( $_SESSION['_db_migrated_v3'] ) )
|
|||||||
`user_id` INT UNSIGNED NOT NULL PRIMARY KEY,
|
`user_id` INT UNSIGNED NOT NULL PRIMARY KEY,
|
||||||
`tasks` TINYINT(1) NOT NULL DEFAULT 1,
|
`tasks` TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
`projects` TINYINT(1) NOT NULL DEFAULT 1,
|
`projects` TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
`projects_view` TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
`projects_add` TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
`projects_edit` TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
`projects_delete` TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
`finances` TINYINT(1) NOT NULL DEFAULT 0,
|
`finances` TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
`wiki` TINYINT(1) NOT NULL DEFAULT 1,
|
`wiki` TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
`crm` TINYINT(1) NOT NULL DEFAULT 0,
|
`crm` TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
@@ -81,32 +100,77 @@ if ( empty( $_SESSION['_db_migrated_v3'] ) )
|
|||||||
$col_z = $mdb -> query( "SHOW COLUMNS FROM `users_permissions` LIKE 'zaplecze'" ) -> fetch();
|
$col_z = $mdb -> query( "SHOW COLUMNS FROM `users_permissions` LIKE 'zaplecze'" ) -> fetch();
|
||||||
if ( $col_z )
|
if ( $col_z )
|
||||||
$mdb -> pdo -> exec( "ALTER TABLE `users_permissions` DROP COLUMN `zaplecze`" );
|
$mdb -> pdo -> exec( "ALTER TABLE `users_permissions` DROP COLUMN `zaplecze`" );
|
||||||
|
|
||||||
|
$project_permission_columns = [ 'projects_view', 'projects_add', 'projects_edit', 'projects_delete' ];
|
||||||
|
foreach ( $project_permission_columns as $permission_column )
|
||||||
|
{
|
||||||
|
$col_perm = $mdb -> query( "SHOW COLUMNS FROM `users_permissions` LIKE '" . $permission_column . "'" ) -> fetch();
|
||||||
|
if ( !$col_perm )
|
||||||
|
{
|
||||||
|
$mdb -> pdo -> exec( "ALTER TABLE `users_permissions` ADD COLUMN `" . $permission_column . "` TINYINT(1) NOT NULL DEFAULT 1" );
|
||||||
|
$mdb -> pdo -> exec( "UPDATE `users_permissions` SET `" . $permission_column . "` = `projects`" );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$_SESSION['_db_migrated_v3'] = true;
|
$tbl_tokens = $mdb -> query( "SHOW TABLES LIKE 'users_remember_tokens'" ) -> fetch();
|
||||||
|
if ( !$tbl_tokens )
|
||||||
|
{
|
||||||
|
$mdb -> pdo -> exec( "
|
||||||
|
CREATE TABLE `users_remember_tokens` (
|
||||||
|
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`user_id` INT UNSIGNED NOT NULL,
|
||||||
|
`token_hash` CHAR(64) NOT NULL,
|
||||||
|
`created_at` DATETIME NOT NULL,
|
||||||
|
`last_used_at` DATETIME NULL,
|
||||||
|
`user_agent` VARCHAR(255) NULL,
|
||||||
|
`ip` VARCHAR(45) NULL,
|
||||||
|
UNIQUE KEY `uniq_token_hash` (`token_hash`),
|
||||||
|
KEY `idx_user_id` (`user_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8
|
||||||
|
" );
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION['_db_migrated_v5'] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( isset( $_COOKIE[$cookie_name] ) && !isset( $_SESSION['user'] ) )
|
if ( isset( $_COOKIE[$cookie_name] ) && !isset( $_SESSION['user'] ) )
|
||||||
{
|
{
|
||||||
|
$cleanup_remember_tokens();
|
||||||
$remember_token = $_COOKIE[$cookie_name];
|
$remember_token = $_COOKIE[$cookie_name];
|
||||||
|
|
||||||
if ( is_string( $remember_token ) && strlen( $remember_token ) === 64 && ctype_xdigit( $remember_token ) )
|
if ( is_string( $remember_token ) && strlen( $remember_token ) === 64 && ctype_xdigit( $remember_token ) )
|
||||||
{
|
{
|
||||||
$user_tmp = $mdb -> get( 'users', '*', [ 'remember_token' => $remember_token ] );
|
$token_hash = hash( 'sha256', $remember_token );
|
||||||
if ( $user_tmp )
|
$token_row = $mdb -> get( 'users_remember_tokens', '*', [ 'token_hash' => $token_hash ] );
|
||||||
\S::set_session( 'user', $user_tmp );
|
|
||||||
|
if ( $token_row )
|
||||||
|
{
|
||||||
|
$user_tmp = $mdb -> get( 'users', '*', [ 'id' => $token_row['user_id'] ] );
|
||||||
|
if ( $user_tmp )
|
||||||
|
{
|
||||||
|
\S::set_session( 'user', $user_tmp );
|
||||||
|
$mdb -> update( 'users_remember_tokens', [
|
||||||
|
'last_used_at' => date( 'Y-m-d H:i:s' ),
|
||||||
|
'user_agent' => substr( (string)($_SERVER['HTTP_USER_AGENT'] ?? ''), 0, 255 ),
|
||||||
|
'ip' => (string)($_SERVER['REMOTE_ADDR'] ?? '')
|
||||||
|
], [ 'id' => (int)$token_row['id'] ] );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$mdb -> delete( 'users_remember_tokens', [ 'id' => (int)$token_row['id'] ] );
|
||||||
|
$clear_remember_cookie();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$clear_remember_cookie();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// stare cookie w nieaktualnym formacie — usunięcie
|
// stale cookie w nieaktualnym formacie - usuniecie
|
||||||
setcookie( $cookie_name, "", [
|
$clear_remember_cookie();
|
||||||
'expires' => strtotime( "-1 year" ),
|
|
||||||
'path' => '/',
|
|
||||||
'domain' => $domain,
|
|
||||||
'secure' => true,
|
|
||||||
'httponly' => true,
|
|
||||||
'samesite' => 'Lax'
|
|
||||||
] );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1426,8 +1426,8 @@ class Gantt {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(s)
|
if(s && this.options.sync_parent_end_with_children)
|
||||||
task.end=s.end;
|
task.end=s.end;
|
||||||
task.has=this.get_all_dependent_tasks(task.id);
|
task.has=this.get_all_dependent_tasks(task.id);
|
||||||
|
|
||||||
return task;
|
return task;
|
||||||
@@ -1666,7 +1666,8 @@ class Gantt {
|
|||||||
date_format: 'YYYY-MM-DD',
|
date_format: 'YYYY-MM-DD',
|
||||||
popup_trigger: 'click',
|
popup_trigger: 'click',
|
||||||
custom_popup_html: null,
|
custom_popup_html: null,
|
||||||
language: 'en'
|
language: 'en',
|
||||||
|
sync_parent_end_with_children: true
|
||||||
};
|
};
|
||||||
this.options = Object.assign({}, default_options, options);
|
this.options = Object.assign({}, default_options, options);
|
||||||
}
|
}
|
||||||
@@ -2747,3 +2748,5 @@ function generate_id(task) {
|
|||||||
return Gantt;
|
return Gantt;
|
||||||
|
|
||||||
}());
|
}());
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -16,10 +16,17 @@
|
|||||||
<h4>Projekty</h4>
|
<h4>Projekty</h4>
|
||||||
<? foreach ( $this -> projects as $project ):?>
|
<? foreach ( $this -> projects as $project ):?>
|
||||||
<div class="_project">
|
<div class="_project">
|
||||||
<label for="project_<?= $project[ 'id' ];?>">
|
<div class="project_row">
|
||||||
<input type="checkbox" class="g-checkbox" name="projects" value="<?= $project[ 'id' ];?>" <? if ( is_array( $this -> selected_projects ) and in_array( $project['id'], $this -> selected_projects ) ):?>checked<? endif;?>>
|
<label for="project_<?= $project[ 'id' ];?>">
|
||||||
<?= $project[ 'name' ];?> (<?= $project['total_tasks'];?>)
|
<input type="checkbox" class="g-checkbox" name="projects" value="<?= $project[ 'id' ];?>" <? if ( is_array( $this -> selected_projects ) and in_array( $project['id'], $this -> selected_projects ) ):?>checked<? endif;?>>
|
||||||
</label>
|
<?= $project[ 'name' ];?> <span class="project_count">(<?= (int)$project['total_tasks'];?>)</span>
|
||||||
|
</label>
|
||||||
|
<? if ( \controls\Users::permissions( $this -> user['id'], 'projects', 'project_delete' ) ):?>
|
||||||
|
<a href="#" class="project_delete_inline" project_id="<?= (int)$project['id'];?>" project_name="<?= htmlspecialchars( $project['name'] );?>" title="Usuń projekt">
|
||||||
|
<i class="fa fa-trash"></i>
|
||||||
|
</a>
|
||||||
|
<? endif;?>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<? endforeach;?>
|
<? endforeach;?>
|
||||||
</div>
|
</div>
|
||||||
@@ -127,6 +134,69 @@
|
|||||||
<div class="task_popup">
|
<div class="task_popup">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<style type="text/css">
|
||||||
|
.tasks_main_view ._left_column {
|
||||||
|
width: fit-content;
|
||||||
|
min-width: 350px;
|
||||||
|
max-width: 520px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tasks_main_view ._right_column {
|
||||||
|
flex: 1;
|
||||||
|
max-width: none;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tasks_main_view ._left_column ._projects ._project .project_row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tasks_main_view ._left_column ._projects ._project .project_row label {
|
||||||
|
margin: 0;
|
||||||
|
flex: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tasks_main_view ._left_column ._projects ._project .project_row .project_count {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 3px;
|
||||||
|
padding: 0 6px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #1f3d72;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 18px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tasks_main_view ._left_column ._projects ._project .project_delete_inline {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #cc563d;
|
||||||
|
background: #cc563d;
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all .2s ease;
|
||||||
|
margin: 1px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tasks_main_view ._left_column ._projects ._project .project_delete_inline:hover {
|
||||||
|
background: #b74831;
|
||||||
|
border-color: #b74831;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tasks_main_view ._left_column ._projects ._project .project_delete_inline i {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
let isProgrammaticUpdate = false;
|
let isProgrammaticUpdate = false;
|
||||||
|
|
||||||
@@ -172,7 +242,49 @@
|
|||||||
function normalizeGanttTasks( tasksData )
|
function normalizeGanttTasks( tasksData )
|
||||||
{
|
{
|
||||||
if ( Array.isArray( tasksData ) && tasksData.length > 0 )
|
if ( Array.isArray( tasksData ) && tasksData.length > 0 )
|
||||||
return tasksData;
|
{
|
||||||
|
var longTaskThresholdDays = 21;
|
||||||
|
var clipStartMoment = null;
|
||||||
|
|
||||||
|
tasksData.forEach( function( task ) {
|
||||||
|
var startMoment = moment( task.start, 'YYYY-MM-DD', true );
|
||||||
|
var endMoment = moment( task.end, 'YYYY-MM-DD', true );
|
||||||
|
|
||||||
|
if ( !startMoment.isValid() || !endMoment.isValid() )
|
||||||
|
return;
|
||||||
|
|
||||||
|
var durationDays = endMoment.diff( startMoment, 'days' );
|
||||||
|
if ( durationDays < longTaskThresholdDays )
|
||||||
|
{
|
||||||
|
if ( !clipStartMoment || startMoment.isBefore( clipStartMoment ) )
|
||||||
|
clipStartMoment = startMoment.clone();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
if ( !clipStartMoment )
|
||||||
|
clipStartMoment = moment().startOf( 'day' );
|
||||||
|
|
||||||
|
return tasksData.map( function( task ) {
|
||||||
|
var normalizedTask = Object.assign( {}, task );
|
||||||
|
var startMoment = moment( normalizedTask.start, 'YYYY-MM-DD', true );
|
||||||
|
var endMoment = moment( normalizedTask.end, 'YYYY-MM-DD', true );
|
||||||
|
|
||||||
|
if ( !startMoment.isValid() || !endMoment.isValid() )
|
||||||
|
return normalizedTask;
|
||||||
|
|
||||||
|
var durationDays = endMoment.diff( startMoment, 'days' );
|
||||||
|
|
||||||
|
if ( durationDays >= longTaskThresholdDays && startMoment.isBefore( clipStartMoment ) && endMoment.isSameOrAfter( clipStartMoment ) )
|
||||||
|
{
|
||||||
|
normalizedTask.start = clipStartMoment.format( 'YYYY-MM-DD' );
|
||||||
|
|
||||||
|
if ( typeof normalizedTask.name === 'string' && normalizedTask.name.indexOf( '... ' ) !== 0 )
|
||||||
|
normalizedTask.name = '... ' + normalizedTask.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizedTask;
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
return getEmptyGanttTasks();
|
return getEmptyGanttTasks();
|
||||||
}
|
}
|
||||||
@@ -232,7 +344,8 @@
|
|||||||
console.log(mode);
|
console.log(mode);
|
||||||
},
|
},
|
||||||
view_mode: 'Half Day',
|
view_mode: 'Half Day',
|
||||||
language: 'en'
|
language: 'en',
|
||||||
|
sync_parent_end_with_children: false
|
||||||
});
|
});
|
||||||
console.log(gantt_chart);
|
console.log(gantt_chart);
|
||||||
|
|
||||||
@@ -443,11 +556,46 @@
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
$( 'body' ).on( 'click', '.task_popup .close', function(e) {
|
$( 'body' ).on( 'click', '.task_popup .close', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
close_task_popup();
|
close_task_popup();
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$( 'body' ).on( 'click', '.project_delete_inline', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var project_id = $( this ).attr( 'project_id' );
|
||||||
|
var project_name = $( this ).attr( 'project_name' );
|
||||||
|
|
||||||
|
$.confirm({
|
||||||
|
title: 'Potwierdź',
|
||||||
|
content: 'Na pewno chcesz usunąć projekt <b>' + project_name + '</b>?',
|
||||||
|
type: 'orange',
|
||||||
|
closeIcon: true,
|
||||||
|
closeIconClass: 'fa fa-close',
|
||||||
|
typeAnimated: true,
|
||||||
|
animation: 'opacity',
|
||||||
|
boxWidth: '500px',
|
||||||
|
useBootstrap: false,
|
||||||
|
theme: 'material',
|
||||||
|
buttons: {
|
||||||
|
cancel: {
|
||||||
|
text: 'Anuluj',
|
||||||
|
btnClass: 'btn-default',
|
||||||
|
action: function() {}
|
||||||
|
},
|
||||||
|
confirm: {
|
||||||
|
text: 'Usuń projekt',
|
||||||
|
btnClass: 'btn-red',
|
||||||
|
keys: [ 'enter' ],
|
||||||
|
action: function() {
|
||||||
|
document.location.href = '/projects/project_delete/project_id=' + project_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
$( 'body' ).on( 'click', '.current_status', function() {
|
$( 'body' ).on( 'click', '.current_status', function() {
|
||||||
$( this ).find( '.status_change' ).toggle();
|
$( this ).find( '.status_change' ).toggle();
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ ob_start();
|
|||||||
'label' => 'Priorytet',
|
'label' => 'Priorytet',
|
||||||
'name' => 'priority',
|
'name' => 'priority',
|
||||||
'id' => 'priority',
|
'id' => 'priority',
|
||||||
'value' => $this -> task[ 'priority' ],
|
'value' => $this -> task['id'] ? $this -> task['priority'] : 1,
|
||||||
'values' => $this -> priorities
|
'values' => $this -> priorities
|
||||||
] );
|
] );
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -11,13 +11,14 @@
|
|||||||
<? endif;?>
|
<? endif;?>
|
||||||
<span class="task-title-wrapper">
|
<span class="task-title-wrapper">
|
||||||
<span class="task-id">#<?= $this -> task['id'];?></span>
|
<span class="task-id">#<?= $this -> task['id'];?></span>
|
||||||
<span class="task-title-text"><?= $this -> task['name'];?></span>
|
<span class="task-title-view">
|
||||||
<a href="#" class="task-title-edit-btn" title="Edytuj tytuł"><i class="fa fa-pencil-square-o"></i></a>
|
<span class="task-title-text"><?= $this -> task['name'];?></span>
|
||||||
|
<a href="#" class="task-title-edit-btn" title="Edytuj tytuł"><i class="fa fa-pencil"></i></a>
|
||||||
|
</span>
|
||||||
<span class="task-title-edit-box" style="display: none;">
|
<span class="task-title-edit-box" style="display: none;">
|
||||||
<input type="text" class="task-title-input" value="<?= htmlspecialchars($this -> task['name']);?>">
|
<input type="text" class="task-title-input form-control" value="<?= htmlspecialchars($this -> task['name']);?>">
|
||||||
<a href="#" class="task-title-save"><i class="fa fa-check"></i></a>
|
<a href="#" class="task-title-save" title="Zapisz"><i class="fa fa-check"></i></a>
|
||||||
<a href="#" class="task-title-cancel"><i class="fa fa-times"></i></a>
|
<a href="#" class="task-title-cancel" title="Anuluj"><i class="fa fa-times"></i></a>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -87,6 +88,11 @@
|
|||||||
<? endforeach; endif;?>
|
<? endforeach; endif;?>
|
||||||
</div>
|
</div>
|
||||||
<div class="task-tab-panel is-active" data-tab="description">
|
<div class="task-tab-panel is-active" data-tab="description">
|
||||||
|
<div class="task-description-editor box">
|
||||||
|
<h3>Treść zadania</h3>
|
||||||
|
<textarea class="form-control task-description-input" rows="5"><?= htmlspecialchars( (string)$this -> task['text'] );?></textarea>
|
||||||
|
<a href="#" class="btn btn-primary btn-sm task-popup-compact-btn js-save-task-description" task_id="<?= (int)$this -> task['id'];?>" style="margin-top: 10px;">Zapisz treść</a>
|
||||||
|
</div>
|
||||||
<? if ( $this -> task['text'] ):?>
|
<? if ( $this -> task['text'] ):?>
|
||||||
<div class="description">
|
<div class="description">
|
||||||
<a href="#" class="fullscreen"><i class="fa fa-expand"></i></a>
|
<a href="#" class="fullscreen"><i class="fa fa-expand"></i></a>
|
||||||
@@ -219,12 +225,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="dates box">
|
<div class="dates box">
|
||||||
<h3>Termin</h3>
|
<h3>Termin</h3>
|
||||||
<? if ( $this -> task['date_start'] ):?>
|
<div class="task-date-edit-grid">
|
||||||
<div class="date_start"><i class="fa fa-regular fa-calendar"></i><?= $this -> task['date_start'];?></div>
|
<div class="task-date-field">
|
||||||
<? endif;?>
|
<label>Data rozpoczęcia</label>
|
||||||
<? if ( $this -> task['date_end'] ):?>
|
<input type="date" class="form-control task-date-start-input" value="<?= htmlspecialchars( (string)$this -> task['date_start'] );?>">
|
||||||
<div class="date_end <? if ( $this -> task['status'] != 2 and $this -> task['date_end'] == date( 'Y-m-d' ) ):?> warning<? endif;?> <? if ( $this -> task['status'] != 2 and $this -> task['date_end'] < date( 'Y-m-d' ) ):?> dangerx<? endif;?>"><i class="fa fa-regular fa-calendar"></i><?= $this -> task['date_end'];?></div>
|
</div>
|
||||||
<? endif;?>
|
<div class="task-date-field">
|
||||||
|
<label>Data zakończenia</label>
|
||||||
|
<input type="date" class="form-control task-date-end-input" value="<?= htmlspecialchars( (string)$this -> task['date_end'] );?>">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a href="#" class="btn btn-primary btn-sm task-popup-compact-btn js-save-task-dates" task_id="<?= (int)$this -> task['id'];?>" style="margin-top: 10px;">Zapisz terminy</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="client box">
|
<div class="client box">
|
||||||
<h3>Klient</h3>
|
<h3>Klient</h3>
|
||||||
@@ -432,31 +443,95 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.task_popup .task_details .title .task-title-wrapper {
|
.task_popup .task_details .title .task-title-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.task_popup .task_details .title .task-title-view {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 8px;
|
||||||
flex-wrap: wrap;
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
.task_popup .task_details .title .task-title-edit-btn {
|
.task_popup .task_details .title .task-title-text {
|
||||||
font-size: 14px;
|
display: inline-block;
|
||||||
color: #999;
|
max-width: 100%;
|
||||||
margin-left: 5px;
|
white-space: nowrap;
|
||||||
opacity: 0.6;
|
overflow: hidden;
|
||||||
transition: opacity 0.2s;
|
text-overflow: ellipsis;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
.task_popup .task_details .title .task-title-edit-btn:hover {
|
.task_popup .task_details .title .task-title-edit-btn,
|
||||||
opacity: 1;
|
.task_popup .task_details .title .task-title-save,
|
||||||
color: #333;
|
.task_popup .task_details .title .task-title-cancel {
|
||||||
|
border: 1px solid #099885;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border-radius: 6px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #099885;
|
||||||
|
text-decoration: none;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
.task_popup .task_details .title .task-title-edit-btn:hover,
|
||||||
|
.task_popup .task_details .title .task-title-save:hover,
|
||||||
|
.task_popup .task_details .title .task-title-cancel:hover {
|
||||||
|
background: #099885;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.task_popup .task_details .title .task-title-cancel {
|
||||||
|
border-color: #cc563d;
|
||||||
|
color: #cc563d;
|
||||||
|
}
|
||||||
|
.task_popup .task_details .title .task-title-cancel:hover {
|
||||||
|
background: #cc563d;
|
||||||
|
color: #fff;
|
||||||
}
|
}
|
||||||
.task_popup .task_details .title .task-title-edit-box {
|
.task_popup .task_details .title .task-title-edit-box {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 5px;
|
gap: 8px;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
.task_popup .task_details .title .task-title-input {
|
.task_popup .task_details .title .task-title-input {
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 6px;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
padding: 4px 8px;
|
width: 100%;
|
||||||
width: 400px;
|
min-width: 240px;
|
||||||
max-width: 100%;
|
}
|
||||||
|
.task_popup .task_details .content .left .task-description-editor {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.task_popup .task_details .content .left .task-description-editor .task-description-input {
|
||||||
|
min-height: 120px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
.task_popup .task_details .content .right .dates .task-date-edit-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
.task_popup .task_details .content .right .dates .task-date-field label {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
color: #4e5e6a;
|
||||||
|
}
|
||||||
|
.task_popup .task_details .task-popup-compact-btn {
|
||||||
|
height: 30px;
|
||||||
|
padding: 0 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
border-radius: 5px;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
@@ -610,8 +685,7 @@
|
|||||||
|
|
||||||
popup.on( 'click', '.task-title-edit-btn', function( e ) {
|
popup.on( 'click', '.task-title-edit-btn', function( e ) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
popup.find( '.task-title-edit-btn' ).hide();
|
popup.find( '.task-title-view' ).hide();
|
||||||
popup.find( '.task-title-text' ).hide();
|
|
||||||
popup.find( '.task-title-edit-box' ).css( 'display', 'inline-flex' );
|
popup.find( '.task-title-edit-box' ).css( 'display', 'inline-flex' );
|
||||||
popup.find( '.task-title-input' ).focus();
|
popup.find( '.task-title-input' ).focus();
|
||||||
});
|
});
|
||||||
@@ -619,8 +693,7 @@
|
|||||||
popup.on( 'click', '.task-title-cancel', function( e ) {
|
popup.on( 'click', '.task-title-cancel', function( e ) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
popup.find( '.task-title-edit-box' ).hide();
|
popup.find( '.task-title-edit-box' ).hide();
|
||||||
popup.find( '.task-title-text' ).show();
|
popup.find( '.task-title-view' ).show();
|
||||||
popup.find( '.task-title-edit-btn' ).show();
|
|
||||||
// Reset input to current text
|
// Reset input to current text
|
||||||
popup.find( '.task-title-input' ).val( popup.find( '.task-title-text' ).text() );
|
popup.find( '.task-title-input' ).val( popup.find( '.task-title-text' ).text() );
|
||||||
});
|
});
|
||||||
@@ -658,6 +731,70 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
popup.on( 'keypress', '.task-title-input', function( e ) {
|
||||||
|
if ( e.which === 13 )
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
popup.find( '.task-title-save' ).trigger( 'click' );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
popup.on( 'click', '.js-save-task-dates', function( e ) {
|
||||||
|
e.preventDefault();
|
||||||
|
var btn = $( this );
|
||||||
|
var task_id = btn.attr( 'task_id' );
|
||||||
|
var date_start = popup.find( '.task-date-start-input' ).val();
|
||||||
|
var date_end = popup.find( '.task-date-end-input' ).val();
|
||||||
|
|
||||||
|
btn.text( 'Zapisywanie...' ).addClass( 'disabled' );
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: '/tasks/task_change_dates/',
|
||||||
|
data: { task_id: task_id, date_start: date_start, date_end: date_end },
|
||||||
|
success: function( response ) {
|
||||||
|
var res = typeof response === 'string' ? JSON.parse( response ) : response;
|
||||||
|
if ( res.status === 'success' )
|
||||||
|
{
|
||||||
|
btn.text( 'Zapisano!' );
|
||||||
|
if ( typeof getSelectedTaskFilters === 'function' && typeof reload_tasks === 'function' )
|
||||||
|
{
|
||||||
|
var selected_filters = getSelectedTaskFilters();
|
||||||
|
reload_tasks( selected_filters.projects, selected_filters.users );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
btn.text( 'Błąd' );
|
||||||
|
|
||||||
|
setTimeout( function() { btn.text( 'Zapisz terminy' ).removeClass( 'disabled' ); }, 1400 );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
popup.on( 'click', '.js-save-task-description', function( e ) {
|
||||||
|
e.preventDefault();
|
||||||
|
var btn = $( this );
|
||||||
|
var task_id = btn.attr( 'task_id' );
|
||||||
|
var text = popup.find( '.task-description-input' ).val();
|
||||||
|
|
||||||
|
btn.text( 'Zapisywanie...' ).addClass( 'disabled' );
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: '/tasks/task_change_text/',
|
||||||
|
data: { task_id: task_id, text: text },
|
||||||
|
success: function( response ) {
|
||||||
|
var res = typeof response === 'string' ? JSON.parse( response ) : response;
|
||||||
|
if ( res.status === 'success' )
|
||||||
|
{
|
||||||
|
btn.text( 'Zapisano!' );
|
||||||
|
task_popup( task_id, is_task_popup_works_time_open() );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
btn.text( 'Błąd' );
|
||||||
|
|
||||||
|
setTimeout( function() { btn.text( 'Zapisz treść' ).removeClass( 'disabled' ); }, 1400 );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
var interval_id = setInterval( function() {
|
var interval_id = setInterval( function() {
|
||||||
if ( !document.body.contains( popup.get( 0 ) ) )
|
if ( !document.body.contains( popup.get( 0 ) ) )
|
||||||
@@ -675,4 +812,3 @@
|
|||||||
}, 1000 );
|
}, 1000 );
|
||||||
} )();
|
} )();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<th style="width: 60px;">ID</th>
|
<th style="width: 60px;">ID</th>
|
||||||
<th>Imię i nazwisko</th>
|
<th>Imię i nazwisko</th>
|
||||||
<th>Email</th>
|
<th>Email</th>
|
||||||
<th>Uprawnienia</th>
|
<th>Status uprawnien</th>
|
||||||
<th style="width: 240px;">Akcje</th>
|
<th style="width: 240px;">Akcje</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -38,20 +38,20 @@
|
|||||||
<? if ( (int)$user_tmp['id'] === 1 ):?>
|
<? if ( (int)$user_tmp['id'] === 1 ):?>
|
||||||
<span class="label label-info">Pelny dostep</span>
|
<span class="label label-info">Pelny dostep</span>
|
||||||
<? elseif ( isset( $this -> permissions_map[ (int)$user_tmp['id'] ] ) ):?>
|
<? elseif ( isset( $this -> permissions_map[ (int)$user_tmp['id'] ] ) ):?>
|
||||||
|
<? $enabled = 0;?>
|
||||||
<? foreach ( $this -> modules as $mod ):?>
|
<? foreach ( $this -> modules as $mod ):?>
|
||||||
<label style="margin-right: 10px; font-weight: normal; white-space: nowrap;">
|
<? if ( !empty( $this -> permissions_map[ (int)$user_tmp['id'] ][ $mod ] ) ) $enabled++;?>
|
||||||
<input type="checkbox"
|
|
||||||
class="permission-checkbox"
|
|
||||||
data-user-id="<?= (int)$user_tmp['id'];?>"
|
|
||||||
data-module="<?= $mod;?>"
|
|
||||||
<?= $this -> permissions_map[ (int)$user_tmp['id'] ][ $mod ] ? 'checked' : '';?>
|
|
||||||
>
|
|
||||||
<?= htmlspecialchars( $this -> module_labels[ $mod ] );?>
|
|
||||||
</label>
|
|
||||||
<? endforeach;?>
|
<? endforeach;?>
|
||||||
|
<span class="label label-default"><?= (int)$enabled;?> / <?= count( $this -> modules );?></span>
|
||||||
<? endif;?>
|
<? endif;?>
|
||||||
</td>
|
</td>
|
||||||
<td class="center">
|
<td class="center">
|
||||||
|
<? if ( (int)$user_tmp['id'] !== 1 ):?>
|
||||||
|
<a href="#" class="btn btn-info btn_small js-open-user-permissions" data-user-id="<?= (int)$user_tmp['id'];?>">
|
||||||
|
<i class="fa fa-key"></i>
|
||||||
|
Uprawnienia
|
||||||
|
</a>
|
||||||
|
<? endif;?>
|
||||||
<? if ( $is_current ):?>
|
<? if ( $is_current ):?>
|
||||||
<span class="btn btn-default btn_small disabled">Aktywna sesja</span>
|
<span class="btn btn-default btn_small disabled">Aktywna sesja</span>
|
||||||
<? else:?>
|
<? else:?>
|
||||||
@@ -71,32 +71,146 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<style>
|
||||||
|
.users-permissions-popup {
|
||||||
|
max-width: 760px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.users-permissions-popup .header {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.users-permissions-popup .header h3 {
|
||||||
|
margin: 0 0 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.users-permissions-popup .groups {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.users-permissions-popup .group {
|
||||||
|
border: 1px solid #dfe4ea;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 10px;
|
||||||
|
background: #f9fbfd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.users-permissions-popup .group h4 {
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.users-permissions-popup .items {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.users-permissions-popup .item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-weight: normal;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.users-permissions-popup .actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.users-permissions-popup .admin-note {
|
||||||
|
background: #eef5ff;
|
||||||
|
border: 1px solid #cfdcf3;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 980px) {
|
||||||
|
.users-permissions-popup .groups {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<script>
|
<script>
|
||||||
$( document ).on( 'change', '.permission-checkbox', function()
|
$( document ).on( 'click', '.js-open-user-permissions', function(e)
|
||||||
{
|
{
|
||||||
var $cb = $( this );
|
e.preventDefault();
|
||||||
|
var userId = $( this ).data( 'user-id' );
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/users/permission_save/',
|
url: '/users/permission_popup/',
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: {
|
data: {
|
||||||
user_id: $cb.data( 'user-id' ),
|
user_id: userId,
|
||||||
perm_module: $cb.data( 'module' ),
|
|
||||||
value: $cb.is( ':checked' ) ? 1 : 0,
|
|
||||||
csrf_token: '<?= \S::csrf_token();?>'
|
csrf_token: '<?= \S::csrf_token();?>'
|
||||||
},
|
},
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function( r )
|
success: function( r )
|
||||||
{
|
{
|
||||||
if ( r.status !== 'success' )
|
if ( r.status === 'success' && r.popup_content )
|
||||||
{
|
{
|
||||||
alert( r.msg || 'Blad zapisu uprawnien.' );
|
show_default_popup( r.popup_content );
|
||||||
$cb.prop( 'checked', !$cb.is( ':checked' ) );
|
$( '.users-permissions-popup input.g-checkbox' ).iCheck({
|
||||||
|
checkboxClass: 'icheckbox_square-blue',
|
||||||
|
radioClass: 'iradio_square-blue'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
alert( r.msg || 'Nie udalo sie otworzyc okna uprawnien.' );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function()
|
||||||
|
{
|
||||||
|
alert( 'Blad polaczenia z serwerem.' );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$( document ).on( 'click', '.js-users-permissions-cancel', function(e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
$( '.default_popup .close' ).trigger( 'click' );
|
||||||
|
});
|
||||||
|
|
||||||
|
$( document ).on( 'click', '.js-users-permissions-save', function(e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
var userId = $( this ).data( 'user-id' );
|
||||||
|
var selected = $( '.users-permissions-popup .permission-popup-checkbox:checked' ).map(function() {
|
||||||
|
return $( this ).data( 'module' );
|
||||||
|
}).get();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/users/permission_save_bulk/',
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
user_id: userId,
|
||||||
|
selected_modules: selected.join( ',' ),
|
||||||
|
csrf_token: '<?= \S::csrf_token();?>'
|
||||||
|
},
|
||||||
|
dataType: 'json',
|
||||||
|
success: function( r )
|
||||||
|
{
|
||||||
|
if ( r.status === 'success' )
|
||||||
|
{
|
||||||
|
$( '.default_popup .close' ).trigger( 'click' );
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
alert( r.msg || 'Blad zapisu uprawnien.' );
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function()
|
error: function()
|
||||||
{
|
{
|
||||||
alert( 'Blad polaczenia z serwerem.' );
|
alert( 'Blad polaczenia z serwerem.' );
|
||||||
$cb.prop( 'checked', !$cb.is( ':checked' ) );
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
36
templates/users/permissions-popup.php
Normal file
36
templates/users/permissions-popup.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<div class="users-permissions-popup">
|
||||||
|
<div class="header">
|
||||||
|
<h3>Uprawnienia uzytkownika</h3>
|
||||||
|
<div class="user">
|
||||||
|
<?= htmlspecialchars( trim( $this -> target_user['name'] . ' ' . $this -> target_user['surname'] ) );?>
|
||||||
|
(<?= htmlspecialchars( $this -> target_user['email'] );?>)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<? if ( (int)$this -> target_user['id'] === 1 ):?>
|
||||||
|
<div class="admin-note">Administrator ma pelny dostep i jego uprawnienia nie sa edytowalne.</div>
|
||||||
|
<? else:?>
|
||||||
|
<div class="groups">
|
||||||
|
<? foreach ( $this -> permission_groups as $group_name => $group_modules ):?>
|
||||||
|
<div class="group">
|
||||||
|
<h4><?= htmlspecialchars( $group_name );?></h4>
|
||||||
|
<div class="items">
|
||||||
|
<? foreach ( $group_modules as $mod ):?>
|
||||||
|
<label class="item">
|
||||||
|
<input type="checkbox" class="g-checkbox permission-popup-checkbox" data-module="<?= htmlspecialchars( $mod );?>" <?= !empty( $this -> permissions[ $mod ] ) ? 'checked="checked"' : '';?>>
|
||||||
|
<span><?= htmlspecialchars( $this -> module_labels[ $mod ] );?></span>
|
||||||
|
</label>
|
||||||
|
<? endforeach;?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<? endforeach;?>
|
||||||
|
</div>
|
||||||
|
<? endif;?>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<a href="#" class="btn btn-default js-users-permissions-cancel">Anuluj</a>
|
||||||
|
<? if ( (int)$this -> target_user['id'] !== 1 ):?>
|
||||||
|
<a href="#" class="btn btn-primary js-users-permissions-save" data-user-id="<?= (int)$this -> target_user['id'];?>">Zapisz</a>
|
||||||
|
<? endif;?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -15,18 +15,26 @@ function run_permission_repository_tests()
|
|||||||
// Test MODULES constant
|
// Test MODULES constant
|
||||||
$modules = PermissionRepository::MODULES;
|
$modules = PermissionRepository::MODULES;
|
||||||
assert_perm( true, in_array( 'tasks', $modules ), 'MODULES should contain tasks' );
|
assert_perm( true, in_array( 'tasks', $modules ), 'MODULES should contain tasks' );
|
||||||
|
assert_perm( true, in_array( 'projects_view', $modules ), 'MODULES should contain projects_view' );
|
||||||
|
assert_perm( true, in_array( 'projects_add', $modules ), 'MODULES should contain projects_add' );
|
||||||
|
assert_perm( true, in_array( 'projects_edit', $modules ), 'MODULES should contain projects_edit' );
|
||||||
|
assert_perm( true, in_array( 'projects_delete', $modules ), 'MODULES should contain projects_delete' );
|
||||||
assert_perm( true, in_array( 'finances', $modules ), 'MODULES should contain finances' );
|
assert_perm( true, in_array( 'finances', $modules ), 'MODULES should contain finances' );
|
||||||
assert_perm( 6, count( $modules ), 'MODULES should have 6 entries' );
|
assert_perm( 9, count( $modules ), 'MODULES should have 9 entries' );
|
||||||
assert_perm( false, in_array( 'zaplecze', $modules ), 'MODULES should not contain zaplecze' );
|
assert_perm( false, in_array( 'zaplecze', $modules ), 'MODULES should not contain zaplecze' );
|
||||||
|
|
||||||
// Test DEFAULTS constant
|
// Test DEFAULTS constant
|
||||||
$defaults = PermissionRepository::DEFAULTS;
|
$defaults = PermissionRepository::DEFAULTS;
|
||||||
assert_perm( 1, $defaults['tasks'], 'tasks should default to 1' );
|
assert_perm( 1, $defaults['tasks'], 'tasks should default to 1' );
|
||||||
|
assert_perm( 1, $defaults['projects_view'], 'projects_view should default to 1' );
|
||||||
|
assert_perm( 1, $defaults['projects_add'], 'projects_add should default to 1' );
|
||||||
|
assert_perm( 1, $defaults['projects_edit'], 'projects_edit should default to 1' );
|
||||||
|
assert_perm( 1, $defaults['projects_delete'], 'projects_delete should default to 1' );
|
||||||
assert_perm( 0, $defaults['finances'], 'finances should default to 0' );
|
assert_perm( 0, $defaults['finances'], 'finances should default to 0' );
|
||||||
assert_perm( 0, $defaults['crm'], 'crm should default to 0' );
|
assert_perm( 0, $defaults['crm'], 'crm should default to 0' );
|
||||||
|
|
||||||
// Test defaults() returns full module array
|
// Test defaults() returns full module array
|
||||||
$result = PermissionRepository::defaults();
|
$result = PermissionRepository::defaults();
|
||||||
assert_perm( 6, count( $result ), 'defaults() should return 6 modules' );
|
assert_perm( 9, count( $result ), 'defaults() should return 9 modules' );
|
||||||
assert_perm( 1, $result['tasks'], 'defaults() tasks should be 1' );
|
assert_perm( 1, $result['tasks'], 'defaults() tasks should be 1' );
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user