Add CSS validation and new checkbox toggle slider styles; update validation tools documentation

This commit is contained in:
2026-01-24 00:38:56 +01:00
parent dfb0ff1fbc
commit 933df204f2
5 changed files with 869 additions and 4 deletions

View File

@@ -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"
]
}

View File

@@ -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`

View File

@@ -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():