# Plugin-System für lizenzierbare Module ## Übersicht Das myCRM Plugin-System ermöglicht die Installation und Verwaltung von optionalen, lizenzierbaren Modulen als separate Composer-Packages. Module können unabhängig vom Core entwickelt, installiert und lizenziert werden. ## Architektur-Prinzipien ### 1. **Lose Kopplung** - Core kennt nur Interfaces, keine konkreten Module - Module registrieren sich selbst über Symfony's Service-Tagging - Keine Hard-Dependencies im Core-Code ### 2. **Lizenzbasierte Aktivierung** - Module werden nur gebootet, wenn eine gültige Lizenz vorhanden ist - Online-Validierung mit Offline-Fallback (Grace Period: 7 Tage) - Caching zur Performance-Optimierung ### 3. **Plugin als Symfony Bundle** - Jedes Modul ist ein vollständiges Symfony Bundle - Eigene Entities, Controller, Services, Routes - Optionale Frontend-Komponenten (Vue.js) ### 4. **Composer-basierte Installation** - Module sind private Composer-Packages - `composer require mycrm/modul-name` installiert das Modul - Automatische Service-Registrierung über Symfony Flex ## Komponenten ### Core-Komponenten (im Hauptprojekt) ``` src/Plugin/ ├── ModulePluginInterface.php # Interface für alle Module ├── LicenseValidatorInterface.php # Interface für Lizenzvalidierung ├── ModuleRegistry.php # Registry für alle installierten Module src/Service/ └── LicenseValidator.php # Standard-Implementierung src/EventListener/ └── ModuleBootListener.php # Bootet Module beim Request src/Command/ ├── ModuleListCommand.php # CLI: Module auflisten └── ModuleLicenseCommand.php # CLI: Lizenzen verwalten src/Controller/Api/ └── ModuleManagementController.php # API für Module-Verwaltung config/ └── services_plugin.yaml # Service-Konfiguration ``` ### Modul-Struktur (externes Package) ``` mycrm-modulname/ ├── composer.json ├── src/ │ ├── ModulnameModulePlugin.php # Implementiert ModulePluginInterface │ ├── ModulnameBundle.php # Symfony Bundle │ ├── DependencyInjection/ │ │ ├── ModulnameExtension.php │ │ └── Configuration.php │ ├── Entity/ # Modul-spezifische Entities │ ├── Controller/ # API-Controller │ ├── Service/ # Business Logic │ ├── Security/Voter/ # Permission-Voter │ └── EventListener/ ├── config/ │ ├── services.yaml │ ├── routes.yaml │ └── packages/ │ └── doctrine.yaml ├── assets/ # Frontend (optional) │ └── js/ │ └── components/ └── migrations/ # Modul-spezifische Migrations ``` ## Installation und Konfiguration ### 1. Core-System vorbereiten Die Core-Komponenten sind bereits implementiert. Konfiguration aktivieren: ```bash # services_plugin.yaml in services.yaml importieren # config/services.yaml imports: - { resource: services_plugin.yaml } ``` Environment-Variablen setzen: ```bash # .env.local LICENSE_SERVER_URL=https://license.mycrm.local INSTANCE_ID=unique-instance-identifier-here # Beispiel-Lizenz für ein Modul LICENSE_BILLING=your-license-key-here ``` ### 2. Modul installieren ```bash # Private Repository konfigurieren composer config repositories.mycrm-billing vcs https://github.com/your-org/mycrm-billing-module # Modul installieren composer require mycrm/billing-module # Bundle registrieren (falls nicht automatisch via Flex) # config/bundles.php return [ // ... MyCRM\BillingModule\BillingBundle::class => ['all' => true], ]; # Migrations ausführen php bin/console doctrine:migrations:migrate ``` ### 3. Lizenz registrieren **Via CLI:** ```bash # Interaktiv php bin/console app:module:license billing # Direkt mit Key php bin/console app:module:license billing YOUR_LICENSE_KEY # Lizenz validieren php bin/console app:module:license billing --validate # Lizenz widerrufen php bin/console app:module:license billing --revoke # Alle Module auflisten php bin/console app:module:list ``` **Via API (für Admin-UI):** ```bash # Module auflisten curl -X GET http://localhost:8000/api/modules \ -H "Authorization: Bearer YOUR_TOKEN" # Lizenz registrieren curl -X POST http://localhost:8000/api/modules/billing/license \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN" \ -d '{"license_key": "YOUR_LICENSE_KEY"}' # Lizenz widerrufen curl -X DELETE http://localhost:8000/api/modules/billing/license \ -H "Authorization: Bearer YOUR_TOKEN" ``` **Via Frontend (Vue.js):** Die Komponente `ModuleManagement.vue` bietet eine Admin-Oberfläche zur Verwaltung: ```javascript // In router.js hinzufügen { path: '/admin/modules', name: 'module-management', component: () => import('@/views/ModuleManagement.vue'), meta: { requiresAuth: true, requiresRole: 'ROLE_ADMIN' } } ``` ### 4. Cache leeren und testen ```bash php bin/console cache:clear php bin/console app:module:list ``` ## Modul entwickeln ### Schritt 1: Projekt-Struktur erstellen ```bash mkdir mycrm-mymodule cd mycrm-mymodule composer init ``` ### Schritt 2: composer.json konfigurieren ```json { "name": "mycrm/mymodule", "description": "Mein Custom CRM-Modul", "type": "symfony-bundle", "require": { "php": ">=8.3", "symfony/framework-bundle": "^7.1" }, "autoload": { "psr-4": { "MyCRM\\MyModule\\": "src/" } } } ``` ### Schritt 3: Plugin-Klasse implementieren ```php getLicenseInfo(); return $info['valid']; } public function getLicenseInfo(): array { return $this->licenseValidator->validate($this->getIdentifier()); } public function boot(): void { if (!$this->isLicensed()) { throw new \RuntimeException('Modul nicht lizenziert'); } // Modul-spezifische Boot-Logik } public function getPermissionModules(): array { return ['mymodule']; } public function canInstall(): array { return ['success' => true, 'errors' => []]; } } ``` ### Schritt 4: Bundle-Klasse erstellen ```php moduleVoter->vote($token, $subject, [$attribute]) === Voter::ACCESS_GRANTED; } } ``` ## Lizenzserver-Integration ### Lizenzserver-Endpunkt Der Lizenzserver muss folgenden Endpunkt bereitstellen: ``` POST /api/validate Content-Type: application/json { "license_key": "YOUR-LICENSE-KEY", "module": "billing", "instance_id": "unique-instance-id", "version": "1.0.0" } Response (200 OK): { "valid": true, "expires_at": "2026-12-31T23:59:59Z", "licensed_to": "Firma GmbH", "features": ["feature1", "feature2"], "message": "Lizenz gültig" } Response (400/403): { "valid": false, "message": "Lizenz abgelaufen / ungültig / etc." } ``` ### Lizenzschlüssel-Format (JWT) Empfohlenes Format als JWT mit folgenden Claims: ```json { "sub": "billing", "customer_id": "CUST-12345", "licensed_to": "Firma GmbH", "expires_at": "2026-12-31T23:59:59Z", "features": ["invoicing", "recurring_billing"], "iat": 1704278400, "exp": 1767350399 } ``` ## Permission-System Integration ### Automatische Modul-Registrierung Module, die `getPermissionModules()` implementieren, werden automatisch im Permission-System verfügbar: ```php public function getPermissionModules(): array { return [ 'billing', // Hauptmodul 'invoices', // Sub-Modul 'payments', // Weiteres Sub-Modul ]; } ``` Diese Module können dann in der Role-Verwaltung zugewiesen werden. ### Permission-Checks in Modul-Code ```php // In Controller $this->denyAccessUnlessGranted('VIEW', 'billing'); // In Voter $user->hasModulePermission('billing', 'edit'); // In API Platform #[ApiResource( security: "is_granted('VIEW', 'billing')" )] ``` ## Frontend-Integration ### Modul-Status im Frontend verfügbar machen ```javascript // In Symfony Twig-Template (oder API-Endpunkt) ``` ### Bedingte Route-Registrierung ```javascript // router.js import { moduleRegistry } from './moduleRegistry' const routes = [ // Core-Routes... ] // Modul-Routes nur hinzufügen, wenn aktiv if (moduleRegistry.isActive('billing')) { routes.push({ path: '/billing', component: () => import('@/views/BillingDashboard.vue') }) } ``` ### Modul-Komponenten lazy-loaden ```javascript // In App.vue oder Layout import { computed } from 'vue' import { useModules } from '@/composables/useModules' const { isModuleActive } = useModules() const showBillingMenu = computed(() => isModuleActive('billing')) ``` ## Testing ### Unit-Tests für Plugin ```php createMock(LicenseValidatorInterface::class); $plugin = new MyModulePlugin($licenseValidator); $this->assertSame('mymodule', $plugin->getIdentifier()); } public function testIsLicensedReturnsTrueWithValidLicense(): void { $licenseValidator = $this->createMock(LicenseValidatorInterface::class); $licenseValidator->method('validate') ->willReturn(['valid' => true]); $plugin = new MyModulePlugin($licenseValidator); $this->assertTrue($plugin->isLicensed()); } } ``` ## Troubleshooting ### Modul wird nicht erkannt 1. Bundle in `config/bundles.php` registriert? 2. Composer-Autoloading aktualisiert? (`composer dump-autoload`) 3. Cache geleert? (`php bin/console cache:clear`) 4. Service-Tag `app.module_plugin` gesetzt? ### Lizenz wird nicht validiert 1. Environment-Variablen korrekt gesetzt? 2. Lizenzserver erreichbar? (Netzwerk, Firewall) 3. Cache-Problem? Cache löschen und neu validieren 4. Logs prüfen: `tail -f var/log/dev.log | grep -i license` ### Modul bootet nicht 1. Lizenz gültig? (`php bin/console app:module:license mymodule --validate`) 2. Abhängigkeiten erfüllt? (`php bin/console app:module:list`) 3. Doctrine-Mappings korrekt? (`php bin/console doctrine:schema:validate`) 4. Fehler in `boot()`-Methode? Logs prüfen ## Best Practices ### 1. Versionierung - Semantic Versioning verwenden (Major.Minor.Patch) - Breaking Changes nur in Major-Versionen - Migrations mit Rollback-Fähigkeit ### 2. Fehlerbehandlung - Keine Exceptions in `isLicensed()` werfen - Detaillierte Fehlermeldungen in `getLicenseInfo()` - Graceful Degradation bei Netzwerkfehlern ### 3. Performance - Lizenz-Status cachen (bereits implementiert) - Lazy-Loading für Frontend-Komponenten - Doctrine-Queries optimieren ### 4. Sicherheit - Nie Lizenzschlüssel im Frontend exponieren - API-Endpunkte mit `ROLE_ADMIN` schützen - Input-Validierung in allen Controllern ### 5. Dokumentation - README.md mit Installation-Guide - API-Dokumentation (OpenAPI/Swagger) - Changelog pflegen ## Beispiel: Komplettes Mini-Modul Ein vollständiges Mini-Beispiel findet sich in: - `/docs/example-module/` - Backend-Code - `/docs/EXAMPLE_MODULE_STRUCTURE.md` - Struktur-Übersicht ## Support und Weiterentwicklung ### Geplante Features - [ ] Multi-Tenancy-Support (verschiedene Instanzen, eine Lizenz) - [ ] Offline-Lizenzierung (Air-Gapped Systeme) - [ ] Automatische Updates über Composer - [ ] Modul-Marketplace Integration - [ ] Rollback-Mechanismus bei fehlgeschlagenen Installations ### Beitragen Weitere Module können nach diesem Pattern entwickelt werden. Core-Interfaces sind stabil und abwärtskompatibel.