Team App Monitor

Automatische Übersicht über Apps, Bibliothek, Dateiänderungen und Pflegezustand.

34erkannte Ordner / Apps
1681Dateien im letzten Scan
36gespeicherte Änderungen
0aktive Arbeitssessions

Bearbeiter zuordnen

Wenn ihr Dateien direkt per FTP/SFTP/Netzlaufwerk bearbeitet, liefert das Dateisystem PHP normalerweise keinen zuverlässigen Benutzernamen des Bearbeiters. Startet daher vor der Arbeit hier eine Session. Alle beim nächsten Scan erkannten Änderungen in dieser App werden diesem Namen zugeordnet. Für eine vollständig revisionssichere Zuordnung empfehle ich zusätzlich Git oder Server-Audit-Logs.
Session vor dem Bearbeiten starten.

Automatisch erkannte Apps & Ordner

Angebotsblatt_03
8 Dateien · 7,8 MB
bb-event
1082 Dateien · 478,2 MB
bb-event_V02
26 Dateien · 698,7 KB
BBVisit
4 Dateien · 1,5 MB
BeBrilliantEvent26
23 Dateien · 4,1 MB
Bestellformular
2 Dateien · 11,5 KB
Bibliothek · zentrale Assets
0 Dateien · 0 B
Digitales Bestellformular
4 Dateien · 240,9 KB
Euha_26
26 Dateien · 895,6 KB
Event_GiveAways
45 Dateien · 1,9 MB
fred
0 Dateien · 0 B
HubSupport
4 Dateien · 126,7 KB
HubSupport_Claude
4 Dateien · 120,2 KB
intern
6 Dateien · 263,3 KB
KI-Kennzeichnung
3 Dateien · 83,4 KB
LebeLaut
8 Dateien · 757,9 KB
NeuEUHA
4 Dateien · 222,8 KB
philipp
9 Dateien · 1,5 MB
Poznan
0 Dateien · 0 B
Prototypdatenbank
3 Dateien · 29,7 KB
rv-rechner
1 Dateien · 33,1 KB
S8687I1987G2025N2502I345A
9 Dateien · 304,5 KB
Signia-game
3 Dateien · 55,8 KB
Singapur
238 Dateien · 174,8 MB
Singapur2
65 Dateien · 4,6 MB
std
4 Dateien · 13,0 MB
Struktur
5 Dateien · 121,6 KB
Test
4 Dateien · 30,2 KB
Tests
0 Dateien · 0 B
unity4-546484955868683053846
22 Dateien · 627,0 KB
unity4_xxx
19 Dateien · 585,7 KB
UploadLogo_Portrait
7 Dateien · 53,1 MB
Upload_Photo
22 Dateien · 8,1 MB
VideoUpload
9 Dateien · 4,7 MB

Neue Ordner auf dieser Ebene werden automatisch erkannt. Es muss nichts in der Konfiguration ergänzt werden.

Änderungshistorie

