false, 'error' => $message, ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); return; } if (!headers_sent()) { http_response_code(500); header('Content-Type: text/html; charset=utf-8'); } echo '
' . $safeMessage . '
'; } } if (!function_exists('task_cert_bootstrap_truthy')) { function task_cert_bootstrap_truthy(string $value): bool { return in_array(strtolower(trim($value)), ['1', 'true', 'yes', 'on', 'always'], true); } } if (!function_exists('task_cert_is_endpoint_request')) { function task_cert_is_endpoint_request(): bool { $uri = strtolower((string) ($_SERVER['REQUEST_URI'] ?? '')); $scriptName = strtolower((string) ($_SERVER['SCRIPT_NAME'] ?? '')); $scriptFilename = strtolower((string) ($_SERVER['SCRIPT_FILENAME'] ?? '')); return ( strpos($uri, '/endpoints/') !== false || strpos($scriptName, '/endpoints/') !== false || strpos($scriptFilename, '/endpoints/') !== false ); } } if (!function_exists('task_cert_has_monolith_context')) { function task_cert_has_monolith_context(): bool { return ( is_array($GLOBALS['site'] ?? null) && is_array($GLOBALS['language'] ?? null) && ( is_array($GLOBALS['main_contact'] ?? null) || isset($_SESSION['login_id']) ) ); } } if (!function_exists('task_cert_bootstrap_context_fallback')) { function task_cert_bootstrap_context_fallback(): void { if (!is_array($GLOBALS['site'] ?? null)) { $GLOBALS['site'] = []; } if (!is_array($GLOBALS['language'] ?? null)) { $GLOBALS['language'] = []; } if (!is_array($GLOBALS['main_contact'] ?? null)) { $GLOBALS['main_contact'] = []; } $uri = (string) ($_SERVER['REQUEST_URI'] ?? ''); $path = parse_url($uri, PHP_URL_PATH); $segments = array_values(array_filter(explode('/', trim((string) $path, '/')), static function (string $segment): bool { return $segment !== ''; })); $siteCode = ''; $languageCode = ''; if (!task_cert_is_endpoint_request()) { if (isset($segments[0])) { $siteCode = strtolower((string) (preg_replace('/[^a-z0-9_-]/i', '', $segments[0]) ?? '')); } if (isset($segments[1])) { $languageCode = strtolower((string) (preg_replace('/[^a-z0-9_-]/i', '', $segments[1]) ?? '')); } } if (($GLOBALS['site']['code'] ?? '') === '') { $GLOBALS['site']['code'] = $siteCode !== '' ? $siteCode : 'intranet'; } if (($GLOBALS['language']['code'] ?? '') === '') { $GLOBALS['language']['code'] = $languageCode !== '' ? $languageCode : 'de'; } if (!isset($GLOBALS['site']['id'])) { $GLOBALS['site']['id'] = 0; } if (!isset($GLOBALS['language']['id'])) { $GLOBALS['language']['id'] = 1; } $loginId = (int) ($_SESSION['login_id'] ?? ($_SESSION['main_contact_id'] ?? ($_SESSION['contact_id'] ?? 0))); if ((int) ($GLOBALS['main_contact']['id'] ?? 0) <= 0 && isset($_SESSION['main_contact']) && is_array($_SESSION['main_contact'])) { $GLOBALS['main_contact'] = $_SESSION['main_contact']; } $conn = $GLOBALS['mysql_con'] ?? null; if ($loginId > 0 && (int) ($GLOBALS['main_contact']['id'] ?? 0) <= 0 && $conn instanceof mysqli) { try { $stmt = $conn->prepare('SELECT * FROM main_contact WHERE id = ? LIMIT 1'); if ($stmt instanceof mysqli_stmt) { $stmt->bind_param('i', $loginId); $stmt->execute(); $result = $stmt->get_result(); if ($result instanceof mysqli_result) { $row = $result->fetch_assoc(); if (is_array($row)) { $GLOBALS['main_contact'] = $row; } $result->free(); } $stmt->close(); } } catch (Throwable $throwable) { TaskCertLogger::warning('Fallback context: could not load main_contact', [ 'login_id' => $loginId, 'exception' => $throwable, ]); } } if ((int) ($GLOBALS['main_contact']['id'] ?? 0) > 0 && !isset($_SESSION['main_contact'])) { $_SESSION['main_contact'] = $GLOBALS['main_contact']; } } } if (!defined('TASK_CERT_BOOTSTRAPPED')) { define('TASK_CERT_BOOTSTRAPPED', true); $taskCertModuleDir = __DIR__; $rootCandidates = [ dirname(dirname($taskCertModuleDir)), dirname($taskCertModuleDir), $taskCertModuleDir, ]; $rootDir = $rootCandidates[0]; foreach ($rootCandidates as $candidate) { if ( file_exists($candidate . '/mysyde/frontend/frontend_functions.inc.php') || file_exists($candidate . '/vendor/autoload.php') || is_dir($candidate . '/config') ) { $rootDir = $candidate; break; } } if (!defined('TASK_CERT_USERDATA_DIR')) { define('TASK_CERT_USERDATA_DIR', $rootDir . '/userdata'); } $autoloadFile = $rootDir . '/vendor/autoload.php'; if (file_exists($autoloadFile)) { require_once $autoloadFile; } $frontendFunctions = $rootDir . '/mysyde/frontend/frontend_functions.inc.php'; if (file_exists($frontendFunctions)) { require_once $frontendFunctions; } $commonFunctions = $rootDir . '/mysyde/common/common_functions.inc.php'; if (file_exists($commonFunctions)) { require_once $commonFunctions; } $intranetFunctionCandidates = [ $rootDir . '/mysyde/intranet/intranet_functions.inc.php', $rootDir . '/mysyde/frontend/intranet_functions.inc.php', $rootDir . '/intranet_functions.inc.php', ]; foreach ($intranetFunctionCandidates as $intranetFunctions) { if (file_exists($intranetFunctions)) { require_once $intranetFunctions; break; } } $envDir = $rootDir . '/config'; $envFile = $envDir . '/.env'; if (is_dir($envDir) && file_exists($envFile) && class_exists('\Dotenv\Dotenv')) { $dotenv = new \Dotenv\Dotenv($envDir); $dotenv->load(); } $connEnv = null; if (function_exists('local_environment')) { $connEnv = local_environment() ? $rootDir . '/mysyde/dc.config.php' : $rootDir . '/mysyde/dc-server.config.php'; } if ($connEnv !== null && file_exists($connEnv)) { require_once $connEnv; } $startIncPath = $rootDir . '/mysyde/frontend/start.inc.php'; $startIncMode = strtolower(trim((string) (getenv('TASK_CERT_BOOTSTRAP_START_INC') ?: ($_ENV['TASK_CERT_BOOTSTRAP_START_INC'] ?? 'auto')))); $shouldLoadStartInc = false; if ($startIncMode === 'always' || task_cert_bootstrap_truthy($startIncMode)) { $shouldLoadStartInc = true; } elseif (in_array($startIncMode, ['never', '0', 'false', 'off'], true)) { $shouldLoadStartInc = false; } else { // Auto mode: include start.inc for view-like requests, skip for direct endpoint calls. $shouldLoadStartInc = !task_cert_is_endpoint_request(); } if (!task_cert_has_monolith_context() && $shouldLoadStartInc && file_exists($startIncPath)) { try { require_once $startIncPath; TaskCertLogger::info('Monolith start.inc included', [ 'path' => $startIncPath, 'mode' => $startIncMode, 'request_uri' => (string) ($_SERVER['REQUEST_URI'] ?? ''), ]); } catch (Throwable $throwable) { TaskCertLogger::warning('Monolith start.inc include failed, continuing with fallback', [ 'path' => $startIncPath, 'mode' => $startIncMode, 'exception' => $throwable, ]); } } if (session_status() === PHP_SESSION_NONE) { session_start(); } $dbConnectError = ''; if (!isset($GLOBALS['mysql_con']) || !($GLOBALS['mysql_con'] instanceof mysqli)) { if (function_exists('db_connect')) { try { db_connect(); } catch (Throwable $throwable) { $dbConnectError = $throwable->getMessage(); } } } if (!isset($GLOBALS['mysql_con']) || !($GLOBALS['mysql_con'] instanceof mysqli)) { mysqli_report(MYSQLI_REPORT_OFF); $mysqlCon = @mysqli_connect( getenv('MAIN_MYSQL_DB_HOST') ?: '127.0.0.1', getenv('MAIN_MYSQL_DB_USER') ?: '', getenv('MAIN_MYSQL_DB_PASS') ?: '', getenv('MAIN_MYSQL_DB_SCHEMA') ?: '', (int) (getenv('MAIN_MYSQL_DB_PORT') ?: 3306) ); mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); if ($mysqlCon) { mysqli_set_charset($mysqlCon, 'utf8mb4'); $GLOBALS['mysql_con'] = $mysqlCon; } else { $driverError = mysqli_connect_error(); $errorParts = []; if ($dbConnectError !== '') { $errorParts[] = 'Legacy-Connector: ' . $dbConnectError; } if ($driverError !== '') { $errorParts[] = 'mysqli: ' . $driverError; } $details = $errorParts !== [] ? ' Details: ' . implode(' | ', $errorParts) : ''; $message = 'task_cert: Datenbankverbindung konnte nicht aufgebaut werden.' . $details; $GLOBALS['task_cert_bootstrap_warning'] = $message; TaskCertLogger::error('Bootstrap DB connection failed', [ 'message' => $message, 'db_connect_error' => $dbConnectError, 'mysqli_error' => $driverError, ]); task_cert_bootstrap_output_warning($message); exit(PHP_SAPI === 'cli' ? 1 : 0); } } if (!task_cert_has_monolith_context()) { task_cert_bootstrap_context_fallback(); TaskCertLogger::info('Monolith context fallback applied', [ 'site_code' => (string) ($GLOBALS['site']['code'] ?? ''), 'language_code' => (string) ($GLOBALS['language']['code'] ?? ''), 'contact_id' => (int) ($GLOBALS['main_contact']['id'] ?? 0), 'request_uri' => (string) ($_SERVER['REQUEST_URI'] ?? ''), ]); } require_once __DIR__ . '/lib/Db.php'; require_once __DIR__ . '/lib/AuthContext.php'; require_once __DIR__ . '/lib/Request.php'; require_once __DIR__ . '/lib/Response.php'; require_once __DIR__ . '/lib/View.php'; require_once __DIR__ . '/lib/Csrf.php'; require_once __DIR__ . '/lib/ContactPrefillColumns.php'; require_once __DIR__ . '/lib/CronHeartbeat.php'; require_once __DIR__ . '/lib/TaskCertConstants.php'; require_once __DIR__ . '/lib/TaskUploadPolicy.php'; require_once __DIR__ . '/lib/TaskUploadStorage.php'; require_once __DIR__ . '/lib/Html.php'; require_once __DIR__ . '/repo/TaskDefinitionRepository.php'; require_once __DIR__ . '/repo/TaskChangeLogRepository.php'; require_once __DIR__ . '/repo/TaskCategoryRepository.php'; require_once __DIR__ . '/repo/TaskCategoryChangeLogRepository.php'; require_once __DIR__ . '/repo/TaskLookupRepository.php'; require_once __DIR__ . '/repo/TaskMonitoringRepository.php'; require_once __DIR__ . '/repo/TaskRuleRepository.php'; require_once __DIR__ . '/repo/TaskAssignmentRepository.php'; require_once __DIR__ . '/repo/TaskSubmissionRepository.php'; require_once __DIR__ . '/repo/TaskCertificateRepository.php'; require_once __DIR__ . '/service/TaskDefinitionService.php'; require_once __DIR__ . '/service/TaskCategoryService.php'; require_once __DIR__ . '/service/TaskLookupService.php'; require_once __DIR__ . '/service/TaskMonitoringService.php'; require_once __DIR__ . '/service/CycleCalculatorService.php'; require_once __DIR__ . '/service/AssignmentResolverService.php'; require_once __DIR__ . '/service/CompletionService.php'; require_once __DIR__ . '/service/ApprovalService.php'; require_once __DIR__ . '/service/CertificateService.php'; require_once __DIR__ . '/service/EscalationService.php'; } function task_cert_services(): array { static $services = null; if ($services !== null) { return $services; } $definitionRepo = new TaskDefinitionRepository(); $changeLogRepo = new TaskChangeLogRepository(); $taskCategoryRepo = new TaskCategoryRepository(); $taskCategoryChangeLogRepo = new TaskCategoryChangeLogRepository(); $lookupRepo = new TaskLookupRepository(); $monitoringRepo = new TaskMonitoringRepository(); $ruleRepo = new TaskRuleRepository(); $assignmentRepo = new TaskAssignmentRepository(); $submissionRepo = new TaskSubmissionRepository(); $certificateRepo = new TaskCertificateRepository(); $cycleCalculatorService = new CycleCalculatorService(); $certificateService = new CertificateService( $certificateRepo, $assignmentRepo, $definitionRepo, $cycleCalculatorService ); $assignmentResolverService = new AssignmentResolverService( $definitionRepo, $ruleRepo, $assignmentRepo, $submissionRepo, $certificateRepo, $cycleCalculatorService, $changeLogRepo ); $taskDefinitionService = new TaskDefinitionService( $definitionRepo, $taskCategoryRepo, $ruleRepo, $assignmentRepo, $submissionRepo, $certificateRepo, $cycleCalculatorService, $assignmentResolverService, $changeLogRepo ); $completionService = new CompletionService( $ruleRepo, $assignmentRepo, $submissionRepo, $certificateService, $definitionRepo, $assignmentResolverService ); $services = [ 'definitionRepo' => $definitionRepo, 'taskCategoryRepo' => $taskCategoryRepo, 'taskCategoryChangeLogRepo' => $taskCategoryChangeLogRepo, 'lookupRepo' => $lookupRepo, 'changeLogRepo' => $changeLogRepo, 'monitoringRepo' => $monitoringRepo, 'ruleRepo' => $ruleRepo, 'assignmentRepo' => $assignmentRepo, 'submissionRepo' => $submissionRepo, 'certificateRepo' => $certificateRepo, 'cycleCalculatorService' => $cycleCalculatorService, 'taskCategoryService' => new TaskCategoryService( $taskCategoryRepo, $taskCategoryChangeLogRepo ), 'lookupService' => new TaskLookupService($lookupRepo), 'taskMonitoringService' => new TaskMonitoringService( $monitoringRepo, $cycleCalculatorService, $ruleRepo ), 'taskDefinitionService' => $taskDefinitionService, 'assignmentResolverService' => $assignmentResolverService, 'completionService' => $completionService, 'approvalService' => new ApprovalService( $submissionRepo, $assignmentRepo, $completionService ), 'certificateService' => $certificateService, 'escalationService' => new EscalationService( $assignmentRepo, $ruleRepo, $changeLogRepo ), ]; return $services; } function task_cert_asset_url(string $relativeAssetPath): string { $cleanPath = ltrim($relativeAssetPath, '/'); return task_cert_url('assets/' . $cleanPath); } function task_cert_base_url(): string { static $baseUrl = null; if ($baseUrl !== null) { return $baseUrl; } $configured = (string) (getenv('TASK_CERT_BASE_URL') ?: ($_ENV['TASK_CERT_BASE_URL'] ?? '')); if (trim($configured) === '') { $configured = '/module/tasks'; } $configured = '/' . trim($configured, '/'); $baseUrl = ($configured === '/') ? '' : $configured; return $baseUrl; } function task_cert_site_code(): string { $siteCode = ''; $siteGlobal = $GLOBALS['site'] ?? null; if (is_array($siteGlobal)) { $siteCode = (string) ($siteGlobal['code'] ?? ($siteGlobal['site_code'] ?? '')); } if ($siteCode === '') { $siteCode = (string) ($GLOBALS['site_code'] ?? ''); } if ($siteCode === '' && isset($_SESSION['site']) && is_array($_SESSION['site'])) { $siteCode = (string) ($_SESSION['site']['code'] ?? ($_SESSION['site']['site_code'] ?? '')); } if ($siteCode === '') { $siteCode = (string) ($_SESSION['site_code'] ?? ''); } if ($siteCode === '') { if (!task_cert_is_endpoint_request()) { $requestPath = (string) parse_url((string) ($_SERVER['REQUEST_URI'] ?? ''), PHP_URL_PATH); $segments = array_values(array_filter(explode('/', trim($requestPath, '/')))); if (isset($segments[0])) { $siteCode = (string) $segments[0]; } } } $siteCode = strtolower(trim($siteCode)); $siteCode = preg_replace('/[^a-z0-9_-]/', '', $siteCode) ?? ''; return $siteCode !== '' ? $siteCode : 'intranet'; } function task_cert_language_code(): string { $languageCode = ''; $languageGlobal = $GLOBALS['language'] ?? null; if (is_array($languageGlobal)) { $languageCode = (string) ($languageGlobal['code'] ?? ''); } if ($languageCode === '' && isset($_SESSION['language']) && is_array($_SESSION['language'])) { $languageCode = (string) ($_SESSION['language']['code'] ?? ''); } if ($languageCode === '') { $languageCode = (string) ($GLOBALS['language_code'] ?? ($_SESSION['language_code'] ?? '')); } if ($languageCode === '') { if (!task_cert_is_endpoint_request()) { $requestPath = (string) parse_url((string) ($_SERVER['REQUEST_URI'] ?? ''), PHP_URL_PATH); $segments = array_values(array_filter(explode('/', trim($requestPath, '/')))); if (isset($segments[1])) { $languageCode = (string) $segments[1]; } } } $languageCode = strtolower(trim($languageCode)); $languageCode = preg_replace('/[^a-z0-9_-]/', '', $languageCode) ?? ''; return $languageCode !== '' ? $languageCode : 'de'; } function task_cert_view_base_url(): string { static $baseUrl = null; if ($baseUrl !== null) { return $baseUrl; } $configured = trim((string) (getenv('TASK_CERT_VIEW_BASE_URL') ?: ($_ENV['TASK_CERT_VIEW_BASE_URL'] ?? ''))); if ($configured !== '') { $configured = '/' . trim($configured, '/'); $baseUrl = ($configured === '/') ? '' : $configured; return $baseUrl; } $slug = trim((string) (getenv('TASK_CERT_VIEW_SLUG') ?: ($_ENV['TASK_CERT_VIEW_SLUG'] ?? 'tasks-cert'))); $slug = strtolower($slug); $slug = preg_replace('/[^a-z0-9_-]/', '', $slug) ?? ''; if ($slug === '') { $slug = 'tasks-cert'; } $baseUrl = '/' . task_cert_site_code() . '/' . task_cert_language_code() . '/' . $slug; return $baseUrl; } function task_cert_view_url(string $viewKey = 'admin.task_list', array $query = []): string { $viewMap = [ 'admin.task_list' => 'admin-task-list', 'admin.task_form' => 'edit', 'admin.task_detail' => 'view', 'admin.approval_queue' => 'approvals', 'admin.escalation_dashboard' => 'escalations', 'admin.task_categories' => 'categories', 'user.my_tasks' => 'my-tasks', 'user.task_detail' => 'task', 'user.overdue_widget' => 'overdue-widget', 'user.summary_widget' => 'summary-widget', 'user.escalation_overlay_widget' => 'escalation-overlay-widget', ]; $relativePath = $viewMap[$viewKey] ?? trim($viewKey); $base = rtrim(task_cert_view_base_url(), '/'); $cleanPath = trim((string) $relativePath, '/'); $url = $base; if ($cleanPath !== '') { $url .= '/' . $cleanPath . '/'; } else { $url .= '/'; } if ($viewKey === 'admin.task_detail' && !array_key_exists('route_guard', $query)) { // Guard param prevents monolith route edge cases when URL ends with numeric query values. $query['route_guard'] = 'load/'; } if ($query !== []) { $queryString = http_build_query($query); if ($queryString !== '') { $url .= '?' . $queryString; } } return $url; } function task_cert_url(string $relativePath = ''): string { $base = task_cert_base_url(); $cleanPath = ltrim(trim($relativePath), '/'); if ($cleanPath === '') { return $base === '' ? '/' : $base; } return ($base === '' ? '' : $base) . '/' . $cleanPath; } function task_cert_asset_versioned_url(string $relativeAssetPath): string { $cleanPath = ltrim($relativeAssetPath, '/'); $absolutePath = __DIR__ . '/assets/' . $cleanPath; $version = file_exists($absolutePath) ? (string) filemtime($absolutePath) : (string) time(); return task_cert_asset_url($cleanPath) . '?v=' . rawurlencode($version); } function task_cert_render_styles(array $variants = [], array $extraFiles = []): void { static $printed = []; $files = [ 'css/task_cert.css', ]; if (in_array('admin', $variants, true)) { $files[] = 'css/task_cert_admin.css'; } if (in_array('user', $variants, true)) { $files[] = 'css/task_cert_user.css'; } $files = array_values(array_unique(array_merge($files, $extraFiles))); foreach ($files as $file) { if (isset($printed[$file])) { continue; } $href = task_cert_asset_versioned_url($file); echo '' . PHP_EOL; $printed[$file] = true; } } function task_cert_render_scripts(array $files): void { static $printed = []; foreach ($files as $file) { $cleanFile = ltrim((string) $file, '/'); if ($cleanFile === '' || isset($printed[$cleanFile])) { continue; } $src = task_cert_asset_versioned_url($cleanFile); echo '' . PHP_EOL; $printed[$cleanFile] = true; } }