Replace legacy Bash check and rebuild script with a new Python-based validator; add comprehensive validation checks and improve documentation for usage and features.
This commit is contained in:
27
README.md
27
README.md
@@ -44,22 +44,28 @@ Keine integrierte KI-Schnittstelle existiert, aber mit Dateizugriff können auto
|
|||||||
|
|
||||||
**WICHTIG:** Nach jeder Änderung an Custom-Dateien muss ein Rebuild durchgeführt werden!
|
**WICHTIG:** Nach jeder Änderung an Custom-Dateien muss ein Rebuild durchgeführt werden!
|
||||||
|
|
||||||
### Check & Rebuild Script (Empfohlen)
|
### Validate & Rebuild Script (Empfohlen)
|
||||||
|
|
||||||
**Zentrales Tool:** `custom/scripts/check_and_rebuild.sh`
|
**Zentrales Tool:** `custom/scripts/validate_and_rebuild.py`
|
||||||
|
|
||||||
|
**NEU ab Januar 2026:** Erweitertes Python-basiertes Validierungs-Tool mit automatischen Checks!
|
||||||
|
|
||||||
Dieses Script sollte **IMMER** verwendet werden (nicht manueller Rebuild). Es führt automatisch aus:
|
Dieses Script sollte **IMMER** verwendet werden (nicht manueller Rebuild). Es führt automatisch aus:
|
||||||
|
|
||||||
✅ **Validierungen:**
|
✅ **Validierungen:**
|
||||||
- JSON-Syntax-Prüfung aller `.json` Dateien im `custom/` Verzeichnis
|
- JSON-Syntax-Prüfung aller `.json` Dateien im `custom/` Verzeichnis
|
||||||
|
- **Relationship-Konsistenz-Prüfung** (bidirektionale Links, foreign-Definitionen)
|
||||||
|
- **Formula-Script Platzierung** (korrekt in `/formula/` statt `/entityDefs/`)
|
||||||
|
- **i18n-Vollständigkeit** (fehlende Übersetzungen für Links)
|
||||||
|
- Layout-Struktur-Prüfung
|
||||||
- Dateirechte-Prüfung (`www-data:www-data` Owner)
|
- Dateirechte-Prüfung (`www-data:www-data` Owner)
|
||||||
- System-Checks (Cache-Verzeichnis, Logs-Verzeichnis)
|
|
||||||
|
|
||||||
✅ **Automatische Korrekturen:**
|
✅ **Automatische Korrekturen:**
|
||||||
- Setzt fehlerhafte Dateirechte auf `www-data:www-data`
|
- Setzt fehlerhafte Dateirechte auf `www-data:www-data`
|
||||||
- Korrigiert Verzeichnis-Permissions (775) und Datei-Permissions (664)
|
- Korrigiert Verzeichnis-Permissions (775) und Datei-Permissions (664)
|
||||||
|
|
||||||
✅ **Rebuild:**
|
✅ **Rebuild:**
|
||||||
|
- **Nur wenn keine kritischen Fehler gefunden werden!**
|
||||||
- Merged alle Custom-Metadata mit Core-Definitionen
|
- Merged alle Custom-Metadata mit Core-Definitionen
|
||||||
- Aktualisiert Datenbank-Schema (neue Felder, Tabellen, Indizes)
|
- Aktualisiert Datenbank-Schema (neue Felder, Tabellen, Indizes)
|
||||||
- Leert Cache-Verzeichnis
|
- Leert Cache-Verzeichnis
|
||||||
@@ -68,18 +74,25 @@ Dieses Script sollte **IMMER** verwendet werden (nicht manueller Rebuild). Es f
|
|||||||
**Verwendung:**
|
**Verwendung:**
|
||||||
```bash
|
```bash
|
||||||
# Im EspoCRM-Root-Verzeichnis ausführen
|
# Im EspoCRM-Root-Verzeichnis ausführen
|
||||||
./custom/scripts/check_and_rebuild.sh
|
python3 custom/scripts/validate_and_rebuild.py
|
||||||
|
|
||||||
|
# Nur Validierung ohne Rebuild
|
||||||
|
python3 custom/scripts/validate_and_rebuild.py --dry-run
|
||||||
```
|
```
|
||||||
|
|
||||||
**Ausgabe:**
|
**Ausgabe:**
|
||||||
- ✓ Grün: Alles in Ordnung, Rebuild erfolgreich
|
- ✓ Grün: Alles in Ordnung, Rebuild erfolgreich
|
||||||
- ⚠ Gelb: Warnungen, Rebuild wird trotzdem ausgeführt
|
- ⚠ Gelb: Warnungen (z.B. fehlende i18n), Rebuild wird trotzdem ausgeführt
|
||||||
- ✗ Rot: Fehler (z.B. ungültiges JSON), Rebuild wird NICHT ausgeführt
|
- ✗ Rot: Kritische Fehler (z.B. ungültiges JSON, fehlende Relationships), Rebuild wird NICHT ausgeführt
|
||||||
|
|
||||||
**Bei Fehlern:**
|
**Bei Fehlern:**
|
||||||
- JSON-Syntax-Fehler werden mit Datei und Zeilennummer angezeigt
|
- JSON-Syntax-Fehler werden mit Datei und Zeilennummer angezeigt
|
||||||
|
- Relationship-Fehler zeigen fehlende Links zwischen Entities
|
||||||
|
- Formula-Platzierungsfehler werden erkannt und gemeldet
|
||||||
|
- i18n-Probleme werden als Warnungen angezeigt (kein Abbruch)
|
||||||
- Dateirechte-Probleme werden automatisch korrigiert
|
- Dateirechte-Probleme werden automatisch korrigiert
|
||||||
- System-Fehler (fehlende Verzeichnisse) müssen manuell behoben werden
|
|
||||||
|
**Detaillierte Dokumentation:** Siehe `custom/scripts/VALIDATOR_README.md`
|
||||||
|
|
||||||
### Wann Rebuild erforderlich?
|
### Wann Rebuild erforderlich?
|
||||||
|
|
||||||
|
|||||||
120
custom/scripts/QUICKSTART.md
Normal file
120
custom/scripts/QUICKSTART.md
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
# 🚀 EspoCRM Validator - Quick Start
|
||||||
|
|
||||||
|
## Installation bereits abgeschlossen ✅
|
||||||
|
|
||||||
|
Das Validierungs-Tool ist bereits installiert und einsatzbereit!
|
||||||
|
|
||||||
|
## Verwendung
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Im EspoCRM-Root-Verzeichnis
|
||||||
|
python3 custom/scripts/validate_and_rebuild.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Das war's! Das Script führt automatisch alle Checks durch und startet den Rebuild.
|
||||||
|
|
||||||
|
**Tipp:** Für nur Validierung ohne Rebuild: `python3 custom/scripts/validate_and_rebuild.py --dry-run`
|
||||||
|
|
||||||
|
## Was wird geprüft?
|
||||||
|
|
||||||
|
1. ✅ **JSON-Syntax** - Alle Custom-Dateien
|
||||||
|
2. ✅ **Relationships** - Bidirektionale Link-Konsistenz
|
||||||
|
3. ✅ **Formula-Scripts** - Korrekte Platzierung
|
||||||
|
4. ✅ **i18n** - Vollständigkeit der Übersetzungen
|
||||||
|
5. ✅ **Layouts** - Struktur-Validierung
|
||||||
|
6. ✅ **Dateirechte** - Owner & Permissions
|
||||||
|
7. ✅ **Rebuild** - Nur bei Fehlerfreiheit
|
||||||
|
|
||||||
|
## Output verstehen
|
||||||
|
|
||||||
|
| Symbol | Bedeutung | Aktion |
|
||||||
|
|--------|-----------|--------|
|
||||||
|
| ✓ Grün | OK | Keine |
|
||||||
|
| ⚠ Gelb | Warnung | Optional beheben |
|
||||||
|
| ✗ Rot | Fehler | **Muss behoben werden!** |
|
||||||
|
|
||||||
|
## Beispiel
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ python3 custom/scripts/validate_and_rebuild.py
|
||||||
|
|
||||||
|
EspoCRM Custom Entity Validator & Rebuild Tool
|
||||||
|
Arbeitsverzeichnis: /var/lib/docker/volumes/vmh-espocrm_espocrm/_data
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
1. JSON-SYNTAX VALIDIERUNG
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
✓ Alle 547 JSON-Dateien sind syntaktisch korrekt
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
2. RELATIONSHIP-KONSISTENZ
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
✗ 4 Relationship-Fehler gefunden:
|
||||||
|
• CMietobjekt.vmhRumungsklages → CVmhRumungsklage:
|
||||||
|
Foreign link 'mietobjekte' fehlt in CVmhRumungsklage
|
||||||
|
...
|
||||||
|
|
||||||
|
✗ REBUILD ABGEBROCHEN: Kritische Fehler müssen behoben werden!
|
||||||
|
```
|
||||||
|
|
||||||
|
## Häufige Fehler beheben
|
||||||
|
|
||||||
|
### Relationship-Fehler
|
||||||
|
```json
|
||||||
|
// In CVmhRumungsklage.json HINZUFÜGEN:
|
||||||
|
{
|
||||||
|
"links": {
|
||||||
|
"mietobjekte": {
|
||||||
|
"type": "hasMany",
|
||||||
|
"entity": "CMietobjekt",
|
||||||
|
"foreign": "vmhRumungsklages"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### JSON-Syntax-Fehler
|
||||||
|
- Mit einem JSON-Validator prüfen (z.B. `python3 -m json.tool datei.json`)
|
||||||
|
- Auf fehlende Kommata, geschweifte Klammern achten
|
||||||
|
|
||||||
|
### i18n-Warnung (optional)
|
||||||
|
```json
|
||||||
|
// In i18n/de_DE/Entity.json ERGÄNZEN:
|
||||||
|
{
|
||||||
|
"links": {
|
||||||
|
"meinLink": "Mein Link"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mehr Infos
|
||||||
|
|
||||||
|
- **Ausführliche Doku:** `custom/scripts/VALIDATOR_README.md`
|
||||||
|
- **EspoCRM-Doku:** `README.md`
|
||||||
|
- **Custom-Struktur:** `custom/CUSTOM_DIRECTORY.md`
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
**Python3 nicht gefunden?**
|
||||||
|
```bash
|
||||||
|
sudo apt-get install python3 # Ubuntu/Debian
|
||||||
|
sudo yum install python3 # CentOS/RHEL
|
||||||
|
```
|
||||||
|
|
||||||
|
**Keine Berechtigung?**
|
||||||
|
```bash
|
||||||
|
chmod +x custom/scripts/validate_and_rebuild.py
|
||||||
|
```
|
||||||
|
|
||||||
|
**Script findet rebuild.php nicht?**
|
||||||
|
```bash
|
||||||
|
# Ins richtige Verzeichnis wechseln
|
||||||
|
cd /var/lib/docker/volumes/vmh-espocrm_espocrm/_data
|
||||||
|
python3 custom/scripts/validate_and_rebuild.py
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**💡 Tipp:** Führe das Script nach **jeder** Änderung an Custom-Dateien aus!
|
||||||
182
custom/scripts/VALIDATOR_README.md
Normal file
182
custom/scripts/VALIDATOR_README.md
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
# EspoCRM Validator & Rebuild Tool
|
||||||
|
|
||||||
|
## Übersicht
|
||||||
|
|
||||||
|
Das neue Python-basierte Validierungs-Tool `validate_and_rebuild.py` ersetzt das bisherige Bash-Script und bietet erweiterte Prüfungen für EspoCRM Custom-Entities.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### ✅ Automatische Validierungen
|
||||||
|
|
||||||
|
1. **JSON-Syntax-Prüfung**
|
||||||
|
- Validiert alle `.json` Dateien im `custom/` Verzeichnis
|
||||||
|
- Findet Syntax-Fehler mit Dateiname und Zeilennummer
|
||||||
|
|
||||||
|
2. **Relationship-Konsistenz**
|
||||||
|
- Prüft bidirektionale Relationships (hasMany/hasOne)
|
||||||
|
- Validiert `foreign`-Links zwischen Entities
|
||||||
|
- Überprüft `relationName`-Konsistenz
|
||||||
|
- Erkennt fehlende Gegenseiten-Definitionen
|
||||||
|
|
||||||
|
3. **Formula-Script Platzierung**
|
||||||
|
- Prüft ob Formula-Scripts in `/formula/` statt `/entityDefs/` liegen
|
||||||
|
- Warnt vor leeren Formula-Definitionen
|
||||||
|
|
||||||
|
4. **i18n-Vollständigkeit**
|
||||||
|
- Findet fehlende Übersetzungen (de_DE / en_US)
|
||||||
|
- Prüft Link-Labels für alle Custom-Relationships
|
||||||
|
- Warnt bei komplett fehlenden i18n-Dateien
|
||||||
|
|
||||||
|
5. **Layout-Struktur**
|
||||||
|
- Validiert clientDefs und bottomPanels
|
||||||
|
- Findet unnötige `false`-Elemente
|
||||||
|
|
||||||
|
6. **Dateirechte**
|
||||||
|
- Prüft Owner (www-data:www-data)
|
||||||
|
- Korrigiert Permissions automatisch
|
||||||
|
|
||||||
|
7. **Rebuild-Ausführung**
|
||||||
|
- Führt `rebuild.php` nur aus, wenn keine kritischen Fehler vorliegen
|
||||||
|
|
||||||
|
## Verwendung
|
||||||
|
|
||||||
|
### Direkt ausführen
|
||||||
|
```bash
|
||||||
|
cd /path/to/espocrm
|
||||||
|
python3 custom/scripts/validate_and_rebuild.py
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Exit Codes
|
||||||
|
|
||||||
|
- `0` - Erfolg: Alle Validierungen bestanden, Rebuild erfolgreich
|
||||||
|
- `1` - Fehler: Kritische Fehler gefunden oder Rebuild fehlgeschlagen
|
||||||
|
|
||||||
|
## Output-Format
|
||||||
|
|
||||||
|
Das Script verwendet farbcodierte Ausgaben:
|
||||||
|
|
||||||
|
- 🟢 **Grün (✓)**: Erfolgreich / OK
|
||||||
|
- 🟡 **Gelb (⚠)**: Warnungen (nicht kritisch)
|
||||||
|
- 🔴 **Rot (✗)**: Fehler (kritisch, Rebuild wird abgebrochen)
|
||||||
|
- 🔵 **Blau (ℹ)**: Informationen
|
||||||
|
|
||||||
|
## Fehlertypen
|
||||||
|
|
||||||
|
### Kritische Fehler (Rebuild-Abbruch)
|
||||||
|
- JSON-Syntax-Fehler
|
||||||
|
- Fehlende Relationship-Definitionen
|
||||||
|
- Falsch platzierte Formula-Scripts
|
||||||
|
|
||||||
|
### Warnungen (kein Abbruch)
|
||||||
|
- Fehlende i18n-Übersetzungen
|
||||||
|
- Layout-Strukturprobleme
|
||||||
|
- Dateirechte-Probleme
|
||||||
|
|
||||||
|
## Beispiel-Output
|
||||||
|
|
||||||
|
```
|
||||||
|
EspoCRM Custom Entity Validator & Rebuild Tool
|
||||||
|
Arbeitsverzeichnis: /var/lib/docker/volumes/vmh-espocrm_espocrm/_data
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
1. JSON-SYNTAX VALIDIERUNG
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
✓ Alle 547 JSON-Dateien sind syntaktisch korrekt
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
2. RELATIONSHIP-KONSISTENZ
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
✗ 4 Relationship-Fehler gefunden:
|
||||||
|
• Contact.cBankverbindungenContact: Ziel-Entity 'CBankverbindung' existiert nicht
|
||||||
|
• CMietobjekt.vmhRumungsklages → CVmhRumungsklage: Foreign link 'mietobjekte' fehlt
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
ZUSAMMENFASSUNG
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
FEHLER: 4
|
||||||
|
✗ Contact.cBankverbindungenContact: Ziel-Entity 'CBankverbindung' existiert nicht
|
||||||
|
...
|
||||||
|
|
||||||
|
REBUILD ABGEBROCHEN: Kritische Fehler müssen behoben werden!
|
||||||
|
```
|
||||||
|
|
||||||
|
## Erweiterung
|
||||||
|
|
||||||
|
Das Script ist modular aufgebaut. Neue Validierungen können einfach hinzugefügt werden:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def validate_custom_check(self) -> bool:
|
||||||
|
"""Eigene Validierung."""
|
||||||
|
print_header("X. CUSTOM CHECK")
|
||||||
|
|
||||||
|
# Prüflogik hier
|
||||||
|
if error_found:
|
||||||
|
self.errors.append("Fehlerbeschreibung")
|
||||||
|
return False
|
||||||
|
|
||||||
|
print_success("Check erfolgreich")
|
||||||
|
return True
|
||||||
|
```
|
||||||
|
|
||||||
|
Dann in `validate_all()` hinzufügen:
|
||||||
|
|
||||||
|
```python
|
||||||
|
if not self.validate_custom_check():
|
||||||
|
all_valid = False
|
||||||
|
```
|
||||||
|
|
||||||
|
## Anforderungen
|
||||||
|
|
||||||
|
- Python 3.6+
|
||||||
|
- Keine zusätzlichen Packages erforderlich (nur Standard-Library)
|
||||||
|
- Optionale sudo-Rechte für Dateirechte-Korrektur
|
||||||
|
|
||||||
|
## Integration in Workflow
|
||||||
|
|
||||||
|
Das Script kann in automatisierte Workflows integriert werden:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# In CI/CD Pipeline
|
||||||
|
python3 custom/scripts/validate_and_rebuild.py || exit 1
|
||||||
|
|
||||||
|
# Als Git Pre-Commit Hook
|
||||||
|
#!/bin/bash
|
||||||
|
python3 custom/scripts/validate_and_rebuild.py --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Script findet rebuild.php nicht
|
||||||
|
```bash
|
||||||
|
# Stelle sicher, dass du im EspoCRM-Root bist
|
||||||
|
cd /var/lib/docker/volumes/vmh-espocrm_espocrm/_data
|
||||||
|
python3 custom/scripts/validate_and_rebuild.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dateirechte können nicht korrigiert werden
|
||||||
|
```bash
|
||||||
|
# Manuell mit sudo korrigieren
|
||||||
|
sudo chown -R www-data:www-data custom/
|
||||||
|
sudo find custom/ -type f -exec chmod 664 {} \;
|
||||||
|
sudo find custom/ -type d -exec chmod 775 {} \;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Python3 nicht verfügbar
|
||||||
|
```bash
|
||||||
|
# Ubuntu/Debian
|
||||||
|
sudo apt-get install python3
|
||||||
|
|
||||||
|
# CentOS/RHEL
|
||||||
|
sudo yum install python3
|
||||||
|
```
|
||||||
|
|
||||||
|
## Siehe auch
|
||||||
|
|
||||||
|
- [README.md](../../README.md) - Hauptdokumentation
|
||||||
|
- [CUSTOM_DIRECTORY.md](../CUSTOM_DIRECTORY.md) - Custom-Verzeichnis-Struktur
|
||||||
|
- [workflow_manager.php](workflow_manager.php) - Workflow-Management-Tool
|
||||||
@@ -1,218 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# EspoCRM Check & Rebuild Script
|
|
||||||
# Prüft auf häufige Fehler und führt bei Fehlerfreiheit einen Rebuild durch
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
||||||
CUSTOM_DIR="$SCRIPT_DIR/custom"
|
|
||||||
ERRORS=0
|
|
||||||
WARNINGS=0
|
|
||||||
|
|
||||||
echo "=========================================="
|
|
||||||
echo "EspoCRM Check & Rebuild Script"
|
|
||||||
echo "=========================================="
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Farben für Output
|
|
||||||
RED='\033[0;31m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
# 1. JSON-Syntax prüfen
|
|
||||||
echo -e "${BLUE}[1/3] Prüfe JSON-Syntax...${NC}"
|
|
||||||
echo "---"
|
|
||||||
|
|
||||||
JSON_FILES=$(find "$CUSTOM_DIR" -type f -name "*.json" 2>/dev/null)
|
|
||||||
JSON_COUNT=$(echo "$JSON_FILES" | grep -c . || echo 0)
|
|
||||||
|
|
||||||
if [ "$JSON_COUNT" -eq 0 ]; then
|
|
||||||
echo -e "${YELLOW}⚠ Warnung: Keine JSON-Dateien im custom/ Verzeichnis gefunden${NC}"
|
|
||||||
WARNINGS=$((WARNINGS + 1))
|
|
||||||
else
|
|
||||||
echo "Gefundene JSON-Dateien: $JSON_COUNT"
|
|
||||||
|
|
||||||
INVALID_JSON=0
|
|
||||||
while IFS= read -r file; do
|
|
||||||
if [ -n "$file" ]; then
|
|
||||||
if ! jq empty "$file" 2>/dev/null; then
|
|
||||||
echo -e "${RED}✗ Fehler: Ungültiges JSON in $file${NC}"
|
|
||||||
ERRORS=$((ERRORS + 1))
|
|
||||||
INVALID_JSON=$((INVALID_JSON + 1))
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done <<< "$JSON_FILES"
|
|
||||||
|
|
||||||
if [ "$INVALID_JSON" -eq 0 ]; then
|
|
||||||
echo -e "${GREEN}✓ Alle JSON-Dateien sind gültig${NC}"
|
|
||||||
else
|
|
||||||
echo -e "${RED}✗ $INVALID_JSON JSON-Dateien mit Syntaxfehlern gefunden${NC}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 2. Dateirechte prüfen und korrigieren
|
|
||||||
echo -e "${BLUE}[2/3] Prüfe Dateirechte...${NC}"
|
|
||||||
echo "---"
|
|
||||||
|
|
||||||
WRONG_OWNER=0
|
|
||||||
FIXED_FILES=0
|
|
||||||
CUSTOM_FILES=$(find "$CUSTOM_DIR" -type f 2>/dev/null || echo "")
|
|
||||||
CUSTOM_DIRS=$(find "$CUSTOM_DIR" -type d 2>/dev/null || echo "")
|
|
||||||
|
|
||||||
if [ -z "$CUSTOM_FILES" ]; then
|
|
||||||
echo -e "${YELLOW}⚠ Warnung: Keine Dateien im custom/ Verzeichnis gefunden${NC}"
|
|
||||||
WARNINGS=$((WARNINGS + 1))
|
|
||||||
else
|
|
||||||
# Prüfe und korrigiere Dateien einzeln
|
|
||||||
while IFS= read -r file; do
|
|
||||||
if [ -n "$file" ]; then
|
|
||||||
OWNER=$(stat -c '%U:%G' "$file" 2>/dev/null || echo "unknown:unknown")
|
|
||||||
if [ "$OWNER" != "www-data:www-data" ]; then
|
|
||||||
WRONG_OWNER=$((WRONG_OWNER + 1))
|
|
||||||
# Korrigiere direkt nur diese Datei
|
|
||||||
if sudo chown www-data:www-data "$file" 2>/dev/null && sudo chmod 664 "$file" 2>/dev/null; then
|
|
||||||
FIXED_FILES=$((FIXED_FILES + 1))
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done <<< "$CUSTOM_FILES"
|
|
||||||
|
|
||||||
# Prüfe und korrigiere Verzeichnisse einzeln
|
|
||||||
while IFS= read -r dir; do
|
|
||||||
if [ -n "$dir" ]; then
|
|
||||||
OWNER=$(stat -c '%U:%G' "$dir" 2>/dev/null || echo "unknown:unknown")
|
|
||||||
if [ "$OWNER" != "www-data:www-data" ]; then
|
|
||||||
sudo chown www-data:www-data "$dir" 2>/dev/null && sudo chmod 775 "$dir" 2>/dev/null
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done <<< "$CUSTOM_DIRS"
|
|
||||||
|
|
||||||
if [ "$WRONG_OWNER" -eq 0 ]; then
|
|
||||||
echo -e "${GREEN}✓ Alle Dateien haben korrekte Berechtigungen (www-data:www-data)${NC}"
|
|
||||||
else
|
|
||||||
if [ "$FIXED_FILES" -eq "$WRONG_OWNER" ]; then
|
|
||||||
echo -e "${GREEN}✓ $FIXED_FILES Dateien korrigiert${NC}"
|
|
||||||
else
|
|
||||||
echo -e "${RED}✗ Fehler: Konnte nicht alle Berechtigungen korrigieren${NC}"
|
|
||||||
ERRORS=$((ERRORS + 1))
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 3. Cache-Verzeichnis prüfen
|
|
||||||
echo -e "${BLUE}[3/3] Prüfe System...${NC}"
|
|
||||||
echo "---"
|
|
||||||
|
|
||||||
if [ ! -d "$SCRIPT_DIR/data/cache" ]; then
|
|
||||||
echo -e "${RED}✗ Fehler: Cache-Verzeichnis existiert nicht${NC}"
|
|
||||||
ERRORS=$((ERRORS + 1))
|
|
||||||
else
|
|
||||||
echo -e "${GREEN}✓ Cache-Verzeichnis existiert${NC}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -d "$SCRIPT_DIR/data/logs" ]; then
|
|
||||||
echo -e "${RED}✗ Fehler: Logs-Verzeichnis existiert nicht${NC}"
|
|
||||||
ERRORS=$((ERRORS + 1))
|
|
||||||
else
|
|
||||||
echo -e "${GREEN}✓ Logs-Verzeichnis existiert${NC}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "=========================================="
|
|
||||||
echo "Zusammenfassung:"
|
|
||||||
echo "---"
|
|
||||||
|
|
||||||
if [ "$ERRORS" -eq 0 ] && [ "$WARNINGS" -eq 0 ]; then
|
|
||||||
echo -e "${GREEN}✓ Keine Fehler oder Warnungen gefunden${NC}"
|
|
||||||
elif [ "$ERRORS" -eq 0 ]; then
|
|
||||||
echo -e "${YELLOW}⚠ $WARNINGS Warnung(en) gefunden (Rebuild wird trotzdem ausgeführt)${NC}"
|
|
||||||
else
|
|
||||||
echo -e "${RED}✗ $ERRORS Fehler und $WARNINGS Warnung(en) gefunden${NC}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "=========================================="
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Entscheidung: Rebuild durchführen oder nicht
|
|
||||||
if [ "$ERRORS" -gt 0 ]; then
|
|
||||||
echo -e "${RED}REBUILD WIRD NICHT DURCHGEFÜHRT${NC}"
|
|
||||||
echo "Bitte behebe die oben genannten Fehler und führe das Script erneut aus."
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo -e "${GREEN}Starte Rebuild und Cache-Bereinigung...${NC}"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Rebuild und Cache-Bereinigung durchführen
|
|
||||||
if command -v docker &> /dev/null; then
|
|
||||||
echo -e "${BLUE}[1/2] Führe Clear Cache aus...${NC}"
|
|
||||||
if docker exec espocrm php /var/www/html/command.php ClearCache 2>&1; then
|
|
||||||
echo -e "${GREEN}✓ Cache erfolgreich gelöscht${NC}"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
echo -e "${BLUE}[2/2] Führe Rebuild aus...${NC}"
|
|
||||||
if docker exec espocrm php /var/www/html/command.php rebuild 2>&1; then
|
|
||||||
echo ""
|
|
||||||
echo -e "${GREEN}=========================================="
|
|
||||||
echo "✓ REBUILD ERFOLGREICH ABGESCHLOSSEN"
|
|
||||||
echo "==========================================${NC}"
|
|
||||||
exit 0
|
|
||||||
else
|
|
||||||
echo ""
|
|
||||||
echo -e "${RED}=========================================="
|
|
||||||
echo "✗ REBUILD FEHLGESCHLAGEN"
|
|
||||||
echo "==========================================${NC}"
|
|
||||||
echo ""
|
|
||||||
echo -e "${YELLOW}Letzte Log-Einträge:${NC}"
|
|
||||||
echo "---"
|
|
||||||
# Zeige die letzten 30 Zeilen der neuesten Log-Datei
|
|
||||||
LATEST_LOG=$(ls -t "$SCRIPT_DIR/data/logs/"*.log 2>/dev/null | head -1)
|
|
||||||
if [ -n "$LATEST_LOG" ]; then
|
|
||||||
echo -e "${BLUE}Aus: $(basename "$LATEST_LOG")${NC}"
|
|
||||||
tail -n 30 "$LATEST_LOG"
|
|
||||||
else
|
|
||||||
echo -e "${YELLOW}Keine Log-Dateien gefunden in data/logs/${NC}"
|
|
||||||
fi
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo -e "${RED}✗ Clear Cache fehlgeschlagen${NC}"
|
|
||||||
echo "Fahre mit Rebuild fort..."
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
if docker exec espocrm php /var/www/html/command.php rebuild 2>&1; then
|
|
||||||
echo ""
|
|
||||||
echo -e "${GREEN}=========================================="
|
|
||||||
echo "✓ REBUILD ERFOLGREICH ABGESCHLOSSEN"
|
|
||||||
echo "==========================================${NC}"
|
|
||||||
exit 0
|
|
||||||
else
|
|
||||||
echo ""
|
|
||||||
echo -e "${RED}=========================================="
|
|
||||||
echo "✗ REBUILD FEHLGESCHLAGEN"
|
|
||||||
echo "==========================================${NC}"
|
|
||||||
echo ""
|
|
||||||
echo -e "${YELLOW}Letzte Log-Einträge:${NC}"
|
|
||||||
echo "---"
|
|
||||||
# Zeige die letzten 30 Zeilen der neuesten Log-Datei
|
|
||||||
LATEST_LOG=$(ls -t "$SCRIPT_DIR/data/logs/"*.log 2>/dev/null | head -1)
|
|
||||||
if [ -n "$LATEST_LOG" ]; then
|
|
||||||
echo -e "${BLUE}Aus: $(basename "$LATEST_LOG")${NC}"
|
|
||||||
tail -n 30 "$LATEST_LOG"
|
|
||||||
else
|
|
||||||
echo -e "${YELLOW}Keine Log-Dateien gefunden in data/logs/${NC}"
|
|
||||||
fi
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo -e "${RED}✗ Docker nicht gefunden. Rebuild kann nicht durchgeführt werden.${NC}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
536
custom/scripts/validate_and_rebuild.py
Executable file
536
custom/scripts/validate_and_rebuild.py
Executable file
@@ -0,0 +1,536 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
EspoCRM Custom Entity Validator & Rebuild Tool
|
||||||
|
Führt umfassende Validierungen durch bevor der Rebuild ausgeführt wird.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Tuple, Set
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
# ANSI Color Codes
|
||||||
|
class Colors:
|
||||||
|
GREEN = '\033[92m'
|
||||||
|
YELLOW = '\033[93m'
|
||||||
|
RED = '\033[91m'
|
||||||
|
BLUE = '\033[94m'
|
||||||
|
BOLD = '\033[1m'
|
||||||
|
END = '\033[0m'
|
||||||
|
|
||||||
|
def print_header(text: str):
|
||||||
|
print(f"\n{Colors.BOLD}{Colors.BLUE}{'='*70}{Colors.END}")
|
||||||
|
print(f"{Colors.BOLD}{Colors.BLUE}{text.center(70)}{Colors.END}")
|
||||||
|
print(f"{Colors.BOLD}{Colors.BLUE}{'='*70}{Colors.END}\n")
|
||||||
|
|
||||||
|
def print_success(text: str):
|
||||||
|
print(f"{Colors.GREEN}✓{Colors.END} {text}")
|
||||||
|
|
||||||
|
def print_warning(text: str):
|
||||||
|
print(f"{Colors.YELLOW}⚠{Colors.END} {text}")
|
||||||
|
|
||||||
|
def print_error(text: str):
|
||||||
|
print(f"{Colors.RED}✗{Colors.END} {text}")
|
||||||
|
|
||||||
|
def print_info(text: str):
|
||||||
|
print(f"{Colors.BLUE}ℹ{Colors.END} {text}")
|
||||||
|
|
||||||
|
class EntityValidator:
|
||||||
|
def __init__(self, base_path: str):
|
||||||
|
self.base_path = Path(base_path)
|
||||||
|
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.errors = []
|
||||||
|
self.warnings = []
|
||||||
|
self.entity_defs = {}
|
||||||
|
self.relationships = defaultdict(list)
|
||||||
|
|
||||||
|
def validate_json_syntax(self) -> bool:
|
||||||
|
"""Validiere JSON-Syntax aller Dateien im custom-Verzeichnis."""
|
||||||
|
print_header("1. JSON-SYNTAX VALIDIERUNG")
|
||||||
|
|
||||||
|
json_files = list(self.custom_path.rglob("*.json"))
|
||||||
|
if not json_files:
|
||||||
|
print_warning("Keine JSON-Dateien gefunden")
|
||||||
|
return True
|
||||||
|
|
||||||
|
invalid_files = []
|
||||||
|
for json_file in json_files:
|
||||||
|
try:
|
||||||
|
with open(json_file, 'r', encoding='utf-8') as f:
|
||||||
|
json.load(f)
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
self.errors.append(f"JSON-Fehler in {json_file.relative_to(self.base_path)}: {e}")
|
||||||
|
invalid_files.append(str(json_file.relative_to(self.base_path)))
|
||||||
|
|
||||||
|
if invalid_files:
|
||||||
|
print_error(f"{len(invalid_files)} Datei(en) mit JSON-Fehlern gefunden:")
|
||||||
|
for f in invalid_files:
|
||||||
|
print(f" {Colors.RED}•{Colors.END} {f}")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
print_success(f"Alle {len(json_files)} JSON-Dateien sind syntaktisch korrekt")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def load_entity_defs(self):
|
||||||
|
"""Lade alle entityDefs für weitere Analysen."""
|
||||||
|
entity_defs_path = self.metadata_path / "entityDefs"
|
||||||
|
if not entity_defs_path.exists():
|
||||||
|
return
|
||||||
|
|
||||||
|
for json_file in entity_defs_path.glob("*.json"):
|
||||||
|
entity_name = json_file.stem
|
||||||
|
try:
|
||||||
|
with open(json_file, 'r', encoding='utf-8') as f:
|
||||||
|
self.entity_defs[entity_name] = json.load(f)
|
||||||
|
except Exception as e:
|
||||||
|
# Fehler wird bereits in JSON-Validierung gemeldet
|
||||||
|
pass
|
||||||
|
|
||||||
|
def validate_relationships(self) -> bool:
|
||||||
|
"""Validiere Relationship-Definitionen zwischen Entities."""
|
||||||
|
print_header("2. RELATIONSHIP-KONSISTENZ")
|
||||||
|
|
||||||
|
if not self.entity_defs:
|
||||||
|
print_warning("Keine entityDefs geladen")
|
||||||
|
return True
|
||||||
|
|
||||||
|
relationship_errors = []
|
||||||
|
checked_pairs = set()
|
||||||
|
|
||||||
|
# Links die nicht geprüft werden (Standard-EspoCRM parent-Relationships)
|
||||||
|
skip_foreign_links = {'parent', 'parents'}
|
||||||
|
|
||||||
|
for entity_name, entity_def in self.entity_defs.items():
|
||||||
|
links = entity_def.get('links', {})
|
||||||
|
|
||||||
|
for link_name, link_def in links.items():
|
||||||
|
link_type = link_def.get('type')
|
||||||
|
target_entity = link_def.get('entity')
|
||||||
|
foreign = link_def.get('foreign')
|
||||||
|
relation_name = link_def.get('relationName')
|
||||||
|
|
||||||
|
# Überspringe parent-Links (Standard Activities-Relationship)
|
||||||
|
if foreign in skip_foreign_links:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Nur hasMany und hasOne prüfen (nicht belongsTo, da das die Gegenseite ist)
|
||||||
|
if link_type in ['hasMany', 'hasOne'] and target_entity and foreign:
|
||||||
|
pair_key = tuple(sorted([f"{entity_name}.{link_name}", f"{target_entity}.{foreign}"]))
|
||||||
|
if pair_key in checked_pairs:
|
||||||
|
continue
|
||||||
|
checked_pairs.add(pair_key)
|
||||||
|
|
||||||
|
# Prüfe ob Ziel-Entity existiert
|
||||||
|
if target_entity not in self.entity_defs:
|
||||||
|
relationship_errors.append(
|
||||||
|
f"{entity_name}.{link_name}: Ziel-Entity '{target_entity}' existiert nicht"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
target_links = self.entity_defs[target_entity].get('links', {})
|
||||||
|
|
||||||
|
# Prüfe ob foreign Link existiert
|
||||||
|
if foreign not in target_links:
|
||||||
|
relationship_errors.append(
|
||||||
|
f"{entity_name}.{link_name} → {target_entity}: "
|
||||||
|
f"Foreign link '{foreign}' fehlt in {target_entity}"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
foreign_def = target_links[foreign]
|
||||||
|
foreign_foreign = foreign_def.get('foreign')
|
||||||
|
foreign_relation_name = foreign_def.get('relationName')
|
||||||
|
|
||||||
|
# Prüfe ob foreign.foreign zurück zeigt
|
||||||
|
if foreign_foreign != link_name:
|
||||||
|
relationship_errors.append(
|
||||||
|
f"{entity_name}.{link_name} ↔ {target_entity}.{foreign}: "
|
||||||
|
f"Foreign zeigt auf '{foreign_foreign}' statt auf '{link_name}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Prüfe ob relationName übereinstimmt (falls beide definiert)
|
||||||
|
if relation_name and foreign_relation_name and relation_name != foreign_relation_name:
|
||||||
|
relationship_errors.append(
|
||||||
|
f"{entity_name}.{link_name} ↔ {target_entity}.{foreign}: "
|
||||||
|
f"relationName unterschiedlich ('{relation_name}' vs '{foreign_relation_name}')"
|
||||||
|
)
|
||||||
|
|
||||||
|
if relationship_errors:
|
||||||
|
print_error(f"{len(relationship_errors)} Relationship-Fehler gefunden:")
|
||||||
|
for err in relationship_errors:
|
||||||
|
print(f" {Colors.RED}•{Colors.END} {err}")
|
||||||
|
self.errors.extend(relationship_errors)
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
print_success(f"{len(checked_pairs)} Relationships geprüft - alle konsistent")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def validate_formula_placement(self) -> bool:
|
||||||
|
"""Prüfe ob Formula-Scripts korrekt in /formula/ statt /entityDefs/ platziert sind."""
|
||||||
|
print_header("3. FORMULA-SCRIPT PLATZIERUNG")
|
||||||
|
|
||||||
|
misplaced_formulas = []
|
||||||
|
|
||||||
|
# Prüfe entityDefs auf formula-Definitionen (sollte nicht da sein)
|
||||||
|
for entity_name, entity_def in self.entity_defs.items():
|
||||||
|
if 'formula' in entity_def:
|
||||||
|
misplaced_formulas.append(
|
||||||
|
f"entityDefs/{entity_name}.json enthält 'formula' - "
|
||||||
|
f"sollte in formula/{entity_name}.json sein"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Prüfe ob formula-Dateien existieren und valide sind
|
||||||
|
formula_path = self.metadata_path / "formula"
|
||||||
|
formula_count = 0
|
||||||
|
if formula_path.exists():
|
||||||
|
for formula_file in formula_path.glob("*.json"):
|
||||||
|
formula_count += 1
|
||||||
|
try:
|
||||||
|
with open(formula_file, 'r', encoding='utf-8') as f:
|
||||||
|
formula_def = json.load(f)
|
||||||
|
# Prüfe auf leere oder null Scripts
|
||||||
|
for key, value in formula_def.items():
|
||||||
|
if value == "" or value is None:
|
||||||
|
self.warnings.append(
|
||||||
|
f"formula/{formula_file.name}: '{key}' ist leer oder null"
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass # JSON-Fehler bereits gemeldet
|
||||||
|
|
||||||
|
if misplaced_formulas:
|
||||||
|
print_error(f"{len(misplaced_formulas)} Formula-Platzierungsfehler:")
|
||||||
|
for err in misplaced_formulas:
|
||||||
|
print(f" {Colors.RED}•{Colors.END} {err}")
|
||||||
|
self.errors.extend(misplaced_formulas)
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
print_success(f"{formula_count} Formula-Definitionen korrekt platziert")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def validate_i18n_completeness(self) -> bool:
|
||||||
|
"""Prüfe i18n-Definitionen auf Vollständigkeit."""
|
||||||
|
print_header("4. i18n-VOLLSTÄNDIGKEIT")
|
||||||
|
|
||||||
|
if not self.entity_defs:
|
||||||
|
print_warning("Keine entityDefs zum Prüfen")
|
||||||
|
return True
|
||||||
|
|
||||||
|
missing_i18n = []
|
||||||
|
incomplete_i18n = []
|
||||||
|
|
||||||
|
languages = ['de_DE', 'en_US']
|
||||||
|
custom_entities = [name for name in self.entity_defs.keys()
|
||||||
|
if name.startswith('C') or name.startswith('CVmh')]
|
||||||
|
|
||||||
|
for entity_name in custom_entities:
|
||||||
|
entity_def = self.entity_defs[entity_name]
|
||||||
|
links = entity_def.get('links', {})
|
||||||
|
|
||||||
|
# Finde alle hasMany/hasOne Links die übersetzt werden sollten
|
||||||
|
custom_links = []
|
||||||
|
for link_name, link_def in links.items():
|
||||||
|
link_type = link_def.get('type')
|
||||||
|
if link_type in ['hasMany', 'hasOne']:
|
||||||
|
# Überspringe System-Links
|
||||||
|
if link_name not in ['createdBy', 'modifiedBy', 'assignedUser', 'teams']:
|
||||||
|
custom_links.append(link_name)
|
||||||
|
|
||||||
|
if not custom_links:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for lang in languages:
|
||||||
|
i18n_file = self.i18n_path / lang / f"{entity_name}.json"
|
||||||
|
|
||||||
|
if not i18n_file.exists():
|
||||||
|
missing_i18n.append(f"{entity_name}: {lang} fehlt komplett")
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(i18n_file, 'r', encoding='utf-8') as f:
|
||||||
|
i18n_def = json.load(f)
|
||||||
|
links_i18n = i18n_def.get('links', {})
|
||||||
|
|
||||||
|
# Prüfe ob alle custom Links übersetzt sind
|
||||||
|
for link_name in custom_links:
|
||||||
|
if link_name not in links_i18n:
|
||||||
|
incomplete_i18n.append(
|
||||||
|
f"{entity_name} ({lang}): Link '{link_name}' fehlt in i18n"
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass # JSON-Fehler bereits gemeldet
|
||||||
|
|
||||||
|
total_issues = len(missing_i18n) + len(incomplete_i18n)
|
||||||
|
|
||||||
|
if missing_i18n:
|
||||||
|
print_error(f"{len(missing_i18n)} komplett fehlende i18n-Dateien:")
|
||||||
|
for err in missing_i18n[:10]: # Max 10 anzeigen
|
||||||
|
print(f" {Colors.RED}•{Colors.END} {err}")
|
||||||
|
if len(missing_i18n) > 10:
|
||||||
|
print(f" {Colors.RED}...{Colors.END} und {len(missing_i18n) - 10} weitere")
|
||||||
|
|
||||||
|
if incomplete_i18n:
|
||||||
|
print_warning(f"{len(incomplete_i18n)} unvollständige i18n-Definitionen:")
|
||||||
|
for err in incomplete_i18n[:10]: # Max 10 anzeigen
|
||||||
|
print(f" {Colors.YELLOW}•{Colors.END} {err}")
|
||||||
|
if len(incomplete_i18n) > 10:
|
||||||
|
print(f" {Colors.YELLOW}...{Colors.END} und {len(incomplete_i18n) - 10} weitere")
|
||||||
|
|
||||||
|
if not missing_i18n and not incomplete_i18n:
|
||||||
|
print_success(f"i18n für {len(custom_entities)} Custom-Entities vollständig")
|
||||||
|
|
||||||
|
# i18n-Fehler sind nur Warnungen, kein Abbruch
|
||||||
|
self.warnings.extend(missing_i18n + incomplete_i18n)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def validate_layout_structure(self) -> bool:
|
||||||
|
"""Prüfe Layout-Dateien auf häufige Fehler."""
|
||||||
|
print_header("5. LAYOUT-STRUKTUR VALIDIERUNG")
|
||||||
|
|
||||||
|
layouts_path = self.metadata_path / "clientDefs"
|
||||||
|
if not layouts_path.exists():
|
||||||
|
print_warning("Keine clientDefs gefunden")
|
||||||
|
return True
|
||||||
|
|
||||||
|
layout_errors = []
|
||||||
|
checked_layouts = 0
|
||||||
|
|
||||||
|
for client_def_file in layouts_path.glob("*.json"):
|
||||||
|
try:
|
||||||
|
with open(client_def_file, 'r', encoding='utf-8') as f:
|
||||||
|
client_def = json.load(f)
|
||||||
|
|
||||||
|
# Prüfe auf häufige Layout-Fehler in bottomPanels
|
||||||
|
bottom_panels = client_def.get('bottomPanelsDetail', {})
|
||||||
|
for panel_key, panel_def in bottom_panels.items():
|
||||||
|
checked_layouts += 1
|
||||||
|
|
||||||
|
# Prüfe auf unnötige false-Elemente
|
||||||
|
if isinstance(panel_def, dict):
|
||||||
|
for key, value in panel_def.items():
|
||||||
|
if value is False and key not in ['disabled', 'sticked']:
|
||||||
|
layout_errors.append(
|
||||||
|
f"{client_def_file.stem}: bottomPanelsDetail.{panel_key}.{key} "
|
||||||
|
f"sollte nicht 'false' sein"
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass # JSON-Fehler bereits gemeldet
|
||||||
|
|
||||||
|
if layout_errors:
|
||||||
|
print_warning(f"{len(layout_errors)} Layout-Strukturprobleme:")
|
||||||
|
for err in layout_errors[:5]:
|
||||||
|
print(f" {Colors.YELLOW}•{Colors.END} {err}")
|
||||||
|
if len(layout_errors) > 5:
|
||||||
|
print(f" {Colors.YELLOW}...{Colors.END} und {len(layout_errors) - 5} weitere")
|
||||||
|
self.warnings.extend(layout_errors)
|
||||||
|
else:
|
||||||
|
print_success(f"{checked_layouts} Layout-Definitionen geprüft")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def check_file_permissions(self) -> bool:
|
||||||
|
"""Prüfe Dateirechte im custom-Verzeichnis."""
|
||||||
|
print_header("6. DATEIRECHTE-PRÜFUNG")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Prüfe ob Dateien von www-data gehören
|
||||||
|
result = subprocess.run(
|
||||||
|
['find', str(self.custom_path), '!', '-user', 'www-data', '-o', '!', '-group', 'www-data'],
|
||||||
|
capture_output=True,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
|
||||||
|
wrong_owner_files = [line for line in result.stdout.strip().split('\n') if line]
|
||||||
|
|
||||||
|
if wrong_owner_files:
|
||||||
|
print_warning(f"{len(wrong_owner_files)} Dateien mit falschen Rechten gefunden")
|
||||||
|
print_info("Versuche automatische Korrektur...")
|
||||||
|
|
||||||
|
# Versuche Rechte zu korrigieren
|
||||||
|
try:
|
||||||
|
subprocess.run(
|
||||||
|
['sudo', 'chown', '-R', 'www-data:www-data', str(self.custom_path)],
|
||||||
|
check=True,
|
||||||
|
capture_output=True
|
||||||
|
)
|
||||||
|
subprocess.run(
|
||||||
|
['sudo', 'find', str(self.custom_path), '-type', 'f', '-exec', 'chmod', '664', '{}', ';'],
|
||||||
|
check=True,
|
||||||
|
capture_output=True
|
||||||
|
)
|
||||||
|
subprocess.run(
|
||||||
|
['sudo', 'find', str(self.custom_path), '-type', 'd', '-exec', 'chmod', '775', '{}', ';'],
|
||||||
|
check=True,
|
||||||
|
capture_output=True
|
||||||
|
)
|
||||||
|
print_success("Dateirechte korrigiert")
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
print_warning("Konnte Dateirechte nicht automatisch korrigieren (sudo erforderlich)")
|
||||||
|
else:
|
||||||
|
print_success("Alle Dateirechte korrekt (www-data:www-data)")
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print_warning(f"Konnte Dateirechte nicht prüfen: {e}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def run_rebuild(self) -> bool:
|
||||||
|
"""Führe den EspoCRM Rebuild aus."""
|
||||||
|
print_header("7. ESPOCRM REBUILD")
|
||||||
|
|
||||||
|
rebuild_script = self.base_path / "rebuild.php"
|
||||||
|
if not rebuild_script.exists():
|
||||||
|
print_error(f"rebuild.php nicht gefunden in {self.base_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
print_info("Starte Rebuild (kann 10-30 Sekunden dauern)...")
|
||||||
|
result = subprocess.run(
|
||||||
|
['php', str(rebuild_script)],
|
||||||
|
cwd=str(self.base_path),
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=60
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
print_success("Rebuild erfolgreich abgeschlossen")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print_error("Rebuild fehlgeschlagen:")
|
||||||
|
if result.stderr:
|
||||||
|
print(f"\n{result.stderr}")
|
||||||
|
return False
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
print_error("Rebuild-Timeout (>60 Sekunden)")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print_error(f"Rebuild-Fehler: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def print_summary(self):
|
||||||
|
"""Drucke Zusammenfassung aller Ergebnisse."""
|
||||||
|
print_header("ZUSAMMENFASSUNG")
|
||||||
|
|
||||||
|
if self.errors:
|
||||||
|
print(f"\n{Colors.RED}{Colors.BOLD}FEHLER: {len(self.errors)}{Colors.END}")
|
||||||
|
for err in self.errors:
|
||||||
|
print(f" {Colors.RED}✗{Colors.END} {err}")
|
||||||
|
|
||||||
|
if self.warnings:
|
||||||
|
print(f"\n{Colors.YELLOW}{Colors.BOLD}WARNUNGEN: {len(self.warnings)}{Colors.END}")
|
||||||
|
for warn in self.warnings[:10]:
|
||||||
|
print(f" {Colors.YELLOW}⚠{Colors.END} {warn}")
|
||||||
|
if len(self.warnings) > 10:
|
||||||
|
print(f" {Colors.YELLOW}...{Colors.END} und {len(self.warnings) - 10} weitere Warnungen")
|
||||||
|
|
||||||
|
if not self.errors and not self.warnings:
|
||||||
|
print(f"\n{Colors.GREEN}{Colors.BOLD}✓ ALLE PRÜFUNGEN BESTANDEN{Colors.END}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
def validate_all(self) -> bool:
|
||||||
|
"""Führe alle Validierungen durch."""
|
||||||
|
all_valid = True
|
||||||
|
|
||||||
|
# 1. JSON-Syntax (kritisch)
|
||||||
|
if not self.validate_json_syntax():
|
||||||
|
all_valid = False
|
||||||
|
print_error("\nAbbruch: JSON-Syntax-Fehler müssen behoben werden!\n")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Lade entityDefs für weitere Checks
|
||||||
|
self.load_entity_defs()
|
||||||
|
|
||||||
|
# 2. Relationships (kritisch)
|
||||||
|
if not self.validate_relationships():
|
||||||
|
all_valid = False
|
||||||
|
|
||||||
|
# 3. Formula-Platzierung (kritisch)
|
||||||
|
if not self.validate_formula_placement():
|
||||||
|
all_valid = False
|
||||||
|
|
||||||
|
# 4. i18n-Vollständigkeit (nur Warnung)
|
||||||
|
self.validate_i18n_completeness()
|
||||||
|
|
||||||
|
# 5. Layout-Struktur (nur Warnung)
|
||||||
|
self.validate_layout_structure()
|
||||||
|
|
||||||
|
# 6. Dateirechte (nicht kritisch für Rebuild)
|
||||||
|
self.check_file_permissions()
|
||||||
|
|
||||||
|
return all_valid
|
||||||
|
|
||||||
|
def main():
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='EspoCRM Custom Entity Validator & Rebuild Tool'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--dry-run',
|
||||||
|
action='store_true',
|
||||||
|
help='Nur Validierungen durchführen, kein Rebuild'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--no-rebuild',
|
||||||
|
action='store_true',
|
||||||
|
help='Synonym für --dry-run'
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
dry_run = args.dry_run or args.no_rebuild
|
||||||
|
|
||||||
|
# Finde EspoCRM Root-Verzeichnis
|
||||||
|
script_dir = Path(__file__).parent.parent.parent
|
||||||
|
|
||||||
|
if not (script_dir / "rebuild.php").exists():
|
||||||
|
print_error("Fehler: Nicht im EspoCRM-Root-Verzeichnis!")
|
||||||
|
print_info(f"Aktueller Pfad: {script_dir}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(f"{Colors.BOLD}EspoCRM Custom Entity Validator & Rebuild Tool{Colors.END}")
|
||||||
|
print(f"Arbeitsverzeichnis: {script_dir}")
|
||||||
|
if dry_run:
|
||||||
|
print(f"{Colors.YELLOW}Modus: DRY-RUN (kein Rebuild){Colors.END}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
validator = EntityValidator(str(script_dir))
|
||||||
|
|
||||||
|
# Validierungen durchführen
|
||||||
|
all_valid = validator.validate_all()
|
||||||
|
|
||||||
|
# Zusammenfassung drucken
|
||||||
|
validator.print_summary()
|
||||||
|
|
||||||
|
# Entscheidung über Rebuild
|
||||||
|
if not all_valid:
|
||||||
|
print_error("REBUILD ABGEBROCHEN: Kritische Fehler müssen behoben werden!")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
print_info("Dry-Run Modus: Rebuild übersprungen")
|
||||||
|
print(f"\n{Colors.GREEN}{Colors.BOLD}✓ VALIDIERUNGEN ABGESCHLOSSEN{Colors.END}\n")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
if validator.warnings:
|
||||||
|
print_warning(
|
||||||
|
f"Es gibt {len(validator.warnings)} Warnungen, aber keine kritischen Fehler."
|
||||||
|
)
|
||||||
|
print_info("Rebuild wird trotzdem durchgeführt...\n")
|
||||||
|
|
||||||
|
# Rebuild ausführen
|
||||||
|
if validator.run_rebuild():
|
||||||
|
print(f"\n{Colors.GREEN}{Colors.BOLD}✓ ERFOLGREICH ABGESCHLOSSEN{Colors.END}\n")
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
print(f"\n{Colors.RED}{Colors.BOLD}✗ REBUILD FEHLGESCHLAGEN{Colors.END}\n")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user