diff --git a/client/custom/css/checkbox-toggle-slider.css b/client/custom/css/checkbox-toggle-slider.css new file mode 100644 index 00000000..732ab3eb --- /dev/null +++ b/client/custom/css/checkbox-toggle-slider.css @@ -0,0 +1,306 @@ +/** + * Custom CSS: Checkbox Toggle Slider + * Verwandelt alle Standard-Checkboxen in moderne On-Off-Slider + * Erstellt: Januar 2026 + * Updated: Angepasst für EspoCRM Struktur (Label vor Input) + */ + +/* ===== HAUPT-CHECKBOX STYLING ===== */ + +/* Standard-Checkbox verstecken und durch Slider ersetzen */ +input[type="checkbox"].main-element, +input[type="checkbox"].form-checkbox, +.field[data-name] input[type="checkbox"] { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + width: 44px; + height: 24px; + background-color: #ccc; + border-radius: 12px; + position: relative; + cursor: pointer; + transition: background-color 0.3s ease; + outline: none; + border: none; + vertical-align: middle; + margin: 0; + flex-shrink: 0; +} + +/* Slider-Button (der runde Schalter) */ +input[type="checkbox"].main-element::before, +input[type="checkbox"].form-checkbox::before, +.field[data-name] input[type="checkbox"]::before { + content: ''; + position: absolute; + width: 18px; + height: 18px; + background-color: white; + border-radius: 50%; + top: 3px; + left: 3px; + transition: transform 0.3s ease, left 0.3s ease; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); +} + +/* Checked State - Slider bewegt sich nach rechts und wird grün */ +input[type="checkbox"].main-element:checked, +input[type="checkbox"].form-checkbox:checked, +.field[data-name] input[type="checkbox"]:checked { + background-color: #4CAF50; +} + +input[type="checkbox"].main-element:checked::before, +input[type="checkbox"].form-checkbox:checked::before, +.field[data-name] input[type="checkbox"]:checked::before { + left: 23px; +} + +/* Hover-Effekt */ +input[type="checkbox"].main-element:hover, +input[type="checkbox"].form-checkbox:hover, +.field[data-name] input[type="checkbox"]:hover { + opacity: 0.9; + box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.2); +} + +/* Focus State für Accessibility */ +input[type="checkbox"].main-element:focus, +input[type="checkbox"].form-checkbox:focus, +.field[data-name] input[type="checkbox"]:focus { + outline: 2px solid #4CAF50; + outline-offset: 2px; +} + +/* Disabled State */ +input[type="checkbox"].main-element:disabled, +input[type="checkbox"].form-checkbox:disabled, +.field[data-name] input[type="checkbox"]:disabled { + background-color: #e0e0e0; + cursor: not-allowed; + opacity: 0.6; +} + +input[type="checkbox"].main-element:disabled::before, +input[type="checkbox"].form-checkbox:disabled::before, +.field[data-name] input[type="checkbox"]:disabled::before { + background-color: #f5f5f5; +} + +/* ===== FIELD CONTAINER STYLING ===== */ + +/* Field Container anpassen für besseres Layout */ +.field[data-name] { + display: flex; + align-items: center; + min-height: 24px; +} + +/* Boolean Field spezifisch */ +.field.field-boolean { + display: flex; + align-items: center; +} + +/* ===== DETAIL VIEW (Readonly) ===== */ + +/* Detail-Mode mit custom Attribut */ +.field.field-boolean[data-value="true"] input[type="checkbox"]:disabled, +.field.field-boolean.detail-mode input[type="checkbox"] { + background-color: #4CAF50; +} + +.field.field-boolean[data-value="true"] input[type="checkbox"]:disabled::before, +.field.field-boolean.detail-mode input[type="checkbox"]::before { + left: 23px; +} + +.field.field-boolean[data-value="false"] input[type="checkbox"]:disabled, +.field.field-boolean.detail-mode input[type="checkbox"]:not(:checked) { + background-color: #ccc; +} + +/* Text "Yes"/"No" in Detail-View ausblenden falls vorhanden */ +.field.field-boolean.detail-mode span.text-muted { + display: none; +} + +/* ===== LIST VIEW & TABLE CELLS ===== */ + +/* List View Checkboxen in Tabellen */ +td.cell.cell-boolean input[type="checkbox"] { + width: 44px; + height: 24px; +} + +/* ===== MODAL & INLINE EDIT ===== */ + +/* Modal-Body Checkboxen */ +.modal-body .field[data-name] input[type="checkbox"] { + width: 44px; + height: 24px; +} + +/* Inline Edit Mode */ +.inline-edit-mode input[type="checkbox"] { + width: 44px; + height: 24px; +} + +/* ===== FILTER & SEARCH ===== */ + +/* Filter-Panel Checkboxen */ +.search-row input[type="checkbox"] { + width: 44px; + height: 24px; +} + +/* Advanced Filters */ +.filter-container input[type="checkbox"] { + width: 44px; + height: 24px; +} + +/* ===== PREFERENCES & SETTINGS ===== */ + +/* Preferences/Settings Panels */ +.panel-body input[type="checkbox"].form-checkbox { + width: 44px; + height: 24px; + margin-right: 8px; +} + +/* ===== MASS ACTION CHECKBOXEN AUSNAHME ===== */ + +/* Mass Action Checkboxen - diese NICHT als Slider darstellen */ +input[type="checkbox"].record-checkbox, +input[type="checkbox"].select-all, +td.cell-checkbox input[type="checkbox"], +th.cell-checkbox input[type="checkbox"] { + appearance: checkbox !important; + -webkit-appearance: checkbox !important; + -moz-appearance: checkbox !important; + width: auto !important; + height: auto !important; + background-color: transparent !important; + border-radius: 0 !important; +} + +input[type="checkbox"].record-checkbox::before, +input[type="checkbox"].select-all::before, +td.cell-checkbox input[type="checkbox"]::before, +th.cell-checkbox input[type="checkbox"]::before { + display: none !important; + content: none !important; +} + +/* ===== ALTERNATIVE FARBEN (Optional) ===== */ + +/* Warning State */ +input[type="checkbox"][data-type="warning"]:checked { + background-color: #ff9800; +} + +/* Danger/Error State */ +input[type="checkbox"][data-type="danger"]:checked { + background-color: #f44336; +} + +/* Info State */ +input[type="checkbox"][data-type="info"]:checked { + background-color: #2196F3; +} + +/* ===== ANIMATIONEN ===== */ + +/* Slide-In Animation beim Laden */ +@keyframes slideIn { + from { + opacity: 0; + transform: scale(0.9); + } + to { + opacity: 1; + transform: scale(1); + } +} + +input[type="checkbox"].main-element, +input[type="checkbox"].form-checkbox { + animation: slideIn 0.3s ease-out; +} + +/* Smooth Toggle Animation */ +input[type="checkbox"].main-element::before, +input[type="checkbox"].form-checkbox::before { + transition: left 0.3s cubic-bezier(0.4, 0.0, 0.2, 1); +} + +/* ===== DARK MODE UNTERSTÜTZUNG ===== */ + +@media (prefers-color-scheme: dark) { + input[type="checkbox"].main-element:not(:checked), + input[type="checkbox"].form-checkbox:not(:checked) { + background-color: #555; + } + + input[type="checkbox"].main-element::before, + input[type="checkbox"].form-checkbox::before { + background-color: #f0f0f0; + } + + input[type="checkbox"].main-element:disabled, + input[type="checkbox"].form-checkbox:disabled { + background-color: #444; + } +} + +/* ===== RESPONSIVE ANPASSUNGEN ===== */ + +/* Mobile Geräte - etwas größere Touch-Targets */ +@media (max-width: 768px) { + input[type="checkbox"].main-element, + input[type="checkbox"].form-checkbox { + width: 48px; + height: 26px; + } + + input[type="checkbox"].main-element::before, + input[type="checkbox"].form-checkbox::before { + width: 20px; + height: 20px; + } + + input[type="checkbox"].main-element:checked::before, + input[type="checkbox"].form-checkbox:checked::before { + left: 25px; + } +} + +/* ===== ACCESSIBILITY VERBESSERUNGEN ===== */ + +/* High Contrast Mode Unterstützung */ +@media (prefers-contrast: high) { + input[type="checkbox"].main-element, + input[type="checkbox"].form-checkbox { + border: 2px solid currentColor; + } + + input[type="checkbox"].main-element:checked, + input[type="checkbox"].form-checkbox:checked { + background-color: #2d7a2e; + } +} + +/* Reduced Motion für Nutzer mit Bewegungsempfindlichkeit */ +@media (prefers-reduced-motion: reduce) { + input[type="checkbox"].main-element, + input[type="checkbox"].form-checkbox, + input[type="checkbox"].main-element::before, + input[type="checkbox"].form-checkbox::before { + transition: none; + animation: none; + } +} + diff --git a/custom/Espo/Custom/Resources/metadata/app/client.json b/custom/Espo/Custom/Resources/metadata/app/client.json index fda7e2ff..c3c80b47 100644 --- a/custom/Espo/Custom/Resources/metadata/app/client.json +++ b/custom/Espo/Custom/Resources/metadata/app/client.json @@ -1,6 +1,7 @@ { "cssList": [ "__APPEND__", - "client/custom/css/erstgespraech-highlight.css" + "client/custom/css/erstgespraech-highlight.css", + "client/custom/css/checkbox-toggle-slider.css" ] } diff --git a/custom/scripts/VALIDATION_TOOLS.md b/custom/scripts/VALIDATION_TOOLS.md new file mode 100644 index 00000000..0ac0e61d --- /dev/null +++ b/custom/scripts/VALIDATION_TOOLS.md @@ -0,0 +1,292 @@ +# Validierungstools für EspoCRM Custom-Entwicklung + +## Installierte Tools + +### 1. PHP-CLI (v8.2.29) +**Zweck:** Validierung von PHP-Syntax + +**Installation:** +```bash +sudo apt install -y php-cli +``` + +**Verwendung im Script:** +```bash +php -l /pfad/zur/datei.php +``` + +**Validiert:** +- PHP-Syntax-Fehler +- Parse-Fehler +- Fehlende Klammern/Semikolons +- Ungültige Funktionsdeklarationen + +--- + +### 2. CSSLint (v1.0.4) +**Zweck:** CSS-Validierung und Best-Practice-Checks + +**Installation:** +```bash +sudo apt install -y nodejs npm +sudo npm install -g csslint +``` + +**Verwendung im Script:** +```bash +csslint --format=compact --quiet /pfad/zur/datei.css +``` + +**Validiert:** +- CSS-Syntax-Fehler +- Ungültige Properties +- Vendor-Prefix-Probleme +- Performance-Probleme +- Kompatibilitätsprobleme + +**Konfiguration:** +Das Script verwendet standardmäßig alle csslint-Regeln. Für custom Rules kann eine `.csslintrc` Datei erstellt werden. + +--- + +### 3. JSHint (v2.13.6) +**Zweck:** JavaScript-Code-Qualität und Syntax-Validierung + +**Installation:** +```bash +sudo npm install -g jshint +``` + +**Verwendung im Script:** +```bash +jshint --config=/dev/null /pfad/zur/datei.js +``` + +**Validiert:** +- JavaScript-Syntax-Fehler +- Potenzielle Bugs +- Code-Style-Probleme +- Ungültige Variablen-Deklarationen +- Scope-Probleme + +**Konfiguration:** +Aktuell nutzt das Script minimale Konfiguration. Für erweiterte Checks kann eine `.jshintrc` Datei erstellt werden: + +```json +{ + "esversion": 6, + "browser": true, + "jquery": true, + "unused": true, + "undef": true +} +``` + +--- + +## Integration im Validierungsscript + +Das Script `custom/scripts/validate_and_rebuild.py` nutzt diese Tools automatisch: + +### Automatische Erkennung +```python +# Prüft ob Tool verfügbar ist +try: + subprocess.run(['csslint', '--version'], capture_output=True) + use_csslint = True +except FileNotFoundError: + use_csslint = False + # Fallback auf Basis-Validierung +``` + +### Fallback-Mechanismus +Falls ein Tool nicht verfügbar ist, verwendet das Script eine Basis-Validierung: + +- **CSS:** Klammer-Matching, Attribut-Selektoren +- **JavaScript:** Klammer-Matching (rund, eckig, geschweift) +- **PHP:** Übersprungen mit Warnung + +--- + +## Validierungsebenen + +### Kritische Fehler (Rebuild wird abgebrochen) +- JSON-Syntax-Fehler +- PHP-Syntax-Fehler +- CSS-Syntax-Fehler (schwerwiegend) +- JavaScript-Syntax-Fehler +- Fehlende Relationship-Definitionen + +### Warnungen (Rebuild wird fortgesetzt) +- CSS-Best-Practice-Verletzungen +- Fehlende i18n-Übersetzungen +- Code-Style-Probleme +- Performance-Hinweise + +--- + +## Manuelle Verwendung der Tools + +### CSS prüfen +```bash +# Einzelne Datei +csslint client/custom/css/my-styles.css + +# Alle CSS-Dateien +find client/custom/css -name "*.css" -exec csslint {} \; + +# Mit spezifischen Regeln +csslint --errors=errors,duplicate-properties client/custom/css/ +``` + +### JavaScript prüfen +```bash +# Einzelne Datei +jshint client/custom/src/views/my-view.js + +# Alle JS-Dateien +find client/custom/src -name "*.js" -exec jshint {} \; + +# Mit Konfiguration +jshint --config .jshintrc client/custom/src/ +``` + +### PHP prüfen +```bash +# Einzelne Datei +php -l custom/Espo/Custom/Classes/MyClass.php + +# Alle PHP-Dateien +find custom/Espo -name "*.php" -exec php -l {} \; | grep -v "No syntax errors" +``` + +--- + +## Zusätzliche empfohlene Tools (optional) + +### 1. ESLint (moderne Alternative zu JSHint) +```bash +sudo npm install -g eslint +eslint --init # Erstellt .eslintrc Konfiguration +``` + +### 2. Stylelint (moderne Alternative zu CSSLint) +```bash +sudo npm install -g stylelint stylelint-config-standard +# Benötigt .stylelintrc Konfiguration +``` + +### 3. PHPStan (statische PHP-Analyse) +```bash +composer require --dev phpstan/phpstan +vendor/bin/phpstan analyse custom/Espo +``` + +### 4. PHP_CodeSniffer (PHP Code-Style) +```bash +sudo apt install -y php-codesniffer +phpcs --standard=PSR12 custom/Espo/ +``` + +--- + +## Troubleshooting + +### Tool nicht gefunden +```bash +# Prüfe Installation +which csslint +which jshint +which php + +# Installiere fehlende Tools +sudo apt update +sudo apt install -y nodejs npm php-cli +sudo npm install -g csslint jshint +``` + +### Validierung zu streng +Bearbeite das Script und passe die Tool-Parameter an: +```python +# Weniger strikte CSS-Validierung +result = subprocess.run( + ['csslint', '--format=compact', '--errors=errors', str(css_file)], + ... +) +``` + +### Performance-Probleme +Für große Projekte mit vielen Dateien: +```python +# Parallele Validierung mit ThreadPoolExecutor +from concurrent.futures import ThreadPoolExecutor + +with ThreadPoolExecutor(max_workers=4) as executor: + results = executor.map(validate_css_file, css_files) +``` + +--- + +## Best Practices + +1. **Vor jedem Commit:** Führe Validierung aus + ```bash + python3 custom/scripts/validate_and_rebuild.py --dry-run + ``` + +2. **Automatisierung:** Integriere in Git pre-commit hook + ```bash + # .git/hooks/pre-commit + #!/bin/bash + python3 custom/scripts/validate_and_rebuild.py --dry-run + if [ $? -ne 0 ]; then + echo "Validierung fehlgeschlagen. Commit abgebrochen." + exit 1 + fi + ``` + +3. **CI/CD Integration:** Nutze in Build-Pipeline + ```yaml + # .gitlab-ci.yml / .github/workflows/ + validate: + script: + - python3 custom/scripts/validate_and_rebuild.py --dry-run + ``` + +4. **IDE Integration:** Konfiguriere Editor für Live-Feedback + - VSCode: ESLint/Stylelint Extensions + - PHPStorm: Built-in PHP/JS/CSS Validierung + +--- + +## Maintenance + +### Updates der Tools +```bash +# System-Packages +sudo apt update && sudo apt upgrade + +# NPM-Packages +sudo npm update -g csslint jshint + +# Prüfe Versionen +php --version +csslint --version +jshint --version +``` + +### Entfernung +```bash +# NPM-Tools +sudo npm uninstall -g csslint jshint + +# APT-Packages +sudo apt remove php-cli nodejs npm +sudo apt autoremove +``` + +--- + +**Letzte Aktualisierung:** Januar 2026 +**Maintainer:** Custom Scripts Team +**Dokumentation:** `custom/scripts/VALIDATION_TOOLS.md` diff --git a/custom/scripts/validate_and_rebuild.py b/custom/scripts/validate_and_rebuild.py index d8ae98ab..4726622d 100755 --- a/custom/scripts/validate_and_rebuild.py +++ b/custom/scripts/validate_and_rebuild.py @@ -45,6 +45,7 @@ class EntityValidator: self.custom_path = self.base_path / "custom" / "Espo" / "Custom" / "Resources" self.metadata_path = self.custom_path / "metadata" self.i18n_path = self.custom_path / "i18n" + self.client_custom_path = self.base_path / "client" / "custom" self.errors = [] self.warnings = [] self.entity_defs = {} @@ -379,9 +380,262 @@ class EntityValidator: print_warning(f"Konnte Dateirechte nicht prüfen: {e}") return True + def validate_css_files(self) -> bool: + """Validiere CSS-Dateien mit csslint.""" + print_header("7. CSS-VALIDIERUNG") + + css_files = [] + if self.client_custom_path.exists(): + css_files = list((self.client_custom_path / "css").rglob("*.css")) if (self.client_custom_path / "css").exists() else [] + + if not css_files: + print_info("Keine CSS-Dateien gefunden") + return True + + # Prüfe ob csslint verfügbar ist + try: + subprocess.run(['csslint', '--version'], capture_output=True, timeout=5) + use_csslint = True + except (FileNotFoundError, subprocess.TimeoutExpired): + use_csslint = False + print_warning("csslint nicht gefunden, verwende Basis-Validierung") + + invalid_files = [] + for css_file in css_files: + if use_csslint: + # Verwende csslint für professionelle Validierung + try: + result = subprocess.run( + ['csslint', '--format=compact', '--quiet', str(css_file)], + capture_output=True, + text=True, + timeout=10 + ) + + if result.returncode != 0 and result.stdout.strip(): + # Parse csslint Ausgabe + errors = [] + for line in result.stdout.strip().split('\n'): + if 'Error' in line or 'Warning' in line: + # Extrahiere nur die Fehlermeldung ohne Pfad + parts = line.split(': ', 2) + if len(parts) >= 3: + errors.append(parts[2]) + else: + errors.append(line) + + if errors: + # Nur echte Fehler, keine Warnungen als kritisch behandeln + if any('Error' in err for err in errors): + self.errors.append(f"CSS-Fehler in {css_file.relative_to(self.base_path)}") + invalid_files.append((str(css_file.relative_to(self.base_path)), errors)) + else: + # Nur Warnungen + for err in errors: + self.warnings.append(f"{css_file.relative_to(self.base_path)}: {err}") + + except subprocess.TimeoutExpired: + self.warnings.append(f"CSS-Validierung timeout für {css_file.relative_to(self.base_path)}") + except Exception as e: + self.warnings.append(f"Konnte {css_file.relative_to(self.base_path)} nicht validieren: {e}") + else: + # Fallback: Basis-Validierung + try: + with open(css_file, 'r', encoding='utf-8') as f: + content = f.read() + + errors = [] + + # Geschlossene Klammern + open_braces = content.count('{') + close_braces = content.count('}') + if open_braces != close_braces: + errors.append(f"Ungleiche Klammern: {open_braces} {{ vs {close_braces} }}") + + if errors: + self.errors.append(f"CSS-Fehler in {css_file.relative_to(self.base_path)}") + invalid_files.append((str(css_file.relative_to(self.base_path)), errors)) + + except Exception as e: + self.errors.append(f"Konnte {css_file.relative_to(self.base_path)} nicht lesen: {e}") + invalid_files.append((str(css_file.relative_to(self.base_path)), [str(e)])) + + if invalid_files: + print_error(f"{len(invalid_files)} CSS-Datei(en) mit Fehlern:") + for filepath, errors in invalid_files: + print(f" {Colors.RED}•{Colors.END} {filepath}") + for err in errors[:5]: # Maximal 5 Fehler pro Datei anzeigen + print(f" {Colors.RED}→{Colors.END} {err}") + if len(errors) > 5: + print(f" {Colors.RED}...{Colors.END} und {len(errors) - 5} weitere Fehler") + return False + else: + print_success(f"Alle {len(css_files)} CSS-Dateien sind syntaktisch korrekt") + return True + + def validate_js_files(self) -> bool: + """Validiere JavaScript-Dateien mit jshint.""" + print_header("8. JAVASCRIPT-VALIDIERUNG") + + js_files = [] + if self.client_custom_path.exists(): + src_path = self.client_custom_path / "src" + js_files = list(src_path.rglob("*.js")) if src_path.exists() else [] + + if not js_files: + print_info("Keine JavaScript-Dateien gefunden") + return True + + # Prüfe ob jshint verfügbar ist + try: + subprocess.run(['jshint', '--version'], capture_output=True, timeout=5) + use_jshint = True + except (FileNotFoundError, subprocess.TimeoutExpired): + use_jshint = False + print_warning("jshint nicht gefunden, verwende Basis-Validierung") + + invalid_files = [] + for js_file in js_files: + if use_jshint: + # Verwende jshint für professionelle Validierung + try: + result = subprocess.run( + ['jshint', '--config=/dev/null', str(js_file)], + capture_output=True, + text=True, + timeout=10 + ) + + if result.returncode != 0 and result.stdout.strip(): + errors = [] + for line in result.stdout.strip().split('\n'): + if line and not line.startswith('Lint'): + # Parse jshint Ausgabe + errors.append(line) + + if errors: + self.errors.append(f"JavaScript-Fehler in {js_file.relative_to(self.base_path)}") + invalid_files.append((str(js_file.relative_to(self.base_path)), errors)) + + except subprocess.TimeoutExpired: + self.warnings.append(f"JavaScript-Validierung timeout für {js_file.relative_to(self.base_path)}") + except Exception as e: + self.warnings.append(f"Konnte {js_file.relative_to(self.base_path)} nicht validieren: {e}") + else: + # Fallback: Basis-Validierung + try: + with open(js_file, 'r', encoding='utf-8') as f: + content = f.read() + + errors = [] + + # Geschlossene Klammern + open_paren = content.count('(') + close_paren = content.count(')') + if open_paren != close_paren: + errors.append(f"Ungleiche runde Klammern: {open_paren} ( vs {close_paren} )") + + open_braces = content.count('{') + close_braces = content.count('}') + if open_braces != close_braces: + errors.append(f"Ungleiche geschweifte Klammern: {open_braces} {{ vs {close_braces} }}") + + open_brackets = content.count('[') + close_brackets = content.count(']') + if open_brackets != close_brackets: + errors.append(f"Ungleiche eckige Klammern: {open_brackets} [ vs {close_brackets} ]") + + if errors: + self.errors.append(f"JavaScript-Fehler in {js_file.relative_to(self.base_path)}") + invalid_files.append((str(js_file.relative_to(self.base_path)), errors)) + + except Exception as e: + self.errors.append(f"Konnte {js_file.relative_to(self.base_path)} nicht lesen: {e}") + invalid_files.append((str(js_file.relative_to(self.base_path)), [str(e)])) + + if invalid_files: + print_error(f"{len(invalid_files)} JavaScript-Datei(en) mit Fehlern:") + for filepath, errors in invalid_files: + print(f" {Colors.RED}•{Colors.END} {filepath}") + for err in errors[:5]: # Maximal 5 Fehler pro Datei + print(f" {Colors.RED}→{Colors.END} {err}") + if len(errors) > 5: + print(f" {Colors.RED}...{Colors.END} und {len(errors) - 5} weitere Fehler") + return False + else: + print_success(f"Alle {len(js_files)} JavaScript-Dateien sind syntaktisch korrekt") + return True + + def validate_php_files(self) -> bool: + """Validiere PHP-Dateien mit php -l (Lint).""" + print_header("9. PHP-VALIDIERUNG") + + php_files = [] + custom_espo_path = self.base_path / "custom" / "Espo" + if custom_espo_path.exists(): + php_files = list(custom_espo_path.rglob("*.php")) + + if not php_files: + print_info("Keine PHP-Dateien gefunden") + return True + + # Prüfe ob php verfügbar ist + try: + subprocess.run(['php', '--version'], capture_output=True, timeout=5) + except (FileNotFoundError, subprocess.TimeoutExpired): + print_warning("PHP-CLI nicht gefunden, überspringe PHP-Validierung") + return True + + invalid_files = [] + for php_file in php_files: + try: + # Verwende php -l für Syntax-Check + result = subprocess.run( + ['php', '-l', str(php_file)], + capture_output=True, + text=True, + timeout=5 + ) + + if result.returncode != 0: + error_lines = [] + output = result.stderr.strip() or result.stdout.strip() + + for line in output.split('\n'): + # Filtere die relevanten Fehlerzeilen + if line and not line.startswith('No syntax errors'): + # Entferne Datei-Pfad aus Fehlermeldung für bessere Lesbarkeit + clean_line = re.sub(r'^.*?in\s+.*?on\s+', '', line) + if clean_line != line: # Wenn Ersetzung stattfand + error_lines.append(clean_line) + else: + error_lines.append(line) + + if error_lines: + self.errors.append(f"PHP-Syntax-Fehler in {php_file.relative_to(self.base_path)}") + invalid_files.append((str(php_file.relative_to(self.base_path)), error_lines)) + + except subprocess.TimeoutExpired: + self.warnings.append(f"PHP-Validierung timeout für {php_file.relative_to(self.base_path)}") + except Exception as e: + self.warnings.append(f"Konnte {php_file.relative_to(self.base_path)} nicht validieren: {e}") + + if invalid_files: + print_error(f"{len(invalid_files)} PHP-Datei(en) mit Syntax-Fehlern:") + for filepath, errors in invalid_files: + print(f" {Colors.RED}•{Colors.END} {filepath}") + for err in errors[:3]: # Maximal 3 Fehler pro Datei + print(f" {Colors.RED}→{Colors.END} {err}") + if len(errors) > 3: + print(f" {Colors.RED}...{Colors.END} und {len(errors) - 3} weitere Fehler") + return False + else: + print_success(f"Alle {len(php_files)} PHP-Dateien sind syntaktisch korrekt") + return True + def run_rebuild(self) -> bool: """Führe den EspoCRM Rebuild aus.""" - print_header("7. ESPOCRM REBUILD") + print_header("10. ESPOCRM REBUILD") # Prüfe ob wir in einem Docker-Volume sind is_docker_volume = '/docker/volumes/' in str(self.base_path) @@ -553,6 +807,18 @@ class EntityValidator: # 6. Dateirechte (nicht kritisch für Rebuild) self.check_file_permissions() + # 7. CSS-Validierung (kritisch) + if not self.validate_css_files(): + all_valid = False + + # 8. JavaScript-Validierung (kritisch) + if not self.validate_js_files(): + all_valid = False + + # 9. PHP-Validierung (kritisch) + if not self.validate_php_files(): + all_valid = False + return all_valid def main(): diff --git a/data/config.php b/data/config.php index 647667c1..d6c665b6 100644 --- a/data/config.php +++ b/data/config.php @@ -359,8 +359,8 @@ return [ 0 => 'youtube.com', 1 => 'google.com' ], - 'cacheTimestamp' => 1769210138, - 'microtime' => 1769210138.514983, + 'cacheTimestamp' => 1769211423, + 'microtime' => 1769211423.866265, 'siteUrl' => 'https://crm.bitbylaw.com', 'fullTextSearchMinLength' => 4, 'appTimestamp' => 1768843902,