Letzter Scan: 17.09.2026 14:10:16
Neu 1 · geändert 3 · gelöscht 32
Detailänderung · 17.09.2026 12:50:16 · Nicht zugeordnet
philipp/Lebe Laut/index(2).php
Schließen
+2451 Zeile(n) −0 Zeile(n) Anzeige gekürzt
1 + <?php
2 + declare(strict_types=1);
3 +
4 + /**
5 + * index.php - Signia LebeLaut Uploadportal
6 + * Zielversion: PHP 8.5 (kompatibel mit PHP 8.2+)
7 + *
8 + * Benötigte Serverfunktionen:
9 + * - PHP-Erweiterung: fileinfo
10 + * - XLSX-Unterstützung über ZipArchive oder den PHP-Kernbestandteil PharData
11 + * - funktionsfähiger PHP-Mailversand (mail()/SMTP-Konfiguration des Servers)
12 + *
13 + * Dateien im gleichen Ordner:
14 + * - max.jpg (Headerbild)
15 + * - optional: lebelaut.png (Seitenhintergrund)
16 + * - optional: CoU-Kundennummern.xlsx (bereits teilgenommene Kundennummern)
17 + * - automatisch: kundendaten.xlsx (Formulardaten, ohne Logo/Portrait)
18 + *
19 + * Datenschutz:
20 + * - Logo und Portrait werden ausschließlich aus dem temporären PHP-Upload
21 + * als E-Mail-Anhänge versendet und nicht dauerhaft gespeichert.
22 + */
23 +
24 + date_default_timezone_set('Europe/Copenhagen');
25 +
26 + /* ----------------------------- Sicherheits-Basis ----------------------------- */
27 +
28 + $isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
29 + || (isset($_SERVER['SERVER_PORT']) && (int)$_SERVER['SERVER_PORT'] === 443)
30 + || (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower((string)$_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https')
31 + || (!empty($_SERVER['HTTP_X_FORWARDED_SSL']) && strtolower((string)$_SERVER['HTTP_X_FORWARDED_SSL']) === 'on');
32 +
33 + session_set_cookie_params([
34 + 'httponly' => true,
35 + 'secure' => $isHttps,
36 + 'samesite' => 'Lax',
37 + ]);
38 + ini_set('session.use_only_cookies', '1');
39 + ini_set('session.use_strict_mode', '1');
40 + session_name('signia_lebelaut_upload_session');
41 + session_start();
42 +
43 + @ini_set('upload_max_filesize', '60M');
44 + @ini_set('post_max_size', '130M');
45 + @ini_set('max_file_uploads', '4');
46 + @ini_set('max_execution_time', '300');
47 + @ini_set('memory_limit', '512M');
48 +
49 + $cspNonce = base64_encode(random_bytes(18));
50 +
51 + header('X-Content-Type-Options: nosniff');
52 + header('X-Frame-Options: SAMEORIGIN');
53 + header('Referrer-Policy: strict-origin-when-cross-origin');
54 + header('Permissions-Policy: camera=(), microphone=(), geolocation=()');
55 + header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
56 + header('Pragma: no-cache');
57 + header(
58 + "Content-Security-Policy: default-src 'self'; "
59 + . "img-src 'self' data: blob:; "
60 + . "style-src 'self' 'nonce-" . $cspNonce . "'; "
61 + . "script-src 'self' 'nonce-" . $cspNonce . "'; "
62 + . "connect-src 'self'; object-src 'none'; base-uri 'self'; "
63 + . "form-action 'self'; frame-ancestors 'self'"
64 + );
65 +
66 + /* -------------------------------- Konfiguration ------------------------------- */
67 +
68 + const MAX_FILE_SIZE_BYTES = 52_428_800; // 50 MB
69 + const PORTRAIT_MIN_BYTES = 1_950_000; // akzeptiert ab ca. 1,95 MB
70 + const CUSTOMER_NUMBER_PATTERN = '/^C300[A-Z0-9]{5}$/D';
71 + const CUSTOMER_NUMBER_PREFIX = 'C300';
72 + const MARKETING_EMAIL = 'marketing.de@signia-pro.com';
73 + const CUSTOMER_DATA_FILE = 'kundendaten.xlsx';
74 + const CUSTOMER_DATA_LOCK_FILE = '.kundendaten.lock';
75 +
76 + $allowedLogoExtensions = ['ai', 'eps', 'png', 'tif', 'tiff', 'pdf', 'jpg', 'jpeg'];
77 + $allowedPortraitExtensions = ['jpg', 'jpeg', 'tif', 'tiff', 'png'];
78 +
79 + $allowedLogoMimes = [
80 + 'application/pdf',
81 + 'application/postscript',
82 + 'application/illustrator',
83 + 'application/octet-stream',
84 + 'text/plain',
85 + 'image/png',
86 + 'image/jpeg',
87 + 'image/tiff',
88 + 'image/x-tiff',
89 + ];
90 +
91 + $allowedPortraitMimes = [
92 + 'image/jpeg',
93 + 'image/png',
94 + 'image/tiff',
95 + 'image/x-tiff',
96 + 'application/octet-stream',
97 + ];
98 +
99 + /* --------------------------------- Hilfsfunktionen ---------------------------- */
100 +
101 + function h(string $value): string
102 + {
103 + return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
104 + }
105 +
106 + function utf8Length(string $value): int
107 + {
108 + return function_exists('mb_strlen') ? mb_strlen($value, 'UTF-8') : strlen($value);
109 + }
110 +
111 + function utf8Substr(string $value, int $start, int $length): string
112 + {
113 + return function_exists('mb_substr')
114 + ? mb_substr($value, $start, $length, 'UTF-8')
115 + : substr($value, $start, $length);
116 + }
117 +
118 + function csrfToken(): string
119 + {
120 + if (empty($_SESSION['csrf_token']) || !is_string($_SESSION['csrf_token'])) {
121 + $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
122 + }
123 +
124 + return $_SESSION['csrf_token'];
125 + }
126 +
127 + function verifyCsrfToken(?string $token): bool
128 + {
129 + return is_string($token)
130 + && isset($_SESSION['csrf_token'])
131 + && is_string($_SESSION['csrf_token'])
132 + && hash_equals($_SESSION['csrf_token'], $token);
133 + }
134 +
135 + function sanitizeText(string $value, int $maxLength = 180): string
136 + {
137 + $value = trim($value);
138 + $value = preg_replace('/[\x00-\x1F\x7F]/u', '', $value) ?? '';
139 + $value = preg_replace('/\s+/u', ' ', $value) ?? '';
140 +
141 + return utf8Length($value) > $maxLength
142 + ? utf8Substr($value, 0, $maxLength)
143 + : $value;
144 + }
145 +
146 + function sanitizeTextarea(string $value, int $maxLength = 1000): string
147 + {
148 + $value = trim($value);
149 + $value = str_replace(["\r\n", "\r"], "\n", $value);
150 + $value = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $value) ?? '';
151 + $value = preg_replace('/[ \t]+/u', ' ', $value) ?? '';
152 + $value = preg_replace('/\n{3,}/u', "\n\n", $value) ?? '';
153 +
154 + return utf8Length($value) > $maxLength
155 + ? utf8Substr($value, 0, $maxLength)
156 + : $value;
157 + }
158 +
159 + function normalizeCustomerNumber(string $value): string
160 + {
161 + $value = strtoupper(trim($value));
162 + $value = preg_replace('/\s+/', '', $value) ?? '';
163 +
164 + return $value;
165 + }
166 +
167 + function isValidCustomerNumber(string $value): bool
168 + {
169 + return preg_match(CUSTOMER_NUMBER_PATTERN, $value) === 1;
170 + }
171 +
172 + function detectMimeType(string $tmpPath): string
173 + {
174 + if (!is_file($tmpPath) || !class_exists('finfo')) {
175 + return 'application/octet-stream';
176 + }
177 +
178 + $finfo = new finfo(FILEINFO_MIME_TYPE);
179 + $mime = $finfo->file($tmpPath);
180 +
181 + return is_string($mime) && $mime !== '' ? $mime : 'application/octet-stream';
182 + }
183 +
184 + function fileExtension(string $fileName): string
185 + {
186 + $extension = strtolower((string)pathinfo($fileName, PATHINFO_EXTENSION));
187 +
188 + return preg_replace('/[^a-z0-9]/', '', $extension) ?? '';
189 + }
190 +
191 + function anonymizeIp(string $ip): string
192 + {
193 + $ip = trim($ip);
194 +
195 + if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
196 + return preg_replace('/\.\d+$/', '.0', $ip) ?? '';
197 + }
198 +
199 + if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
200 + $packed = @inet_pton($ip);
201 + if ($packed !== false) {
202 + $masked = substr($packed, 0, 8) . str_repeat("\0", 8);
203 + $readable = @inet_ntop($masked);
204 +
205 + return is_string($readable) ? $readable : 'ipv6';
206 + }
207 +
208 + return 'ipv6';
209 + }
210 +
211 + return '';
212 + }
213 +
214 + function uploadErrorMessage(int $errorCode): string
215 + {
216 + return match ($errorCode) {
217 + UPLOAD_ERR_INI_SIZE,
218 + UPLOAD_ERR_FORM_SIZE => 'Die Datei ist größer als erlaubt. Bitte laden Sie eine Datei mit maximal 50 MB hoch.',
219 + UPLOAD_ERR_PARTIAL => 'Die Datei wurde nur teilweise hochgeladen. Bitte versuchen Sie es erneut.',
220 + UPLOAD_ERR_NO_FILE => 'Bitte wählen Sie eine Datei aus oder ziehen Sie diese in das Upload-Feld.',
221 + UPLOAD_ERR_NO_TMP_DIR => 'Serverfehler: Der temporäre Upload-Ordner fehlt.',
222 + UPLOAD_ERR_CANT_WRITE => 'Serverfehler: Die Datei konnte temporär nicht verarbeitet werden.',
223 + UPLOAD_ERR_EXTENSION => 'Der Upload wurde durch eine Server-Erweiterung gestoppt.',
224 + default => 'Die Datei konnte nicht hochgeladen werden. Bitte versuchen Sie es erneut.',
225 + };
226 + }
227 +
228 + function validateUploadedFile(
229 + array $file,
230 + string $label,
231 + array $allowedExtensions,
232 + array $allowedMimes,
233 + bool $requiresPortraitMinimum = false
234 + ): array {
235 + if (!isset($file['error'], $file['tmp_name'], $file['name'], $file['size'])) {
236 + return ['ok' => false, 'message' => $label . ': Die Upload-Daten sind unvollständig.'];
237 + }
238 +
239 + if ((int)$file['error'] !== UPLOAD_ERR_OK) {
240 + return ['ok' => false, 'message' => $label . ': ' . uploadErrorMessage((int)$file['error'])];
241 + }
242 +
243 + $tmpPath = (string)$file['tmp_name'];
244 + $size = (int)$file['size'];
245 + $originalName = sanitizeText((string)$file['name'], 220);
246 + $extension = fileExtension($originalName);
247 + $mime = detectMimeType($tmpPath);
248 +
249 + if (!is_uploaded_file($tmpPath)) {
250 + return ['ok' => false, 'message' => $label . ': Die Datei wurde nicht als gültiger Upload erkannt.'];
251 + }
252 +
253 + if ($size <= 0) {
254 + return ['ok' => false, 'message' => $label . ': Die Datei scheint leer zu sein.'];
255 + }
256 +
257 + if ($size > MAX_FILE_SIZE_BYTES) {
258 + return ['ok' => false, 'message' => $label . ': Die Datei ist größer als 50 MB.'];
259 + }
260 +
261 + if (!in_array($extension, $allowedExtensions, true)) {
262 + return ['ok' => false, 'message' => $label . ': Dieses Dateiformat ist nicht vorgesehen.'];
263 + }
264 +
265 + if (!in_array($mime, $allowedMimes, true)) {
266 + return ['ok' => false, 'message' => $label . ': Das Dateiformat konnte nicht sicher erkannt werden. Bitte laden Sie eine andere Originaldatei hoch.'];
267 + }
268 +
269 + $imageWidth = null;
270 + $imageHeight = null;
271 + $imageMime = '';
272 +
273 + if (in_array($extension, ['jpg', 'jpeg', 'png'], true)) {
274 + $imageInfo = @getimagesize($tmpPath);
275 +
276 + if ($imageInfo === false || empty($imageInfo[0]) || empty($imageInfo[1])) {
277 + return ['ok' => false, 'message' => $label . ': Die Datei konnte nicht als lesbares JPG- oder PNG-Bild geprüft werden.'];
278 + }
279 +
280 + $imageWidth = (int)$imageInfo[0];
281 + $imageHeight = (int)$imageInfo[1];
282 + $imageMime = isset($imageInfo['mime']) && is_string($imageInfo['mime']) ? $imageInfo['mime'] : '';
283 + $expectedImageMimes = $extension === 'png' ? ['image/png'] : ['image/jpeg'];
284 +
285 + if (!in_array($imageMime, $expectedImageMimes, true)) {
286 + return ['ok' => false, 'message' => $label . ': Dateiendung und geprüftes Bildformat stimmen nicht überein.'];
287 + }
288 + }
289 +
290 + if ($requiresPortraitMinimum && $size < PORTRAIT_MIN_BYTES) {
291 + return [
292 + 'ok' => false,
293 + 'message' => 'Das Portrait ist zu klein oder zu stark komprimiert. Bitte laden Sie ein möglichst unbearbeitetes Originalbild ab ca. 2 MB hoch.',
294 + ];
295 + }
296 +
297 + return [
298 + 'ok' => true,
299 + 'message' => '',
300 + 'extension' => $extension,
301 + 'mime' => $mime,
302 + 'size' => $size,
303 + 'image_width' => $imageWidth,
304 + 'image_height' => $imageHeight,
305 + 'image_mime' => $imageMime,
306 + 'original_name' => $originalName,
307 + 'tmp_path' => $tmpPath,
308 + ];
309 + }
310 +
311 + function cleanupTemporaryUploads(array ...$validatedFiles): void
312 + {
313 + foreach ($validatedFiles as $validatedFile) {
314 + $path = isset($validatedFile['tmp_path']) ? (string)$validatedFile['tmp_path'] : '';
315 + if ($path !== '' && is_file($path) && is_uploaded_file($path)) {
316 + @unlink($path);
317 + }
318 + }
319 + }
320 +
321 + function formatMegabytes(int $bytes): string
322 + {
323 + return number_format($bytes / 1_048_576, 2, ',', '.');
324 + }
325 +
326 + /* ------------------------- XLSX lesen: CoU-Kundennummern ---------------------- */
327 +
328 + function findCouCustomerFile(): ?string
329 + {
330 + $files = glob(__DIR__ . DIRECTORY_SEPARATOR . 'CoU-Kundennummern.*') ?: [];
331 +
332 + foreach ($files as $file) {
333 + $base = pathinfo($file, PATHINFO_FILENAME);
334 + $extension = strtolower((string)pathinfo($file, PATHINFO_EXTENSION));
335 + if (strcasecmp($base, 'CoU-Kundennummern') === 0 && in_array($extension, ['xlsx', 'csv', 'xls'], true)) {
336 + return $file;
337 + }
338 + }
339 +
340 + return null;
341 + }
342 +
343 + function decodeXmlValue(string $value): string
344 + {
345 + return html_entity_decode(strip_tags($value), ENT_QUOTES | ENT_XML1, 'UTF-8');
346 + }
347 +
348 + function extractXmlAttribute(string $attributes, string $name): string
349 + {
350 + $quotedName = preg_quote($name, '/');
351 + customerNumber.addEventListener('input', scheduleCustomerCheck);
352 + customerNumber.addEventListener('blur', checkExistingCustomer);
353 +
354 + function readImageDimensions(file) {
355 + return new Promise(function (resolve, reject) {
356 + const objectUrl = URL.createObjectURL(file);
357 + const img = new Image();
358 +
359 + img.onload = function () {
360 + const dimensions = { width: img.naturalWidth || img.width, height: img.naturalHeight || img.height };
361 + URL.revokeObjectURL(objectUrl);
362 + resolve(dimensions);
363 + };
364 +
365 + img.onerror = function () {
366 + URL.revokeObjectURL(objectUrl);
367 + reject(new Error('Bildmaße konnten nicht gelesen werden.'));
368 + };
369 +
370 + img.src = objectUrl;
371 + });
372 + }
373 +
374 + function setQuality(qualityElement, result) {
375 + qualityElement.className = 'quality-meter';
376 + qualityElement.textContent = '';
377 + if (!result) return;
378 +
379 + qualityElement.classList.add('is-visible', 'is-' + result.level);
380 +
381 + const head = document.createElement('div');
382 + head.className = 'quality-head';
383 +
384 + const title = document.createElement('div');
385 + title.className = 'quality-title';
386 +
387 + const dot = document.createElement('span');
388 + dot.className = 'quality-dot';
389 + dot.setAttribute('aria-hidden', 'true');
390 +
391 + const titleText = document.createElement('span');
392 + titleText.textContent = 'Datei-Check';
393 +
394 + const badge = document.createElement('span');
395 + badge.className = 'quality-badge';
396 + badge.textContent = result.label;
397 +
398 + title.appendChild(dot);
399 + title.appendChild(titleText);
400 + head.appendChild(title);
401 + head.appendChild(badge);
402 +
403 + const message = document.createElement('p');
404 + message.className = 'quality-message';
405 + message.textContent = result.message;
406 +
407 + qualityElement.appendChild(head);
408 + qualityElement.appendChild(message);
409 + }
410 +
411 + function unsupportedQuality(type) {
412 + return {
413 + level: 'red',
414 + label: 'Nicht geeignet',
415 + message: type === 'logo'
416 + ? 'Dieses Logoformat wird nicht akzeptiert. Bitte AI, EPS, PDF, PNG, TIFF oder JPG/JPEG verwenden.'
417 + : 'Dieses Portraitformat wird nicht akzeptiert. Bitte JPG/JPEG, PNG oder TIFF verwenden.'
418 + };
419 + }
420 +
421 + function evaluateLogoQuality(file, dimensions) {
422 + const ext = extensionOf(file);
423 +
424 + if (!ALLOWED_LOGO_EXTENSIONS.includes(ext)) return unsupportedQuality('logo');
425 + if (file.size > MAX_FILE_SIZE) {
426 + return { level: 'red', label: 'Nicht geeignet', message: 'Die Datei ist größer als 50 MB und wird nicht akzeptiert.' };
427 + }
428 +
429 + if (['ai', 'eps'].includes(ext)) {
430 + if (file.size >= 100 * 1024) return { level: 'green', label: 'Sehr gut', message: 'Echte Vektordatei mit plausibler Dateigröße. Ideal für eine hochwertige Weiterverarbeitung.' };
431 + if (file.size >= 25 * 1024) return { level: 'yellow', label: 'Bitte prüfen', message: 'Vektordatei vorhanden, aber ungewöhnlich klein. Wenn möglich, bitte die originale Druck- oder Agenturdatei hochladen.' };
432 + return { level: 'red', label: 'Nicht geeignet', message: 'Die Vektordatei ist ungewöhnlich klein. Bitte prüfen, ob die vollständige Logo-Datei vorliegt.' };
433 + }
434 +
435 + if (ext === 'pdf') {
436 + if (file.size >= 100 * 1024) return { level: 'green', label: 'Sehr gut', message: 'PDF mit plausibler Dateigröße. Besonders geeignet, wenn das Logo als Vektorgrafik enthalten ist.' };
437 + if (file.size >= 25 * 1024) return { level: 'yellow', label: 'Bitte prüfen', message: 'Das PDF ist eher klein. Wenn möglich, bitte eine größere PDF- oder echte Vektordatei verwenden.' };
438 + return { level: 'red', label: 'Nicht geeignet', message: 'Das PDF ist ungewöhnlich klein. Bitte die vollständige Logodatei prüfen.' };
439 + }
440 +
441 + if (['tif', 'tiff'].includes(ext)) {
442 + if (file.size >= 1024 * 1024) return { level: 'green', label: 'Sehr gut', message: 'TIFF ab 1 MB. Für die Weiterverarbeitung sehr gut geeignet.' };
443 + if (file.size >= 350 * 1024) return { level: 'yellow', label: 'Bitte prüfen', message: 'Das TIFF ist verwendbar, aber eher klein. Wenn möglich, bitte eine höher aufgelöste Datei verwenden.' };
444 + return { level: 'red', label: 'Nicht geeignet', message: 'Das TIFF ist sehr klein. Bitte eine größere Datei oder eine Vektordatei verwenden.' };
445 + }
446 +
447 + if (['jpg', 'jpeg', 'png'].includes(ext) && dimensions) {
448 + const minSide = Math.min(dimensions.width, dimensions.height);
449 + const maxSide = Math.max(dimensions.width, dimensions.height);
450 + const detail = formatPixels(dimensions.width, dimensions.height) + ' und ' + formatBytes(file.size);
451 +
452 + if (maxSide >= 1600 && minSide >= 400 && file.size >= 300 * 1024) {
453 + return { level: 'green', label: 'Sehr gut', message: 'Für eine Pixeldatei sehr gut geeignet: ' + detail + '.' };
454 + }
455 + if (maxSide >= 900 && minSide >= 250 && file.size >= 120 * 1024) {
456 + return { level: 'yellow', label: 'Bitte prüfen', message: 'Verwendbar, aber knapp: ' + detail + '. Eine größere Datei oder Vektordatei wäre besser.' };
457 + }
458 + return { level: 'red', label: 'Nicht geeignet', message: 'Die Pixeldatei ist zu klein: ' + detail + '. Bitte eine größere Datei verwenden.' };
459 + }
460 +
461 + return { level: 'yellow', label: 'Bitte prüfen', message: 'Die Dateigröße beträgt ' + formatBytes(file.size) + '. Die Pixelmaße können für dieses Format im Browser nicht sicher geprüft werden.' };
462 + }
463 +
464 + function evaluatePortraitQuality(file, dimensions) {
465 + const ext = extensionOf(file);
466 +
467 + if (!ALLOWED_PORTRAIT_EXTENSIONS.includes(ext)) return unsupportedQuality('portrait');
468 + if (file.size > MAX_FILE_SIZE) return { level: 'red', label: 'Nicht geeignet', message: 'Die Datei ist größer als 50 MB und wird nicht akzeptiert.' };
469 + if (file.size < PORTRAIT_MIN_SIZE) return { level: 'red', label: 'Nicht geeignet', message: 'Das Portrait liegt unter ca. 1,95 MB. Bitte ein möglichst unbearbeitetes Originalbild ab ca. 2 MB hochladen.' };
470 +
471 + if (['jpg', 'jpeg', 'png'].includes(ext) && dimensions) {
472 + const minSide = Math.min(dimensions.width, dimensions.height);
473 + const detail = formatPixels(dimensions.width, dimensions.height) + ' und ' + formatBytes(file.size);
474 +
475 + if (dimensions.width >= 1600 && dimensions.height >= 1600) {
476 + return { level: 'green', label: 'Sehr gut', message: 'Für eine hochwertige Darstellung sehr gut geeignet: ' + detail + '.' };
477 + }
478 + if (minSide >= 1200) {
479 + return { level: 'yellow', label: 'Bitte prüfen', message: 'Verwendbar, aber bei größeren Anwendungen knapp: ' + detail + '.' };
480 + }
481 + return { level: 'red', label: 'Nicht geeignet', message: 'Trotz ausreichender Dateigröße sind die Bildmaße zu klein: ' + detail + '. Bitte ein größeres Originalbild verwenden.' };
482 + }
483 +
484 + if (['tif', 'tiff'].includes(ext)) {
485 + if (file.size >= 5 * 1024 * 1024) return { level: 'green', label: 'Sehr gut', message: 'TIFF ab 5 MB. Für die professionelle Bearbeitung sehr gut geeignet.' };
486 + return { level: 'yellow', label: 'Bitte prüfen', message: 'Das TIFF erfüllt die Mindestgröße, liegt aber unter 5 MB. Wenn möglich, bitte das größte verfügbare Original hochladen.' };
487 + }
488 +
489 + return { level: 'yellow', label: 'Bitte prüfen', message: 'Die Dateigröße beträgt ' + formatBytes(file.size) + '. Die Pixelmaße können für dieses Format nicht sicher geprüft werden.' };
490 + }
491 +
492 + function updateQuality(config, file) {
493 + const ext = extensionOf(file);
494 + const token = Date.now() + ':' + file.name + ':' + file.size;
495 + config.qualityToken = token;
496 +
497 + if (!['jpg', 'jpeg', 'png'].includes(ext)) {
498 + setQuality(config.quality, config.type === 'logo' ? evaluateLogoQuality(file, null) : evaluatePortraitQuality(file, null));
499 + return;
500 + }
501 +
502 + setQuality(config.quality, { level: 'yellow', label: 'Prüfung läuft', message: 'Die Bildmaße werden gerade geprüft.' });
503 + readImageDimensions(file).then(function (dimensions) {
504 + if (config.qualityToken !== token) return;
505 + setQuality(config.quality, config.type === 'logo' ? evaluateLogoQuality(file, dimensions) : evaluatePortraitQuality(file, dimensions));
506 + }).catch(function () {
507 + if (config.qualityToken !== token) return;
508 + setQuality(config.quality, { level: 'yellow', label: 'Bitte prüfen', message: 'Die Bildmaße konnten im Browser nicht sicher gelesen werden. Bitte möglichst eine hochauflösende Originaldatei verwenden.' });
509 + });
510 + }
511 +
512 + function validateFile(file, type) {
513 + const ext = extensionOf(file);
514 + const allowed = type === 'logo' ? ALLOWED_LOGO_EXTENSIONS : ALLOWED_PORTRAIT_EXTENSIONS;
515 + if (!allowed.includes(ext)) {
516 + return type === 'logo'
517 + ? 'Bitte laden Sie ein Logo im Format AI, EPS, PNG, TIFF, PDF oder JPG/JPEG hoch.'
518 + : 'Bitte laden Sie ein Portrait im Format JPG/JPEG, PNG oder TIFF hoch.';
519 + }
520 + if (file.size > MAX_FILE_SIZE) return 'Diese Datei ist größer als 50 MB.';
521 + if (type === 'portrait' && file.size < PORTRAIT_MIN_SIZE) return 'Das Portrait muss mindestens ca. 1,95 MB groß sein.';
522 + return '';
523 + }
524 +
525 + function clearPreview(preview) {
526 + preview.innerHTML = '';
527 + preview.classList.remove('is-visible');
528 + }
529 +
530 + function setTipsVisibility(tips, tools, hasFile) {
531 + tips.classList.toggle('is-hidden', hasFile);
532 + tools.classList.toggle('is-visible', hasFile);
533 + }
534 +
535 + function renderPreview(config) {
536 + clearPreview(config.preview);
537 + showInlineError(config.error, '');
538 +
539 + const file = config.input.files && config.input.files[0] ? config.input.files[0] : null;
540 + if (!file) {
541 + config.status.textContent = 'Noch keine Datei ausgewählt.';
542 + config.qualityToken = '';
543 + setQuality(config.quality, null);
544 + setTipsVisibility(config.tips, config.tools, false);
545 + updateSubmitState();
546 + return;
547 + }
548 +
549 + config.status.textContent = file.name + ' (' + formatBytes(file.size) + ')';
550 + setTipsVisibility(config.tips, config.tools, true);
551 +
552 + const validationMessage = validateFile(file, config.type);
553 + showInlineError(config.error, validationMessage);
554 + updateQuality(config, file);
555 +
556 + const ext = extensionOf(file);
557 + const canPreview = file.type.startsWith('image/') && ['jpg', 'jpeg', 'png'].includes(ext);
558 + config.preview.classList.add('is-visible');
559 +
560 + if (canPreview) {
561 + const img = document.createElement('img');
562 + img.alt = 'Vorschau ' + (config.type === 'logo' ? 'Logo' : 'Portrait');
563 + img.src = URL.createObjectURL(file);
564 + img.onload = function () { URL.revokeObjectURL(img.src); };
565 + config.preview.appendChild(img);
566 + } else {
567 + const placeholder = document.createElement('div');
568 + placeholder.className = 'preview-placeholder';
569 + placeholder.innerHTML = '<strong>Datei ausgewählt.</strong><br>Für dieses Format ist im Browser keine sichere Vorschau verfügbar.';
570 + config.preview.appendChild(placeholder);
571 + }
572 +
573 + updateSubmitState();
574 + }
575 +
576 + function removeFile(config) {
577 + config.input.value = '';
578 + renderPreview(config);
579 + config.input.focus();
580 + }
581 +
582 + function updateSubmitState() {
583 + if (!submitButton) return;
584 + submitButton.disabled = isSubmitting || couMatched;
585 + if (isSubmitting) submitButton.setAttribute('aria-busy', 'true');
586 + else submitButton.removeAttribute('aria-busy');
587 + }
588 +
589 + function validateFileInputsForSubmit() {
590 + let firstProblem = null;
591 + [logoConfig, portraitConfig].forEach(function (config) {
592 + const file = config.input.files && config.input.files[0] ? config.input.files[0] : null;
593 + const message = file ? validateFile(file, config.type) : '';
594 + showInlineError(config.error, message);
595 + if (message && !firstProblem) firstProblem = config.error;
596 + });
597 + return firstProblem;
598 + }
599 +
600 + function setSubmitInProgress() {
601 + isSubmitting = true;
602 + submitButton.textContent = 'Übermittlung läuft...';
603 + updateSubmitState();
604 + }
605 +
606 + function resetSubmitButton() {
607 + isSubmitting = false;
608 + submitButton.textContent = originalSubmitText;
609 + updateSubmitState();
610 + }
611 +
612 + function setDroppedFiles(input, files) {
613 + if (!files || !files.length) return;
614 + try {
615 + const transfer = new DataTransfer();
616 + transfer.items.add(files[0]);
617 + input.files = transfer.files;
618 + } catch (error) {
619 + input.files = files;
620 + }
621 + input.dispatchEvent(new Event('change', { bubbles: true }));
622 + }
623 +
624 + function initDragAndDrop(box) {
625 + const input = box.querySelector('input[type="file"]');
626 + if (!input) return;
627 +
628 + ['dragenter', 'dragover'].forEach(function (eventName) {
629 + box.addEventListener(eventName, function (event) {
630 + event.preventDefault();
631 + box.classList.add('is-dragover');
632 + });
633 + });
634 +
635 + ['dragleave', 'drop'].forEach(function (eventName) {
636 + box.addEventListener(eventName, function (event) {
637 + event.preventDefault();
638 + box.classList.remove('is-dragover');
639 + });
640 + });
641 +
642 + box.addEventListener('drop', function (event) {
643 + const files = event.dataTransfer && event.dataTransfer.files ? event.dataTransfer.files : null;
644 + setDroppedFiles(input, files);
645 + });
646 + }
647 +
648 + const logoConfig = { input: logoInput, status: logoStatus, preview: logoPreview, error: logoError, quality: logoQuality, tips: logoTips, tools: logoTools, type: 'logo' };
649 + const portraitConfig = { input: portraitInput, status: portraitStatus, preview: portraitPreview, error: portraitError, quality: portraitQuality, tips: portraitTips, tools: portraitTools, type: 'portrait' };
650 +
651 + logoInput.addEventListener('change', function () { renderPreview(logoConfig); });
652 + portraitInput.addEventListener('change', function () { renderPreview(portraitConfig); });
653 + logoRemove.addEventListener('click', function () { removeFile(logoConfig); });
654 + portraitRemove.addEventListener('click', function () { removeFile(portraitConfig); });
655 +
656 + [companyName, contactEmail].forEach(function (input) {
657 + input.addEventListener('input', updateSubmitState);
658 + input.addEventListener('blur', updateSubmitState);
659 + });
660 + consentUsage.addEventListener('change', updateSubmitState);
661 + document.querySelectorAll('[data-upload-box]').forEach(initDragAndDrop);
662 +
663 + form.addEventListener('submit', function (event) {
664 + if (isSubmitting || couMatched) {
665 + event.preventDefault();
666 + return;
667 + }
668 +
669 + normalizeCustomerField();
670 + const customerIsValid = CUSTOMER_PATTERN.test(customerNumber.value.trim().toUpperCase());
671 + showInlineError(customerNumberError, customerIsValid ? '' : 'Die Kundennummer muss mit C300 beginnen und insgesamt genau 9 Stellen enthalten.');
672 +
673 + if (!customerIsValid || !form.checkValidity()) {
674 + event.preventDefault();
675 + form.reportValidity();
676 + return;
677 + }
678 +
679 + const firstFileProblem = validateFileInputsForSubmit();
680 + if (firstFileProblem) {
681 + event.preventDefault();
682 + firstFileProblem.scrollIntoView({ behavior: 'smooth', block: 'center' });
683 + return;
684 + }
685 +
686 + setSubmitInProgress();
687 + });
688 +
689 + window.addEventListener('pageshow', function () {
690 + if (isSubmitting) resetSubmitButton();
691 + });
692 +
693 + normalizeCustomerField();
694 + updateSubmitState();
695 + if (CUSTOMER_PATTERN.test(customerNumber.value.trim().toUpperCase())) checkExistingCustomer();
696 + })();
697 + </script>
698 + </body>
699 + </html>
700 +

