Compare commits

...

2 Commits

Author SHA1 Message Date
6246f29135 refactor: Remove test module references from composer and configuration files 2025-12-14 18:07:53 +01:00
00b42e4a4c feat: Add module removal command (app:module:remove)
Implementiert einen sicheren Weg zum Entfernen von Modulen aus dem System:

Features:
- Zeigt Modul-Informationen (Plugin + Datenbank) an
- Widerruft Lizenz automatisch (optional mit --keep-license)
- Löscht Datenbank-Eintrag im Permission-System (optional mit --keep-db-entry)
- Warnt vor verknüpften Permissions
- Zeigt nächste manuelle Schritte (Composer, Bundle, Migrations, Cache)
- Bestätigung erforderlich (überspringbar mit --force)

Dokumentation:
- Command-Hilfe in src/Command/ModuleRemoveCommand.php
- Benutzer-Anleitung in docs/PLUGIN_QUICKSTART.md
- Entwickler-Referenz in CLAUDE.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-14 18:04:10 +01:00
7 changed files with 389 additions and 83 deletions

View File

@ -80,6 +80,24 @@ php bin/console cache:clear
APP_ENV=prod php bin/console cache:warmup
```
### Module Management (Plugin System)
```bash
# List all installed modules with license status
php bin/console app:module:list
# Register/manage module license
php bin/console app:module:license billing # Interactive
php bin/console app:module:license billing <license-key> # Direct
php bin/console app:module:license billing --validate # Validate
php bin/console app:module:license billing --revoke # Revoke
# Remove a module safely
php bin/console app:module:remove billing # Interactive
php bin/console app:module:remove billing --force # No confirmation
php bin/console app:module:remove billing --keep-license # Keep license
php bin/console app:module:remove billing --keep-db-entry # Keep DB entry
```
## Architecture Overview
### Security System (6 Layers)

View File

@ -16,7 +16,6 @@
"knpuniversity/oauth2-client-bundle": "*",
"league/oauth2-client": "*",
"mycrm/billing-module": "^1.0",
"mycrm/test-module": "*",
"nelmio/cors-bundle": "^2.6",
"phpdocumentor/reflection-docblock": "^5.6",
"phpstan/phpdoc-parser": "^2.3",

120
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "ac7d8a38f7c8dd8a8f2760177304dcb4",
"content-hash": "f89ce01cc8bcb7810af77472d2437dd0",
"packages": [
{
"name": "api-platform/core",
@ -3259,42 +3259,6 @@
"description": "Ausgangsrechnungsverwaltung für myCRM",
"time": "2025-12-05T10:24:44+00:00"
},
{
"name": "mycrm/test-module",
"version": "v1.0.1",
"source": {
"type": "git",
"url": "https://git.osdata-home.de/mycrm/mycrm-test-module",
"reference": "0630a840ffdca475208b51f7807196468e44c27c"
},
"require": {
"php": ">=8.2",
"symfony/framework-bundle": "^7.0"
},
"type": "symfony-bundle",
"extra": {
"branch-alias": {
"dev-main": "1.0-dev",
"stable": "v1.0.0"
}
},
"autoload": {
"psr-4": {
"MyCRM\\TestModule\\": "src/"
}
},
"license": [
"proprietary"
],
"authors": [
{
"name": "Your Name",
"email": "your.email@example.com"
}
],
"description": "Test Module for myCRM - Demonstrates the plugin system",
"time": "2025-12-03T15:57:07+00:00"
},
{
"name": "nelmio/cors-bundle",
"version": "2.6.0",
@ -6578,16 +6542,16 @@
},
{
"name": "symfony/monolog-bundle",
"version": "v3.11.0",
"version": "v3.11.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/monolog-bundle.git",
"reference": "e12eb92655b234cd50c21cda648088847a7ec777"
"reference": "0e675a6e08f791ef960dc9c7e392787111a3f0c1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/e12eb92655b234cd50c21cda648088847a7ec777",
"reference": "e12eb92655b234cd50c21cda648088847a7ec777",
"url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/0e675a6e08f791ef960dc9c7e392787111a3f0c1",
"reference": "0e675a6e08f791ef960dc9c7e392787111a3f0c1",
"shasum": ""
},
"require": {
@ -6634,7 +6598,7 @@
],
"support": {
"issues": "https://github.com/symfony/monolog-bundle/issues",
"source": "https://github.com/symfony/monolog-bundle/tree/v3.11.0"
"source": "https://github.com/symfony/monolog-bundle/tree/v3.11.1"
},
"funding": [
{
@ -6654,7 +6618,7 @@
"type": "tidelift"
}
],
"time": "2025-11-27T09:16:19+00:00"
"time": "2025-12-08T07:58:26+00:00"
},
{
"name": "symfony/notifier",
@ -9797,16 +9761,16 @@
},
{
"name": "twig/extra-bundle",
"version": "v3.22.1",
"version": "v3.22.2",
"source": {
"type": "git",
"url": "https://github.com/twigphp/twig-extra-bundle.git",
"reference": "b6534bc925bec930004facca92fccebd0c809247"
"reference": "09de9be7f6c0d19ede7b5a1dbfcfb2e9d1e0ea9e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/b6534bc925bec930004facca92fccebd0c809247",
"reference": "b6534bc925bec930004facca92fccebd0c809247",
"url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/09de9be7f6c0d19ede7b5a1dbfcfb2e9d1e0ea9e",
"reference": "09de9be7f6c0d19ede7b5a1dbfcfb2e9d1e0ea9e",
"shasum": ""
},
"require": {
@ -9855,7 +9819,7 @@
"twig"
],
"support": {
"source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.22.1"
"source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.22.2"
},
"funding": [
{
@ -9867,7 +9831,7 @@
"type": "tidelift"
}
],
"time": "2025-11-02T11:00:49+00:00"
"time": "2025-12-05T08:51:53+00:00"
},
{
"name": "twig/twig",
@ -10362,16 +10326,16 @@
},
{
"name": "nikic/php-parser",
"version": "v5.6.2",
"version": "v5.7.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "3a454ca033b9e06b63282ce19562e892747449bb"
"reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb",
"reference": "3a454ca033b9e06b63282ce19562e892747449bb",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82",
"reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82",
"shasum": ""
},
"require": {
@ -10414,9 +10378,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2"
"source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0"
},
"time": "2025-10-21T19:32:17+00:00"
"time": "2025-12-06T11:56:16+00:00"
},
{
"name": "phar-io/manifest",
@ -10538,23 +10502,23 @@
},
{
"name": "phpunit/php-code-coverage",
"version": "12.5.0",
"version": "12.5.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "bca180c050dd3ae15f87c26d25cabb34fe1a0a5a"
"reference": "c467c59a4f6e04b942be422844e7a6352fa01b57"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/bca180c050dd3ae15f87c26d25cabb34fe1a0a5a",
"reference": "bca180c050dd3ae15f87c26d25cabb34fe1a0a5a",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c467c59a4f6e04b942be422844e7a6352fa01b57",
"reference": "c467c59a4f6e04b942be422844e7a6352fa01b57",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-xmlwriter": "*",
"nikic/php-parser": "^5.6.2",
"nikic/php-parser": "^5.7.0",
"php": ">=8.3",
"phpunit/php-file-iterator": "^6.0",
"phpunit/php-text-template": "^5.0",
@ -10562,10 +10526,10 @@
"sebastian/environment": "^8.0.3",
"sebastian/lines-of-code": "^4.0",
"sebastian/version": "^6.0",
"theseer/tokenizer": "^1.3.1"
"theseer/tokenizer": "^2.0"
},
"require-dev": {
"phpunit/phpunit": "^12.4.4"
"phpunit/phpunit": "^12.5.1"
},
"suggest": {
"ext-pcov": "PHP extension that provides line coverage",
@ -10603,7 +10567,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.0"
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.1"
},
"funding": [
{
@ -10623,7 +10587,7 @@
"type": "tidelift"
}
],
"time": "2025-11-29T07:15:54+00:00"
"time": "2025-12-08T07:17:58+00:00"
},
{
"name": "phpunit/php-file-iterator",
@ -10872,16 +10836,16 @@
},
{
"name": "phpunit/phpunit",
"version": "12.5.0",
"version": "12.5.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "fef037fe50d20ce826cdbd741b7a2afcdec5f45b"
"reference": "6dc2e076d09960efbb0c1272aa9bc156fc80955e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fef037fe50d20ce826cdbd741b7a2afcdec5f45b",
"reference": "fef037fe50d20ce826cdbd741b7a2afcdec5f45b",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6dc2e076d09960efbb0c1272aa9bc156fc80955e",
"reference": "6dc2e076d09960efbb0c1272aa9bc156fc80955e",
"shasum": ""
},
"require": {
@ -10895,7 +10859,7 @@
"phar-io/manifest": "^2.0.4",
"phar-io/version": "^3.2.1",
"php": ">=8.3",
"phpunit/php-code-coverage": "^12.5.0",
"phpunit/php-code-coverage": "^12.5.1",
"phpunit/php-file-iterator": "^6.0.0",
"phpunit/php-invoker": "^6.0.0",
"phpunit/php-text-template": "^5.0.0",
@ -10949,7 +10913,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.0"
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.3"
},
"funding": [
{
@ -10973,7 +10937,7 @@
"type": "tidelift"
}
],
"time": "2025-12-05T04:59:40+00:00"
"time": "2025-12-11T08:52:59+00:00"
},
{
"name": "sebastian/cli-parser",
@ -12379,23 +12343,23 @@
},
{
"name": "theseer/tokenizer",
"version": "1.3.1",
"version": "2.0.1",
"source": {
"type": "git",
"url": "https://github.com/theseer/tokenizer.git",
"reference": "b7489ce515e168639d17feec34b8847c326b0b3c"
"reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c",
"reference": "b7489ce515e168639d17feec34b8847c326b0b3c",
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/7989e43bf381af0eac72e4f0ca5bcbfa81658be4",
"reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-tokenizer": "*",
"ext-xmlwriter": "*",
"php": "^7.2 || ^8.0"
"php": "^8.1"
},
"type": "library",
"autoload": {
@ -12417,7 +12381,7 @@
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
"support": {
"issues": "https://github.com/theseer/tokenizer/issues",
"source": "https://github.com/theseer/tokenizer/tree/1.3.1"
"source": "https://github.com/theseer/tokenizer/tree/2.0.1"
},
"funding": [
{
@ -12425,7 +12389,7 @@
"type": "github"
}
],
"time": "2025-11-17T20:03:58+00:00"
"time": "2025-12-08T11:19:18+00:00"
}
],
"aliases": [],

View File

@ -18,6 +18,5 @@ return [
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
KnpU\OAuth2ClientBundle\KnpUOAuth2ClientBundle::class => ['all' => true],
MyCRM\TestModule\TestModuleBundle::class => ['all' => true],
MyCRM\BillingModule\BillingModuleBundle::class => ['all' => true],
];

View File

@ -223,6 +223,132 @@ Navigiere zu: `http://localhost:8000/admin/modules`
---
## 🗑️ Modul entfernen
### Option A: CLI (empfohlen)
```bash
# Interaktiv mit Bestätigung
php bin/console app:module:remove billing
# Ohne Bestätigung (force)
php bin/console app:module:remove billing --force
# Lizenz behalten
php bin/console app:module:remove billing --keep-license
# Permission-Datenbank-Eintrag behalten
php bin/console app:module:remove billing --keep-db-entry
```
Der Command führt folgende Schritte durch:
1. ✓ **Modul-Informationen anzeigen** (Plugin + Datenbank)
2. ✓ **Bestätigung einholen** (außer mit `--force`)
3. ✓ **Lizenz widerrufen** (außer mit `--keep-license`)
4. ✓ **Datenbank-Eintrag löschen** (außer mit `--keep-db-entry`)
5. → **Composer-Command vorschlagen**
### Manuelle Schritte nach app:module:remove
Der Command zeigt dir die nötigen manuellen Schritte:
```bash
# 1. Composer-Package entfernen
composer remove mycrm/billing-module
# 2. Bundle aus config/bundles.php entfernen (falls vorhanden)
# Öffne config/bundles.php und entferne die Zeile:
# MyCRM\BillingModule\BillingBundle::class => ['all' => true],
# 3. Optional: Migrationen zurückrollen (falls benötigt)
php bin/console doctrine:migrations:migrate prev
# 4. Cache leeren
php bin/console cache:clear
```
### Option B: Vollständig manuell
Falls du den Command nicht nutzen möchtest:
```bash
# 1. Lizenz widerrufen
php bin/console app:module:license billing --revoke
# 2. Composer-Package entfernen
composer remove mycrm/billing-module
# 3. Bundle aus config/bundles.php entfernen (manuell editieren)
# 4. Optional: Module-Entity aus DB löschen
# Nur nötig, wenn das Modul auch im Permission-System registriert ist
# VORSICHT: Löscht auch verknüpfte RolePermissions!
# 5. Cache leeren
php bin/console cache:clear
```
### Beispiel-Ausgabe
```
Modul "billing" entfernen
=========================
Modul-Informationen
-------------------
Eigenschaft Wert
Plugin-Informationen
Name Rechnungsmodul
Version 1.0.0
Beschreibung Rechnungsverwaltung für myCRM
Lizenziert ✓ Ja
Aktiv ✓ Ja
Datenbank-Informationen (Permission-System)
Name Rechnungen
Code billing
Aktiv ✓ Ja
Permissions 3
! [NOTE] Möchten Sie dieses Modul wirklich entfernen? (yes/no) [no]:
> yes
Entferne Modul
--------------
⏳ Widerrufe Lizenz...
[OK] ✓ Lizenz erfolgreich widerrufen
⏳ Entferne Datenbank-Eintrag (Permission-System)...
! [WARNING] ACHTUNG: Dieses Modul hat 3 verknüpfte Permission(s). Diese werden ebenfalls gelöscht!
! [NOTE] Trotzdem fortfahren? (yes/no) [no]:
> yes
[OK] ✓ Datenbank-Eintrag erfolgreich entfernt
Zusammenfassung
---------------
* ✓ Lizenz widerrufen
* ✓ Datenbank-Eintrag gelöscht
Nächste Schritte (manuell)
--------------------------
* 1. Composer-Package entfernen: composer remove mycrm/billing-module
* 2. Bundle aus config/bundles.php entfernen (falls vorhanden)
* 3. Optional: Migrationen zurückrollen (falls benötigt)
php bin/console doctrine:migrations:migrate prev
* 4. Cache leeren: php bin/console cache:clear
[OK] Modul "billing" wurde vorbereitet zum Entfernen. Führen Sie die obigen Schritte manuell aus.
```
✅ **Modul sicher entfernt!**
---
## 🔧 Troubleshooting
### Problem: "Modul wird nicht erkannt"

