generated from Nodarx/template
Merge pull request 'feat: console commands' (#9) from feat/console-commands into main
Reviewed-on: #9
This commit was merged in pull request #9.
This commit is contained in:
202
lib/Console/ConnectCommand.php
Normal file
202
lib/Console/ConnectCommand.php
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace KTXM\ProviderImap\Console;
|
||||||
|
|
||||||
|
use KTXM\ProviderImap\Providers\Provider;
|
||||||
|
use KTXM\ProviderImap\Providers\Service;
|
||||||
|
use KTXM\ProviderImap\Providers\ServiceIdentityBasic;
|
||||||
|
use KTXM\ProviderImap\Providers\ServiceLocation;
|
||||||
|
use KTXC\SessionTenant;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manual IMAP service connection wizard.
|
||||||
|
*
|
||||||
|
* Interactively prompts for all connection details (host, port, encryption,
|
||||||
|
* username, password), runs a live connection test, then optionally persists
|
||||||
|
* the service to the store.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* bin/console provider_imap_mail:service:connect
|
||||||
|
* bin/console provider_imap_mail:service:connect --tenant=t1 --user=u1
|
||||||
|
*/
|
||||||
|
#[AsCommand(
|
||||||
|
name: 'provider_imap_mail:service:connect',
|
||||||
|
description: 'Manually configure and connect an IMAP service',
|
||||||
|
)]
|
||||||
|
class ConnectCommand extends Command
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly Provider $provider,
|
||||||
|
private readonly SessionTenant $sessionTenant,
|
||||||
|
) {
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->addOption('tenant', 't', InputOption::VALUE_REQUIRED, 'Tenant ID')
|
||||||
|
->addOption('user', 'u', InputOption::VALUE_REQUIRED, 'User ID')
|
||||||
|
->addOption('host', null, InputOption::VALUE_REQUIRED, 'IMAP server hostname')
|
||||||
|
->addOption('port', null, InputOption::VALUE_REQUIRED, 'IMAP port (default: 993)')
|
||||||
|
->addOption('encryption', null, InputOption::VALUE_REQUIRED, 'Encryption: ssl | starttls | none (default: ssl)')
|
||||||
|
->addOption('username', null, InputOption::VALUE_REQUIRED, 'IMAP username / e-mail')
|
||||||
|
->addOption('no-verify', null, InputOption::VALUE_NONE, 'Disable TLS certificate verification')
|
||||||
|
->addOption('no-save', null, InputOption::VALUE_NONE, 'Test connection only; do not persist')
|
||||||
|
->setHelp(<<<'HELP'
|
||||||
|
The <info>provider_imap_mail:service:connect</info> command walks you through
|
||||||
|
manually configuring an IMAP account. All prompts can be pre-filled via
|
||||||
|
options to support non-interactive / scripted usage.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
Fully interactive:
|
||||||
|
<info>bin/console provider_imap_mail:service:connect</info>
|
||||||
|
|
||||||
|
Pre-fill common options:
|
||||||
|
<info>bin/console provider_imap_mail:service:connect \
|
||||||
|
--host=mail.example.com --username=user@example.com \
|
||||||
|
--tenant=t1 --user=u1</info>
|
||||||
|
HELP);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function interact(InputInterface $input, OutputInterface $output): void
|
||||||
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
$io->title('IMAP Service — Manual Configuration');
|
||||||
|
|
||||||
|
if (!$input->getOption('tenant')) {
|
||||||
|
$tenant = $io->ask('Tenant ID');
|
||||||
|
if ($tenant) $input->setOption('tenant', $tenant);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$input->getOption('user')) {
|
||||||
|
$user = $io->ask('User ID');
|
||||||
|
if ($user) $input->setOption('user', $user);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$input->getOption('host')) {
|
||||||
|
$host = $io->ask('IMAP server hostname');
|
||||||
|
if ($host) $input->setOption('host', $host);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$input->getOption('port')) {
|
||||||
|
$port = $io->ask('Port', '993');
|
||||||
|
if ($port) $input->setOption('port', $port);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$input->getOption('encryption')) {
|
||||||
|
$enc = $io->choice('Encryption', ['ssl', 'starttls', 'none'], 'ssl');
|
||||||
|
$input->setOption('encryption', $enc);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$input->getOption('username')) {
|
||||||
|
$username = $io->ask('Username / e-mail address');
|
||||||
|
if ($username) $input->setOption('username', $username);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
|
||||||
|
$tenantId = (string) ($input->getOption('tenant') ?? '');
|
||||||
|
$userId = (string) ($input->getOption('user') ?? '');
|
||||||
|
$host = (string) ($input->getOption('host') ?? '');
|
||||||
|
$port = (int) ($input->getOption('port') ?? 993);
|
||||||
|
$encryption = (string) ($input->getOption('encryption') ?? 'ssl');
|
||||||
|
$username = (string) ($input->getOption('username') ?? '');
|
||||||
|
$noVerify = (bool) $input->getOption('no-verify');
|
||||||
|
$noSave = (bool) $input->getOption('no-save');
|
||||||
|
|
||||||
|
// ── Validate required fields ─────────────────────────────────────────
|
||||||
|
|
||||||
|
$errors = [];
|
||||||
|
if ($host === '') $errors[] = 'Hostname is required.';
|
||||||
|
if ($username === '') $errors[] = 'Username is required.';
|
||||||
|
if (!$noSave) {
|
||||||
|
if ($tenantId === '') $errors[] = 'Tenant ID is required (or pass --no-save to test only).';
|
||||||
|
if ($userId === '') $errors[] = 'User ID is required (or pass --no-save to test only).';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($errors)) {
|
||||||
|
$io->error($errors);
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Prompt for password (always interactive — never passed via option) ─
|
||||||
|
|
||||||
|
$password = $io->askHidden('Password');
|
||||||
|
|
||||||
|
if (!$password) {
|
||||||
|
$io->error('Password is required.');
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Build service object ────────────────────────────────────────────
|
||||||
|
|
||||||
|
$location = new ServiceLocation(
|
||||||
|
host: $host,
|
||||||
|
port: $port > 0 ? $port : 993,
|
||||||
|
encryption: $encryption,
|
||||||
|
verifyPeer: !$noVerify,
|
||||||
|
verifyPeerName: !$noVerify,
|
||||||
|
allowSelfSigned: $noVerify,
|
||||||
|
);
|
||||||
|
|
||||||
|
$identity = (new ServiceIdentityBasic())->jsonDeserialize([
|
||||||
|
'identity' => $username,
|
||||||
|
'secret' => $password,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$service = new Service();
|
||||||
|
$service->setLocation($location);
|
||||||
|
$service->setIdentity($identity);
|
||||||
|
|
||||||
|
// ── Test connection ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
$io->text('Testing connection to <info>' . $host . ':' . $port . '</info>…');
|
||||||
|
|
||||||
|
$result = $this->provider->serviceTest($service);
|
||||||
|
|
||||||
|
if (!$result['success']) {
|
||||||
|
$io->error('Connection test failed: ' . $result['message']);
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$io->success($result['message']);
|
||||||
|
|
||||||
|
if ($noSave) {
|
||||||
|
$io->note('Connection test passed. Service not saved (--no-save).');
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Persist ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
$this->sessionTenant->configureById($tenantId);
|
||||||
|
|
||||||
|
$label = $io->ask('Service label', $username);
|
||||||
|
if ($label) {
|
||||||
|
$service->setLabel($label);
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = $this->provider->serviceCreate($tenantId, $userId, $service);
|
||||||
|
$io->success("Service saved with ID: <info>{$id}</info>");
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
177
lib/Console/DisconnectCommand.php
Normal file
177
lib/Console/DisconnectCommand.php
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace KTXM\ProviderImap\Console;
|
||||||
|
|
||||||
|
use KTXM\ProviderImap\Providers\Provider;
|
||||||
|
use KTXM\ProviderImap\Providers\Service;
|
||||||
|
use KTXC\SessionTenant;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a stored IMAP service connection.
|
||||||
|
*
|
||||||
|
* Looks up the service by its ID (or prompts for one), confirms with the
|
||||||
|
* operator, then permanently deletes the service document from the store.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* bin/console provider_imap_mail:service:disconnect --tenant=t1 --user=u1 <service-id>
|
||||||
|
*/
|
||||||
|
#[AsCommand(
|
||||||
|
name: 'provider_imap_mail:service:disconnect',
|
||||||
|
description: 'Remove a stored IMAP service connection',
|
||||||
|
)]
|
||||||
|
class DisconnectCommand extends Command
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly Provider $provider,
|
||||||
|
private readonly SessionTenant $sessionTenant,
|
||||||
|
) {
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->addArgument('service-id', InputArgument::OPTIONAL, 'Service ID to remove')
|
||||||
|
->addOption('tenant', 't', InputOption::VALUE_REQUIRED, 'Tenant ID')
|
||||||
|
->addOption('user', 'u', InputOption::VALUE_REQUIRED, 'User ID')
|
||||||
|
->addOption('force', 'f', InputOption::VALUE_NONE, 'Skip confirmation prompt')
|
||||||
|
->setHelp(<<<'HELP'
|
||||||
|
The <info>provider_imap_mail:service:disconnect</info> command permanently removes
|
||||||
|
a stored IMAP service configuration from the store.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
Interactive (lists services and prompts for ID):
|
||||||
|
<info>bin/console provider_imap_mail:service:disconnect --tenant=t1 --user=u1</info>
|
||||||
|
|
||||||
|
Direct, with confirmation:
|
||||||
|
<info>bin/console provider_imap_mail:service:disconnect abc123 --tenant=t1 --user=u1</info>
|
||||||
|
|
||||||
|
Skip confirmation:
|
||||||
|
<info>bin/console provider_imap_mail:service:disconnect abc123 --tenant=t1 --user=u1 --force</info>
|
||||||
|
HELP);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function interact(InputInterface $input, OutputInterface $output): void
|
||||||
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
|
||||||
|
if (!$input->getOption('tenant')) {
|
||||||
|
$tenant = $io->ask('Tenant ID');
|
||||||
|
if ($tenant) $input->setOption('tenant', $tenant);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$input->getOption('user')) {
|
||||||
|
$user = $io->ask('User ID');
|
||||||
|
if ($user) $input->setOption('user', $user);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no service ID given, list available services and let the operator pick
|
||||||
|
if (!$input->getArgument('service-id')) {
|
||||||
|
$tenantId = (string) ($input->getOption('tenant') ?? '');
|
||||||
|
$userId = (string) ($input->getOption('user') ?? '');
|
||||||
|
|
||||||
|
if ($tenantId !== '' && $userId !== '') {
|
||||||
|
$this->sessionTenant->configureById($tenantId);
|
||||||
|
$services = $this->provider->serviceList($tenantId, $userId);
|
||||||
|
|
||||||
|
if (empty($services)) {
|
||||||
|
// nothing to select — let execute() handle the error
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$choices = [];
|
||||||
|
foreach ($services as $id => $service) {
|
||||||
|
$label = $service instanceof Service ? ($service->getLabel() ?? $id) : $id;
|
||||||
|
$choices[$id] = "{$label} [{$id}]";
|
||||||
|
}
|
||||||
|
|
||||||
|
$chosen = $io->choice('Select service to disconnect', array_values($choices));
|
||||||
|
// resolve the chosen label back to its key
|
||||||
|
$serviceId = (string) array_search($chosen, $choices, true);
|
||||||
|
if ($serviceId !== '') {
|
||||||
|
$input->setArgument('service-id', $serviceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
|
||||||
|
$tenantId = (string) ($input->getOption('tenant') ?? '');
|
||||||
|
$userId = (string) ($input->getOption('user') ?? '');
|
||||||
|
$serviceId = (string) ($input->getArgument('service-id') ?? '');
|
||||||
|
$force = (bool) $input->getOption('force');
|
||||||
|
|
||||||
|
$errors = [];
|
||||||
|
if ($tenantId === '') $errors[] = 'Tenant ID is required (--tenant).';
|
||||||
|
if ($userId === '') $errors[] = 'User ID is required (--user).';
|
||||||
|
if ($serviceId === '') $errors[] = 'Service ID is required.';
|
||||||
|
|
||||||
|
if (!empty($errors)) {
|
||||||
|
$io->error($errors);
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Fetch service for display ────────────────────────────────────────
|
||||||
|
$this->sessionTenant->configureById($tenantId);
|
||||||
|
$service = $this->provider->serviceFetch($tenantId, $userId, $serviceId);
|
||||||
|
|
||||||
|
if ($service === null) {
|
||||||
|
$io->error("Service '{$serviceId}' not found.");
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$label = $service->getLabel() ?? $serviceId;
|
||||||
|
$host = $service->getLocation()?->getHost() ?? 'unknown';
|
||||||
|
|
||||||
|
$io->title('Disconnect IMAP Service');
|
||||||
|
$io->definitionList(
|
||||||
|
['ID' => $serviceId],
|
||||||
|
['Label' => $label],
|
||||||
|
['Host' => $host],
|
||||||
|
);
|
||||||
|
|
||||||
|
// ── Confirmation ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
if (!$force) {
|
||||||
|
$confirm = $io->confirm(
|
||||||
|
"Permanently remove service <comment>{$label}</comment> ({$serviceId})?",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
if (!$confirm) {
|
||||||
|
$io->note('Aborted.');
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Destroy ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
$deleted = $this->provider->serviceDestroy($tenantId, $userId, $service);
|
||||||
|
|
||||||
|
if (!$deleted) {
|
||||||
|
$io->error("Failed to remove service '{$serviceId}'.");
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$io->success("Service '{$label}' ({$serviceId}) has been removed.");
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
226
lib/Console/DiscoverCommand.php
Normal file
226
lib/Console/DiscoverCommand.php
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace KTXM\ProviderImap\Console;
|
||||||
|
|
||||||
|
use KTXM\ProviderImap\Providers\Provider;
|
||||||
|
use KTXM\ProviderImap\Providers\Service;
|
||||||
|
use KTXM\ProviderImap\Providers\ServiceIdentityBasic;
|
||||||
|
use KTXM\ProviderImap\Providers\ServiceLocation;
|
||||||
|
use KTXM\ProviderImap\Service\Discovery;
|
||||||
|
use KTXM\ProviderImap\Service\Remote\RemoteService;
|
||||||
|
use KTXC\SessionTenant;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automated IMAP service discovery.
|
||||||
|
*
|
||||||
|
* Probes DNS SRV records and common hostnames to determine the correct IMAP
|
||||||
|
* server settings for a given e-mail address, then optionally persists the
|
||||||
|
* resulting service to the store.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* bin/console provider_imap_mail:service:discover user@example.com
|
||||||
|
* bin/console provider_imap_mail:service:discover user@example.com --tenant=t1 --user=u1 --save
|
||||||
|
*/
|
||||||
|
#[AsCommand(
|
||||||
|
name: 'provider_imap_mail:service:discover',
|
||||||
|
description: 'Auto-discover IMAP server settings from an e-mail address',
|
||||||
|
)]
|
||||||
|
class DiscoverCommand extends Command
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly Provider $provider,
|
||||||
|
private readonly Discovery $discovery,
|
||||||
|
private readonly SessionTenant $sessionTenant,
|
||||||
|
) {
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->addArgument('address', InputArgument::OPTIONAL, 'E-mail address to discover settings for')
|
||||||
|
->addOption('tenant', 't', InputOption::VALUE_REQUIRED, 'Tenant ID (required when --save is set)')
|
||||||
|
->addOption('user', 'u', InputOption::VALUE_REQUIRED, 'User ID (required when --save is set)')
|
||||||
|
->addOption('host', null, InputOption::VALUE_REQUIRED, 'Explicit hostname to probe instead of DNS lookup')
|
||||||
|
->addOption('no-verify', null, InputOption::VALUE_NONE, 'Disable TLS certificate verification')
|
||||||
|
->addOption('save', null, InputOption::VALUE_NONE, 'Persist the discovered service after a successful test')
|
||||||
|
->setHelp(<<<'HELP'
|
||||||
|
The <info>provider_imap_mail:service:discover</info> command auto-discovers IMAP
|
||||||
|
server settings by probing DNS SRV records (_imaps._tcp / _imap._tcp) and
|
||||||
|
common hostname conventions (mail.<domain>, imap.<domain>, …).
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
Dry-run discovery (no persistence):
|
||||||
|
<info>bin/console provider_imap_mail:service:discover user@example.com</info>
|
||||||
|
|
||||||
|
Discover and save under a specific tenant/user pair:
|
||||||
|
<info>bin/console provider_imap_mail:service:discover user@example.com --tenant=t1 --user=u1 --save</info>
|
||||||
|
|
||||||
|
Probe an explicit host:
|
||||||
|
<info>bin/console provider_imap_mail:service:discover user@example.com --host=mail.example.com</info>
|
||||||
|
HELP);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function interact(InputInterface $input, OutputInterface $output): void
|
||||||
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
|
||||||
|
if (!$input->getArgument('address')) {
|
||||||
|
$address = $io->ask('E-mail address');
|
||||||
|
if ($address) {
|
||||||
|
$input->setArgument('address', $address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($input->getOption('save')) {
|
||||||
|
if (!$input->getOption('tenant')) {
|
||||||
|
$tenant = $io->ask('Tenant ID');
|
||||||
|
if ($tenant) {
|
||||||
|
$input->setOption('tenant', $tenant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$input->getOption('user')) {
|
||||||
|
$user = $io->ask('User ID');
|
||||||
|
if ($user) {
|
||||||
|
$input->setOption('user', $user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
|
||||||
|
$address = (string) $input->getArgument('address');
|
||||||
|
$host = $input->getOption('host');
|
||||||
|
$noVerify = (bool) $input->getOption('no-verify');
|
||||||
|
$save = (bool) $input->getOption('save');
|
||||||
|
$tenantId = (string) ($input->getOption('tenant') ?? '');
|
||||||
|
$userId = (string) ($input->getOption('user') ?? '');
|
||||||
|
|
||||||
|
if ($address === '') {
|
||||||
|
$io->error('An e-mail address is required.');
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($save && ($tenantId === '' || $userId === '')) {
|
||||||
|
$io->error('--tenant and --user are required when --save is set.');
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$io->title('IMAP Service Discovery');
|
||||||
|
$io->text("Discovering settings for <info>{$address}</info>…");
|
||||||
|
|
||||||
|
// ── Discovery ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
$candidates = $this->discovery->discoverAll(
|
||||||
|
identity: $address,
|
||||||
|
location: $host,
|
||||||
|
verifySSL: !$noVerify,
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($candidates === []) {
|
||||||
|
$io->error('Discovery failed — no reachable IMAP server found.');
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Build labelled choice list ────────────────────────────────────────
|
||||||
|
|
||||||
|
$encLabel = static fn (string $e): string => match ($e) {
|
||||||
|
'ssl' => 'SSL/TLS',
|
||||||
|
'starttls' => 'STARTTLS',
|
||||||
|
default => 'None (plain)',
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @var array<string, ServiceLocation> $choiceMap */
|
||||||
|
$choiceMap = [];
|
||||||
|
foreach ($candidates as $c) {
|
||||||
|
$label = sprintf('%s : %d [%s]', $c->getHost(), $c->getPort(), $encLabel($c->getEncryption()));
|
||||||
|
$choiceMap[$label] = $c;
|
||||||
|
}
|
||||||
|
|
||||||
|
$io->text(sprintf('<info>%d</info> location(s) found:', count($candidates)));
|
||||||
|
$io->newLine();
|
||||||
|
|
||||||
|
if (count($candidates) === 1) {
|
||||||
|
$chosenLabel = array_key_first($choiceMap);
|
||||||
|
} else {
|
||||||
|
$chosenLabel = $io->choice('Select which server to use', array_keys($choiceMap), array_key_first($choiceMap));
|
||||||
|
}
|
||||||
|
|
||||||
|
$location = $choiceMap[$chosenLabel];
|
||||||
|
|
||||||
|
// ── Display chosen result ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
$io->success('Server selected:');
|
||||||
|
$io->definitionList(
|
||||||
|
['Host' => $location->getHost()],
|
||||||
|
['Port' => (string) $location->getPort()],
|
||||||
|
['Encryption' => $encLabel($location->getEncryption())],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!$save) {
|
||||||
|
$io->note('Run with --save --tenant=<id> --user=<id> to persist this service.');
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Interactive credential prompt ────────────────────────────────────
|
||||||
|
|
||||||
|
$username = $io->ask('Username', $address);
|
||||||
|
$password = $io->askHidden('Password');
|
||||||
|
|
||||||
|
if (!$username || !$password) {
|
||||||
|
$io->error('Username and password are required to save the service.');
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Test before saving ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
$service = new Service();
|
||||||
|
$service->setLocation($location);
|
||||||
|
$service->setIdentity((new ServiceIdentityBasic())->jsonDeserialize([
|
||||||
|
'identity' => $username,
|
||||||
|
'secret' => $password,
|
||||||
|
]));
|
||||||
|
|
||||||
|
$io->text('Testing connection…');
|
||||||
|
$result = $this->provider->serviceTest($service);
|
||||||
|
|
||||||
|
if (!$result['success']) {
|
||||||
|
$io->error('Connection test failed: ' . $result['message']);
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$io->success($result['message']);
|
||||||
|
|
||||||
|
// ── Persist ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
$this->sessionTenant->configureById($tenantId);
|
||||||
|
|
||||||
|
$label = $io->ask('Service label', $address);
|
||||||
|
if ($label) {
|
||||||
|
$service->setLabel($label);
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = $this->provider->serviceCreate($tenantId, $userId, $service);
|
||||||
|
$io->success("Service saved with ID: <info>{$id}</info>");
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
362
lib/Console/TestCommand.php
Normal file
362
lib/Console/TestCommand.php
Normal file
@@ -0,0 +1,362 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace KTXM\ProviderImap\Console;
|
||||||
|
|
||||||
|
use KTXC\SessionTenant;
|
||||||
|
use KTXM\ProviderImap\Client\Command\SelectCommand;
|
||||||
|
use KTXM\ProviderImap\Client\FetchOptions;
|
||||||
|
use KTXM\ProviderImap\Client\FetchTarget;
|
||||||
|
use KTXM\ProviderImap\Client\Message;
|
||||||
|
use KTXM\ProviderImap\Client\SequenceSet;
|
||||||
|
use KTXM\ProviderImap\Providers\Provider;
|
||||||
|
use KTXM\ProviderImap\Providers\Service;
|
||||||
|
use KTXM\ProviderImap\Service\Remote\RemoteService;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Live IMAP service diagnostic.
|
||||||
|
*
|
||||||
|
* Loads an existing stored IMAP service, connects with its saved credentials,
|
||||||
|
* shows a bounded list of mailboxes, then prints the most recent messages
|
||||||
|
* from a selected mailbox.
|
||||||
|
*/
|
||||||
|
#[AsCommand(
|
||||||
|
name: 'provider_imap_mail:service:test',
|
||||||
|
description: 'Test an existing IMAP service and inspect mailboxes and messages',
|
||||||
|
)]
|
||||||
|
class TestCommand extends Command
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly Provider $provider,
|
||||||
|
private readonly SessionTenant $sessionTenant,
|
||||||
|
) {
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->addArgument('service-id', InputArgument::OPTIONAL, 'Stored service ID to test')
|
||||||
|
->addOption('tenant', 't', InputOption::VALUE_REQUIRED, 'Tenant ID')
|
||||||
|
->addOption('user', 'u', InputOption::VALUE_REQUIRED, 'User ID')
|
||||||
|
->addOption('mailbox', 'm', InputOption::VALUE_REQUIRED, 'Specific mailbox to inspect (for example: INBOX)')
|
||||||
|
->addOption('mailbox-limit', null, InputOption::VALUE_REQUIRED, 'Maximum number of mailboxes to inspect (default: 5, 0 = all)', '5')
|
||||||
|
->addOption('message-limit', null, InputOption::VALUE_REQUIRED, 'Maximum number of recent messages to show (default: 5, 0 = none)', '5')
|
||||||
|
->setHelp(<<<'HELP'
|
||||||
|
The <info>provider_imap_mail:service:test</info> command performs a live
|
||||||
|
diagnostic against an already stored IMAP service.
|
||||||
|
|
||||||
|
It will:
|
||||||
|
1. Load the saved service for a tenant/user pair
|
||||||
|
2. Connect and authenticate with the stored credentials
|
||||||
|
3. Show a bounded list of selectable mailboxes
|
||||||
|
4. Show the most recent messages from the selected mailbox
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
Interactive service selection:
|
||||||
|
<info>provider_imap_mail:service:test --tenant=t1 --user=u1</info>
|
||||||
|
|
||||||
|
Test a specific stored service:
|
||||||
|
<info>provider_imap_mail:service:test abc123 --tenant=t1 --user=u1</info>
|
||||||
|
|
||||||
|
Show recent messages from INBOX:
|
||||||
|
<info>provider_imap_mail:service:test abc123 --tenant=t1 --user=u1 --mailbox=INBOX</info>
|
||||||
|
|
||||||
|
Show 10 mailboxes and 3 recent messages from Drafts:
|
||||||
|
<info>provider_imap_mail:service:test abc123 --tenant=t1 --user=u1 --mailbox=Drafts --mailbox-limit=10 --message-limit=3</info>
|
||||||
|
HELP);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
|
||||||
|
$tenantId = (string) ($input->getOption('tenant') ?? '');
|
||||||
|
$userId = (string) ($input->getOption('user') ?? '');
|
||||||
|
$serviceId = (string) ($input->getArgument('service-id') ?? '');
|
||||||
|
$mailboxName = trim((string) ($input->getOption('mailbox') ?? 'INBOX'));
|
||||||
|
$mailboxLimit = $this->normalizeLimit($input->getOption('mailbox-limit'), 5);
|
||||||
|
$messageLimit = $this->normalizeLimit($input->getOption('message-limit'), 5);
|
||||||
|
|
||||||
|
$errors = [];
|
||||||
|
if ($tenantId === '') {
|
||||||
|
$errors[] = 'Tenant ID is required (--tenant).';
|
||||||
|
}
|
||||||
|
if ($userId === '') {
|
||||||
|
$errors[] = 'User ID is required (--user).';
|
||||||
|
}
|
||||||
|
if ($serviceId === '') {
|
||||||
|
$errors[] = 'Service ID is required.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($errors !== []) {
|
||||||
|
$io->error($errors);
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->sessionTenant->configureById($tenantId);
|
||||||
|
$service = $this->provider->serviceFetch($tenantId, $userId, $serviceId);
|
||||||
|
|
||||||
|
if ($service === null) {
|
||||||
|
$io->error(sprintf("Service '%s' not found.", $serviceId));
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$startedAt = microtime(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$client = RemoteService::freshClient($service);
|
||||||
|
$mailService = RemoteService::mailService($service, $client);
|
||||||
|
$mailboxes = $mailService->collectionList();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$io->error('IMAP diagnostic failed: ' . $e->getMessage());
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mailboxNames = array_keys($mailboxes);
|
||||||
|
sort($mailboxNames);
|
||||||
|
|
||||||
|
if (!in_array($mailboxName, $mailboxNames, true)) {
|
||||||
|
$io->error(sprintf("Mailbox '%s' not found on this service.", $mailboxName));
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$visibleMailboxNames = $mailboxLimit === 0
|
||||||
|
? $mailboxNames
|
||||||
|
: array_slice($mailboxNames, 0, $mailboxLimit);
|
||||||
|
|
||||||
|
$io->section('Service');
|
||||||
|
$io->definitionList(
|
||||||
|
['ID' => $serviceId],
|
||||||
|
['Label' => $service->getLabel() ?? $serviceId],
|
||||||
|
['Target' => $this->formatTarget($service)],
|
||||||
|
['Username' => $service->getIdentity()?->getIdentity() ?? '-'],
|
||||||
|
['Mailbox target' => $mailboxName],
|
||||||
|
['Mailbox list size' => $mailboxLimit === 0 ? 'all' : (string) $mailboxLimit],
|
||||||
|
['Recent message count' => (string) $messageLimit],
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($mailboxNames === []) {
|
||||||
|
$io->warning('Authenticated successfully, but no selectable mailboxes were returned.');
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
$io->section('Mailboxes');
|
||||||
|
$mailboxRows = [];
|
||||||
|
foreach ($visibleMailboxNames as $name) {
|
||||||
|
$mailbox = $mailboxes[$name];
|
||||||
|
$properties = $mailbox->getProperties()->jsonSerialize();
|
||||||
|
$mailboxRows[] = [
|
||||||
|
$name,
|
||||||
|
(string) ($properties['role'] ?? 'custom'),
|
||||||
|
(string) ($mailbox->collection() ?? '-'),
|
||||||
|
(string) ($properties['delimiter'] ?? '/'),
|
||||||
|
$this->formatMailboxAttributes($properties['attributes'] ?? []),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$io->table(
|
||||||
|
['Mailbox', 'Role', 'Parent', 'Delimiter', 'Attributes'],
|
||||||
|
$mailboxRows,
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($mailboxLimit !== 0 && count($visibleMailboxNames) < count($mailboxNames)) {
|
||||||
|
$io->note(sprintf(
|
||||||
|
'Mailbox list truncated to the first %d selectable mailboxes. Increase --mailbox-limit or set it to 0 to inspect all mailboxes.',
|
||||||
|
count($visibleMailboxNames),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$io->section(sprintf('Recent Messages: %s', $mailboxName));
|
||||||
|
|
||||||
|
try {
|
||||||
|
$mailboxClient = RemoteService::freshClient($service);
|
||||||
|
$mailboxService = RemoteService::mailService($service, $mailboxClient);
|
||||||
|
$selectedMailbox = $mailboxClient->perform(new SelectCommand($mailboxName, true));
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$io->error('Mailbox inspection failed: ' . $e->getMessage());
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$totalMessages = $selectedMailbox->messages();
|
||||||
|
$io->text(sprintf('Total messages: <info>%d</info>', $totalMessages));
|
||||||
|
|
||||||
|
if ($totalMessages === 0) {
|
||||||
|
$io->note('No messages found in this mailbox.');
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($messageLimit === 0) {
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sampleCount = min($totalMessages, $messageLimit);
|
||||||
|
$startSequence = max(1, $totalMessages - $sampleCount + 1);
|
||||||
|
$messageRows = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
foreach ($mailboxService->messageList(
|
||||||
|
$mailboxName,
|
||||||
|
FetchTarget::sequence(SequenceSet::range($startSequence, $totalMessages)),
|
||||||
|
FetchOptions::message(),
|
||||||
|
) as $message) {
|
||||||
|
$messageRows[] = [
|
||||||
|
(string) $message->uid(),
|
||||||
|
$this->formatSubject($message),
|
||||||
|
$this->formatSender($message),
|
||||||
|
$message->internalDate() ?? '-',
|
||||||
|
$this->formatFlags($message),
|
||||||
|
$this->formatBytes($message->size()),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$io->error('Message listing failed: ' . $e->getMessage());
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$messageRows = array_reverse($messageRows);
|
||||||
|
|
||||||
|
$io->table(
|
||||||
|
['UID', 'Subject', 'From', 'Date', 'Flags', 'Size'],
|
||||||
|
$messageRows,
|
||||||
|
);
|
||||||
|
|
||||||
|
$elapsed = (int) round((microtime(true) - $startedAt) * 1000);
|
||||||
|
$io->text(sprintf('Elapsed: <info>%d ms</info>', $elapsed));
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function interact(InputInterface $input, OutputInterface $output): void
|
||||||
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
$io->title('IMAP Service Diagnostic');
|
||||||
|
|
||||||
|
if (!$input->getOption('tenant')) {
|
||||||
|
$tenant = $io->ask('Tenant ID');
|
||||||
|
if ($tenant) {
|
||||||
|
$input->setOption('tenant', $tenant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$input->getOption('user')) {
|
||||||
|
$user = $io->ask('User ID');
|
||||||
|
if ($user) {
|
||||||
|
$input->setOption('user', $user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($input->getArgument('service-id')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tenantId = (string) ($input->getOption('tenant') ?? '');
|
||||||
|
$userId = (string) ($input->getOption('user') ?? '');
|
||||||
|
|
||||||
|
if ($tenantId === '' || $userId === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->sessionTenant->configureById($tenantId);
|
||||||
|
$services = $this->provider->serviceList($tenantId, $userId);
|
||||||
|
|
||||||
|
if ($services === []) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$choices = [];
|
||||||
|
foreach ($services as $id => $service) {
|
||||||
|
$label = $service->getLabel() ?? (string) $id;
|
||||||
|
$choices[$id] = sprintf('%s [%s] %s', $label, $id, $this->formatTarget($service));
|
||||||
|
}
|
||||||
|
|
||||||
|
$chosen = $io->choice('Select service to test', array_values($choices));
|
||||||
|
$serviceId = (string) array_search($chosen, $choices, true);
|
||||||
|
if ($serviceId !== '') {
|
||||||
|
$input->setArgument('service-id', $serviceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalizeLimit(mixed $value, int $default): int
|
||||||
|
{
|
||||||
|
if ($value === null || $value === '') {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
$limit = (int) $value;
|
||||||
|
return $limit >= 0 ? $limit : $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatTarget(Service $service): string
|
||||||
|
{
|
||||||
|
$location = $service->getLocation();
|
||||||
|
if ($location === null) {
|
||||||
|
return 'unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf('%s://%s:%d', $location->getEncryption(), $location->getHost(), $location->getPort());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatSender(Message $message): string
|
||||||
|
{
|
||||||
|
$sender = $message->from()[0] ?? $message->sender()[0] ?? null;
|
||||||
|
if ($sender === null) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sender->name() ?: ($sender->email() ?? '-');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatSubject(Message $message): string
|
||||||
|
{
|
||||||
|
$subject = trim((string) ($message->subject() ?? ''));
|
||||||
|
if ($subject === '') {
|
||||||
|
return '(no subject)';
|
||||||
|
}
|
||||||
|
|
||||||
|
return mb_strimwidth($subject, 0, 60, '...');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatFlags(Message $message): string
|
||||||
|
{
|
||||||
|
$flags = $message->flags();
|
||||||
|
return $flags === [] ? '-' : implode(', ', $flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatMailboxAttributes(array $attributes): string
|
||||||
|
{
|
||||||
|
if ($attributes === []) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode(', ', $attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatBytes(int $bytes): string
|
||||||
|
{
|
||||||
|
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||||
|
$size = (float) max(0, $bytes);
|
||||||
|
$unitIndex = 0;
|
||||||
|
|
||||||
|
while ($size >= 1024 && $unitIndex < count($units) - 1) {
|
||||||
|
$size /= 1024;
|
||||||
|
$unitIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf($unitIndex === 0 ? '%.0f %s' : '%.1f %s', $size, $units[$unitIndex]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,10 +14,10 @@ use KTXF\Module\ModuleBrowserInterface;
|
|||||||
use KTXF\Module\ModuleConsoleInterface;
|
use KTXF\Module\ModuleConsoleInterface;
|
||||||
use KTXF\Module\ModuleInstanceAbstract;
|
use KTXF\Module\ModuleInstanceAbstract;
|
||||||
use KTXF\Resource\Provider\ProviderInterface;
|
use KTXF\Resource\Provider\ProviderInterface;
|
||||||
use KTXM\ProviderImap\Console\ServiceConnectCommand;
|
use KTXM\ProviderImap\Console\ConnectCommand;
|
||||||
use KTXM\ProviderImap\Console\ServiceDiscoverCommand;
|
use KTXM\ProviderImap\Console\DiscoverCommand;
|
||||||
use KTXM\ProviderImap\Console\ServiceDisconnectCommand;
|
use KTXM\ProviderImap\Console\DisconnectCommand;
|
||||||
use KTXM\ProviderImap\Console\ServiceTestCommand;
|
use KTXM\ProviderImap\Console\TestCommand;
|
||||||
use KTXM\ProviderImap\Providers\Provider as MailProvider;
|
use KTXM\ProviderImap\Providers\Provider as MailProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -75,10 +75,10 @@ class Module extends ModuleInstanceAbstract implements ModuleConsoleInterface, M
|
|||||||
public function registerCI(): array
|
public function registerCI(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
ServiceDiscoverCommand::class,
|
DiscoverCommand::class,
|
||||||
ServiceConnectCommand::class,
|
ConnectCommand::class,
|
||||||
ServiceDisconnectCommand::class,
|
DisconnectCommand::class,
|
||||||
ServiceTestCommand::class,
|
TestCommand::class,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user