Consolidates the previously separate omsorgapp and omsorgCore repos (each had their own nested .git with GitHub history) plus the old root-level website/mitarbeiter-app into a single monorepo, matching the structure already documented in the root CLAUDE.md. Also moves the PHP employee app aside as omsorgWeb/mitarbeiter-app-legacy/ to serve as a template for a ground-up rewrite. Fixes .gitignore in the same pass: the config-secrets/uploads/data patterns were unanchored (relative to repo root, not depth-agnostic), so they silently stopped matching once the app moved under omsorgWeb/. Patterns are now **/-prefixed and cover both mitarbeiter-app and mitarbeiter-app-legacy, keeping DB/SMTP credentials and uploaded employee documents out of version control. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
133 lines
4.7 KiB
PHP
133 lines
4.7 KiB
PHP
<?php
|
|
require_once __DIR__ . '/auth.php';
|
|
|
|
// Gemeinsame Endung→MIME-Whitelists.
|
|
const UPLOAD_TYPES_DOCS = [
|
|
'pdf' => ['application/pdf'],
|
|
'doc' => ['application/msword'],
|
|
'docx' => ['application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
|
|
'jpg' => ['image/jpeg'],
|
|
'jpeg' => ['image/jpeg'],
|
|
'png' => ['image/png'],
|
|
];
|
|
|
|
const UPLOAD_TYPES_PDF_IMG = [
|
|
'pdf' => ['application/pdf'],
|
|
'jpg' => ['image/jpeg'],
|
|
'jpeg' => ['image/jpeg'],
|
|
'png' => ['image/png'],
|
|
];
|
|
|
|
const UPLOAD_TYPES_IMAGES = [
|
|
'jpg' => ['image/jpeg'],
|
|
'jpeg' => ['image/jpeg'],
|
|
'png' => ['image/png'],
|
|
'gif' => ['image/gif'],
|
|
'webp' => ['image/webp'],
|
|
];
|
|
|
|
const UPLOAD_TYPES_PDF = [
|
|
'pdf' => ['application/pdf'],
|
|
];
|
|
|
|
const UPLOAD_TYPES_OFFICE = [
|
|
'pdf' => ['application/pdf'],
|
|
'doc' => ['application/msword'],
|
|
'docx' => ['application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
|
|
'xls' => ['application/vnd.ms-excel'],
|
|
'xlsx' => ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
|
|
'ppt' => ['application/vnd.ms-powerpoint'],
|
|
'pptx' => ['application/vnd.openxmlformats-officedocument.presentationml.presentation'],
|
|
'txt' => ['text/plain'],
|
|
'zip' => ['application/zip', 'application/x-zip-compressed'],
|
|
];
|
|
|
|
/**
|
|
* Validiert und speichert eine hochgeladene Datei.
|
|
*
|
|
* @param array $file Eintrag aus $_FILES, z.B. $_FILES['datei']
|
|
* @param array $opts allowed (ext=>mimes, Pflicht), dir (Pflicht), prefix,
|
|
* max_bytes (Default 12 MB), required (Default true),
|
|
* naming ('dated' [Default] = Datum_Username_Prefix_Rand,
|
|
* 'plain' = Username_Rand, 'hash' = nur Rand),
|
|
* username (Default current_username(); für 'dated'/'plain'),
|
|
* messages (Überschreibt Standardtexte: missing/upload/ext/mime/size/move)
|
|
* @return array ['ok'=>bool, 'error'=>?string, 'filename'=>?string,
|
|
* 'original_name'=>?string, 'mime'=>?string]
|
|
*/
|
|
function handle_upload(array $file, array $opts): array {
|
|
$allowed = $opts['allowed'];
|
|
$dir = rtrim($opts['dir'], '/') . '/';
|
|
$maxBytes = $opts['max_bytes'] ?? 12 * 1024 * 1024;
|
|
$required = $opts['required'] ?? true;
|
|
$naming = $opts['naming'] ?? 'dated';
|
|
$prefix = $opts['prefix'] ?? 'datei';
|
|
$username = $opts['username'] ?? current_username();
|
|
|
|
$msg = array_merge([
|
|
'missing' => 'Bitte eine Datei auswählen.',
|
|
'upload' => 'Fehler beim Hochladen der Datei.',
|
|
'ext' => 'Dateityp nicht erlaubt.',
|
|
'mime' => 'Dateityp nicht erlaubt.',
|
|
'size' => 'Datei ist zu groß (max. 12 MB).',
|
|
'move' => 'Fehler beim Hochladen der Datei.',
|
|
], $opts['messages'] ?? []);
|
|
|
|
$err = $file['error'] ?? UPLOAD_ERR_NO_FILE;
|
|
|
|
if ($err === UPLOAD_ERR_NO_FILE || !isset($file['tmp_name'])) {
|
|
if ($required) return ['ok' => false, 'error' => $msg['missing']];
|
|
return ['ok' => true, 'error' => null, 'filename' => null, 'original_name' => null, 'mime' => null];
|
|
}
|
|
if ($err !== UPLOAD_ERR_OK) {
|
|
return ['ok' => false, 'error' => $msg['upload']];
|
|
}
|
|
|
|
$originalName = $file['name'];
|
|
$ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
|
|
if (!isset($allowed[$ext])) {
|
|
return ['ok' => false, 'error' => $msg['ext']];
|
|
}
|
|
|
|
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
|
$mime = $finfo->file($file['tmp_name']);
|
|
if (!in_array($mime, $allowed[$ext], true)) {
|
|
return ['ok' => false, 'error' => $msg['mime']];
|
|
}
|
|
|
|
if ($file['size'] > $maxBytes) {
|
|
return ['ok' => false, 'error' => $msg['size']];
|
|
}
|
|
|
|
$safeName = match ($naming) {
|
|
'hash' => bin2hex(random_bytes(16)) . '.' . $ext,
|
|
'plain' => $username . '_' . bin2hex(random_bytes(8)) . '.' . $ext,
|
|
default => date('Y-m-d') . '_' . $username . '_' . $prefix . '_' . bin2hex(random_bytes(8)) . '.' . $ext,
|
|
};
|
|
|
|
if (!is_dir($dir)) {
|
|
mkdir($dir, 0755, true);
|
|
}
|
|
if (!move_uploaded_file($file['tmp_name'], $dir . $safeName)) {
|
|
return ['ok' => false, 'error' => $msg['move']];
|
|
}
|
|
|
|
return [
|
|
'ok' => true,
|
|
'error' => null,
|
|
'filename' => $safeName,
|
|
'original_name' => $originalName,
|
|
'mime' => $mime,
|
|
];
|
|
}
|
|
|
|
function redirect_error(string $back, string $msg): never {
|
|
header('Location: ' . $back . '?error=' . urlencode($msg));
|
|
exit;
|
|
}
|
|
|
|
function redirect_ok(string $back, string $param = 'success', string $val = '1'): never {
|
|
header('Location: ' . $back . '?' . $param . '=' . $val);
|
|
exit;
|
|
}
|