View File

@ -0,0 +1,203 @@
<?php
namespace App\Command;
use App\Entity\Module;
use App\Plugin\LicenseValidatorInterface;
use App\Plugin\ModuleRegistry;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* CLI-Command zum sicheren Entfernen von Modulen
*/
#[AsCommand(
name: 'app:module:remove',
description: 'Entfernt ein Modul sicher aus dem System',
)]
class ModuleRemoveCommand extends Command
{
public function __construct(
private readonly ModuleRegistry $moduleRegistry,
private readonly LicenseValidatorInterface $licenseValidator,
private readonly EntityManagerInterface $entityManager
) {
parent::__construct();
}
protected function configure(): void
{
$this
->addArgument('module', InputArgument::REQUIRED, 'Modul-Identifier (z.B. "billing")')
->addOption('keep-license', null, InputOption::VALUE_NONE, 'Lizenz behalten (nicht widerrufen)')
->addOption('keep-db-entry', null, InputOption::VALUE_NONE, 'Datenbank-Eintrag behalten (Permission-System)')
->addOption('force', 'f', InputOption::VALUE_NONE, 'Keine Bestätigung erforderlich')
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$moduleIdentifier = $input->getArgument('module');
$keepLicense = $input->getOption('keep-license');
$keepDbEntry = $input->getOption('keep-db-entry');
$force = $input->getOption('force');
$io->title(sprintf('Modul "%s" entfernen', $moduleIdentifier));
// 1. Modul-Informationen sammeln
$modulePlugin = $this->moduleRegistry->getModule($moduleIdentifier);
$moduleEntity = $this->findModuleEntity($moduleIdentifier);
if (!$modulePlugin && !$moduleEntity) {
$io->error(sprintf('Modul "%s" wurde nicht gefunden (weder als Plugin noch in der Datenbank).', $moduleIdentifier));
return Command::FAILURE;
}
// 2. Informationen anzeigen
$this->displayModuleInfo($io, $modulePlugin, $moduleEntity);
// 3. Bestätigung einholen
if (!$force && !$io->confirm('Möchten Sie dieses Modul wirklich entfernen?', false)) {
$io->info('Abgebrochen.');
return Command::SUCCESS;
}
$io->section('Entferne Modul');
$steps = [];
// 4. Lizenz widerrufen
if (!$keepLicense && $modulePlugin && $modulePlugin->isLicensed()) {
$io->text('⏳ Widerrufe Lizenz...');
try {
$this->licenseValidator->revokeLicense($moduleIdentifier);
$io->success('✓ Lizenz erfolgreich widerrufen');
$steps[] = '✓ Lizenz widerrufen';
} catch (\Exception $e) {
$io->warning(sprintf('Lizenz konnte nicht widerrufen werden: %s', $e->getMessage()));
$steps[] = '⚠ Lizenz-Widerruf fehlgeschlagen';
}
} elseif ($keepLicense) {
$io->text('⏭ Lizenz wird behalten (--keep-license)');
$steps[] = '⏭ Lizenz behalten';
} else {
$io->text('⏭ Keine Lizenz vorhanden');
$steps[] = '⏭ Keine Lizenz';
}
// 5. Datenbank-Eintrag entfernen
if (!$keepDbEntry && $moduleEntity) {
$io->text('⏳ Entferne Datenbank-Eintrag (Permission-System)...');
// Warnung, wenn Permissions existieren
$permissionCount = $moduleEntity->getPermissions()->count();
if ($permissionCount > 0) {
$io->warning(sprintf(
'ACHTUNG: Dieses Modul hat %d verknüpfte Permission(s). Diese werden ebenfalls gelöscht!',
$permissionCount
));
if (!$force && !$io->confirm('Trotzdem fortfahren?', false)) {
$io->info('Abgebrochen.');
return Command::SUCCESS;
}
}
try {
$this->entityManager->remove($moduleEntity);
$this->entityManager->flush();
$io->success('✓ Datenbank-Eintrag erfolgreich entfernt');
$steps[] = '✓ Datenbank-Eintrag gelöscht';
} catch (\Exception $e) {
$io->error(sprintf('Datenbank-Eintrag konnte nicht entfernt werden: %s', $e->getMessage()));
$steps[] = '✗ Datenbank-Eintrag Fehler';
return Command::FAILURE;
}
} elseif ($keepDbEntry) {
$io->text('⏭ Datenbank-Eintrag wird behalten (--keep-db-entry)');
$steps[] = '⏭ DB-Eintrag behalten';
} else {
$io->text('⏭ Kein Datenbank-Eintrag vorhanden');
$steps[] = '⏭ Kein DB-Eintrag';
}
// 6. Nächste Schritte anzeigen
$io->section('Zusammenfassung');
$io->listing($steps);
$io->section('Nächste Schritte (manuell)');
$nextSteps = [];
// Composer-Package entfernen
if ($modulePlugin) {
$packageName = $this->guessPackageName($moduleIdentifier);
$nextSteps[] = sprintf('1. Composer-Package entfernen: <comment>composer remove %s</comment>', $packageName);
}
// Bundle aus config/bundles.php entfernen
$nextSteps[] = '2. Bundle aus <comment>config/bundles.php</comment> entfernen (falls vorhanden)';
// Migrationen
$nextSteps[] = '3. Optional: Migrationen zurückrollen (falls benötigt)';
$nextSteps[] = ' <comment>php bin/console doctrine:migrations:migrate prev</comment>';
// Cache leeren
$nextSteps[] = '4. Cache leeren: <comment>php bin/console cache:clear</comment>';
$io->listing($nextSteps);
$io->success(sprintf('Modul "%s" wurde vorbereitet zum Entfernen. Führen Sie die obigen Schritte manuell aus.', $moduleIdentifier));
return Command::SUCCESS;
}
private function displayModuleInfo(SymfonyStyle $io, $modulePlugin, ?Module $moduleEntity): void
{
$io->section('Modul-Informationen');
$rows = [];
if ($modulePlugin) {
$rows[] = ['<info>Plugin-Informationen</info>', ''];
$rows[] = [' Name', $modulePlugin->getDisplayName()];
$rows[] = [' Version', $modulePlugin->getVersion()];
$rows[] = [' Beschreibung', $modulePlugin->getDescription()];
$rows[] = [' Lizenziert', $modulePlugin->isLicensed() ? '✓ Ja' : '✗ Nein'];
$rows[] = [' Aktiv', $this->moduleRegistry->isModuleActive($modulePlugin->getIdentifier()) ? '✓ Ja' : '✗ Nein'];
}
if ($moduleEntity) {
if (!empty($rows)) {
$rows[] = ['', ''];
}
$rows[] = ['<info>Datenbank-Informationen (Permission-System)</info>', ''];
$rows[] = [' Name', $moduleEntity->getName()];
$rows[] = [' Code', $moduleEntity->getCode()];
$rows[] = [' Aktiv', $moduleEntity->isActive() ? '✓ Ja' : '✗ Nein'];
$rows[] = [' Permissions', (string)$moduleEntity->getPermissions()->count()];
}
$io->table(['Eigenschaft', 'Wert'], $rows);
}
private function findModuleEntity(string $identifier): ?Module
{
return $this->entityManager
->getRepository(Module::class)
->findOneBy(['code' => $identifier]);
}
private function guessPackageName(string $identifier): string
{
// Versuche Package-Name zu erraten (z.B. billing -> mycrm/billing-module)
return sprintf('mycrm/%s-module', $identifier);
}
}

View File

@ -90,9 +90,6 @@
"mycrm/billing-module": {
"version": "dev-main"
},
"mycrm/test-module": {
"version": "dev-main"
},
"nelmio/cors-bundle": {
"version": "2.6",
"recipe": {