Links steht die alte, rechts die neue Zeilennummer. Rot wurde entfernt, grün wurde hinzugefügt.

ZeitTypAppDateiBearbeiterDetails
17.09.2026 12:50:16 Gelöscht VideoUpload VideoUpload/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
26,5 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht UploadLogo_Portrait UploadLogo_Portrait/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
46,7 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht unity4_xxx unity4_xxx/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
36,6 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht unity4-546484955868683053846 unity4-546484955868683053846/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
37,0 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht Test Test/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
24,4 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht Struktur Struktur/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
37,9 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht std std/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
2,2 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht Singapur2 Singapur2/Internclaude/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
23,5 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht Singapur2 Singapur2/Intern/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
4,6 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht Singapur2 Singapur2/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
20,2 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht S8687I1987G2025N2502I345A S8687I1987G2025N2502I345A/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
112,7 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht rv-rechner rv-rechner/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
13,1 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht philipp philipp/Pakete/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
69,3 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht NeuEUHA NeuEUHA/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
57,6 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht LebeLaut LebeLaut/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
135,7 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht fred fred/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
26,5 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht Event_GiveAways Event_GiveAways/Vorgänger bis 03.09./index.php
Nicht zugeordnet
Keine Bearbeiterinfo
105,2 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht Event_GiveAways Event_GiveAways/Neuer Aufschlag/signia_materialsystem_alt/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
98,0 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht Event_GiveAways Event_GiveAways/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
116,7 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht Euha_26 Euha_26/Meetings/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
69,4 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht Euha_26 Euha_26/LEBE_LAUT_Game_Prototypes/catch-the-beat/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
12,8 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht Euha_26 Euha_26/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
40,0 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht Euha_26 Euha_26/catch-the-beat/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
12,8 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht EUHA-Bestellformular EUHA-Bestellformular/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
59,3 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht EUHA-Bestellformular EUHA-Bestellformular/data/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
7,8 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht Digitales Bestellformular Digitales Bestellformular/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
83,5 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht Bestellformular Bestellformular/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
45,1 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht BeBrilliantEvent26 BeBrilliantEvent26/Kampagne/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
3,3 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht bb-event bb-event/public/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
1,1 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht bb-event bb-event/Archiv Vorgängerversion/public/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
991 B
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht Angebotsblatt_03 Angebotsblatt_03/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
31,0 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Gelöscht Angebotsblatt_03 Angebotsblatt_03/data/data/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
30,5 KB
Keine gespeicherte Text-Vorversion vorhanden.
17.09.2026 12:50:16 Neu philipp philipp/Lebe Laut/index(2).php
Nicht zugeordnet
Keine Bearbeiterinfo
99,5 KB Änderungen anzeigen
+2451 / −0 Zeilen
17.09.2026 12:50:16 Geändert LebeLaut LebeLaut/kundendaten.xlsx
Nicht zugeordnet
Keine Bearbeiterinfo
22,8 KB → 23,1 KB
Kein unterstütztes Text-/Codeformat
17.09.2026 12:50:16 Geändert LebeLaut LebeLaut/kundendaten.csv
Nicht zugeordnet
Keine Bearbeiterinfo
7,9 KB → 8,2 KB Änderungen anzeigen
+53 / −0 Zeilen
17.09.2026 11:55:56 Geändert fred fred/index.php
Nicht zugeordnet
Keine Bearbeiterinfo
26,5 KB → 26,5 KB
Kein Text-Diff für ältere Historie

