Files
awo-hamburg-intranet/module/tasks/bootstrap.php

701 lines
24 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
require_once __DIR__ . '/lib/Logger.php';
if (!function_exists('task_cert_bootstrap_output_warning')) {
function task_cert_bootstrap_output_warning(string $message): void
{
if (PHP_SAPI === 'cli') {
fwrite(STDERR, $message . PHP_EOL);
return;
}
$safeMessage = htmlspecialchars($message, ENT_QUOTES, 'UTF-8');
$requestUri = (string) ($_SERVER['REQUEST_URI'] ?? '');
$accept = (string) ($_SERVER['HTTP_ACCEPT'] ?? '');
$isAjax = strtolower((string) ($_SERVER['HTTP_X_REQUESTED_WITH'] ?? '')) === 'xmlhttprequest';
$expectsJson = (
strpos($requestUri, '/endpoints/') !== false
|| strpos($accept, 'application/json') !== false
|| $isAjax
);
if ($expectsJson) {
if (!headers_sent()) {
http_response_code(500);
header('Content-Type: application/json; charset=utf-8');
}
echo json_encode([
'success' => 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 '<div class="task-cert"><div class="task-cert-error">' . $safeMessage . '</div></div>';
}
}
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) {
// Endpoint-Requests überspringen start.inc.php (kein session_name-Setup durch den Monolith).
// Cookie-Detection stellt sicher, dass wir dieselbe Session wie der Monolith verwenden
// (Monolith setzt den Session-Namen auf "sid{site_code}", z.B. "sidawo").
if (session_name() === 'PHPSESSID') {
foreach (array_keys($_COOKIE) as $_tc_sn) {
if (strncmp($_tc_sn, 'sid', 3) === 0) {
session_name($_tc_sn);
break;
}
}
unset($_tc_sn);
}
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 '<link rel="stylesheet" href="' . htmlspecialchars($href, ENT_QUOTES, 'UTF-8') . '">' . 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 '<script src="' . htmlspecialchars($src, ENT_QUOTES, 'UTF-8') . '" defer></script>' . PHP_EOL;
$printed[$cleanFile] = true;
}
}