Implement IMAP email-to-ticket gateway

This commit is contained in:
macktic committed 2026-10-10 16:22:56 +01:00
1 parent b7e5884457
commit 6f25869345
33 files changed
+5619 -5

No files matched your search

+111
View File
@@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
namespace Box\Mod\Mailgateway;
use FOSSBilling\InformationException;
class DependencyInstaller
{
public function status(): array
{
return ['installed' => $this->installed(), 'available' => function_exists('proc_open')
&& $this->php() !== null && $this->composer() !== null && is_writable(__DIR__),
'request_timeout_ms' => (RuntimeConfig::get('composer_timeout_seconds') + 30) * 1000];
}
public function install(bool $force = false): array
{
if (!$force && $this->installed()) {
return ['installed' => true];
}
$php = $this->php();
$composer = $this->composer();
if (!function_exists('proc_open') || $php === null || $composer === null || !is_writable(__DIR__)) {
throw new InformationException('Web installation is unavailable. Run composer install from the Mailgateway directory using SSH.');
}
if (!is_file(__DIR__ . '/composer.lock')) {
throw new InformationException('The module composer.lock is missing. Restore it before installing dependencies.');
}
$command = [$php, $composer, 'install', '--no-dev', '--prefer-dist', '--no-interaction',
'--no-progress', '--no-scripts', '--no-plugins', '--optimize-autoloader'];
$result = $this->run($command);
if ($result['timeout']) {
throw new InformationException('Dependency installation exceeded its configured timeout. Run composer install from the Mailgateway directory using SSH.');
}
if ($result['exit'] !== 0 || !$this->installed()) {
throw new InformationException('Composer installation failed. Run composer install from the Mailgateway directory using SSH to see the detailed error.');
}
return ['installed' => true];
}
private function installed(): bool
{
return is_file(__DIR__ . '/vendor/autoload.php')
&& is_file(__DIR__ . '/vendor/webklex/php-imap/src/ClientManager.php');
}
private function php(): ?string
{
foreach (RuntimeConfig::get('php_cli_paths') as $path) {
if (is_file($path) && is_executable($path)) {
return $path;
}
}
return null;
}
private function composer(): ?string
{
foreach (RuntimeConfig::get('composer_paths') as $path) {
if (is_file($path) && is_readable($path)) {
return $path;
}
}
return null;
}
protected function run(array $command): array
{
// PHP may terminate a web request before Composer completes otherwise.
@set_time_limit(RuntimeConfig::get('composer_timeout_seconds') + 15);
$process = proc_open($command, [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']], $pipes, __DIR__);
if (!is_resource($process)) {
throw new InformationException('Composer could not be started. Run composer install using SSH.');
}
fclose($pipes[0]);
stream_set_blocking($pipes[1], false);
stream_set_blocking($pipes[2], false);
$deadline = microtime(true) + RuntimeConfig::get('composer_timeout_seconds');
$exit = -1;
$timeout = false;
try {
while (true) {
// Drain both pipes to avoid deadlock. Never expose Composer output:
// repository authentication and environment details can appear there.
stream_get_contents($pipes[1]);
stream_get_contents($pipes[2]);
$status = proc_get_status($process);
if (!$status['running']) {
$exit = $status['exitcode'];
break;
}
if (microtime(true) >= $deadline) {
$timeout = true;
proc_terminate($process, 9);
break;
}
usleep(100000);
}
} finally {
fclose($pipes[1]);
fclose($pipes[2]);
$closed = proc_close($process);
if ($exit === -1) {
$exit = $closed;
}
}
return ['exit' => $exit, 'timeout' => $timeout];
}
}