Wartung & Bug-Check

Prüft u. a. PHP-Syntax (wenn auf dem Server erlaubt), sehr große Dateien und global beschreibbare Dateien. Das ersetzt keinen echten Anwendungstest, findet aber typische technische Probleme früh.

Noch kein Wartungscheck durchgeführt. Klicke oben auf „Apps auf Bugs prüfen“.

Empfohlener Team-Ablauf

  1. Vor Änderungen Namen + App auswählen und eine Arbeitssession starten.
  2. Dateien wie gewohnt bearbeiten / hochladen.
  3. Danach „Jetzt Änderungen prüfen“ ausführen. In der Historie bei Bedarf auf „Änderungen anzeigen“ klicken, um den Zeilen-Diff zu sehen, und anschließend die Session beenden.
  4. Vor wichtigen Releases zusätzlich „Apps auf Bugs prüfen“ ausführen.
  5. Gemeinsame Bilder nur noch unter Bibliothek ablegen und in den Apps über relative Pfade referenzieren.
Für eure Bildbibliothek: Wenn z. B. index.php, Bibliothek und MeineApp nebeneinander liegen, kann MeineApp ein Bild beispielsweise mit ../Bibliothek/logo.png referenzieren. Noch sauberer ist eine feste Web-URL wie /Bibliothek/logo.png, sofern eure Serverstruktur das erlaubt.