mirror of
https://github.com/itflow-org/itflow
synced 2026-07-24 09:20:40 +00:00
Rewrite email parser using ImapEngine, harden processing loop
Replace webklex/php-imap with directorytree/imapengine in the ticket email parser. ImapEngine is pure PHP over sockets. Parser improvements: - Wrap per-message processing in try/catch so one malformed email can't abort the run; failures are flagged and logged with UID - Query unseen + unflagged so previously-failed (flagged) messages are no longer re-processed on every cron run - Skip vacation/auto-responder emails (RFC 3834) to prevent mail loops with the ticket auto-reply - Cap messages per run (50) and attachment size (15MB); inline images over 2MB are stored as attachments instead of base64-embedded in ticket details - Atomic lock file creation - preg_quote() the ticket prefix in subject matching - Dedupe CC watchers and exclude the sender - Map webklex 'tls' encryption setting to STARTTLS for compatibility NDR/DSN parsing now walks MIME parts via the underlying zbateson parser instead of relying on attachment extraction.
This commit is contained in:
173
plugins/vendor/directorytree/imapengine/src/Idle.php
vendored
Normal file
173
plugins/vendor/directorytree/imapengine/src/Idle.php
vendored
Normal file
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonInterface;
|
||||
use Closure;
|
||||
use DirectoryTree\ImapEngine\Connection\Responses\UntaggedResponse;
|
||||
use DirectoryTree\ImapEngine\Connection\Tokens\Atom;
|
||||
use DirectoryTree\ImapEngine\Exceptions\Exception;
|
||||
use DirectoryTree\ImapEngine\Exceptions\ImapConnectionClosedException;
|
||||
use DirectoryTree\ImapEngine\Exceptions\ImapConnectionTimedOutException;
|
||||
use Generator;
|
||||
|
||||
class Idle
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
protected Mailbox $mailbox,
|
||||
protected string $folder,
|
||||
protected Closure|int $timeout,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$this->disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Await new messages on the connection.
|
||||
*/
|
||||
public function await(callable $callback): void
|
||||
{
|
||||
$this->connect();
|
||||
|
||||
while ($ttl = $this->getNextTimeout()) {
|
||||
try {
|
||||
$this->listen($callback, $ttl);
|
||||
} catch (ImapConnectionTimedOutException) {
|
||||
$this->restart();
|
||||
} catch (ImapConnectionClosedException) {
|
||||
$this->reconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start listening for new messages using the idle() generator.
|
||||
*/
|
||||
protected function listen(callable $callback, CarbonInterface $ttl): void
|
||||
{
|
||||
// Iterate over responses yielded by the idle generator.
|
||||
foreach ($this->idle($ttl) as $response) {
|
||||
if (! $response instanceof UntaggedResponse) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! $token = $response->tokenAt(2)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token instanceof Atom && $token->is('EXISTS')) {
|
||||
$msgn = (int) $response->tokenAt(1)->value;
|
||||
|
||||
$callback($msgn);
|
||||
|
||||
$ttl = $this->getNextTimeout();
|
||||
}
|
||||
|
||||
if ($ttl === false) {
|
||||
break;
|
||||
}
|
||||
|
||||
// If we've been idle too long, break out to restart the session.
|
||||
if (Carbon::now()->greaterThanOrEqualTo($ttl)) {
|
||||
$this->restart();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the folder to idle.
|
||||
*/
|
||||
protected function folder(): FolderInterface
|
||||
{
|
||||
return $this->mailbox->folders()->findOrFail($this->folder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Issue a done command and restart the idle session.
|
||||
*/
|
||||
protected function restart(): void
|
||||
{
|
||||
try {
|
||||
// Send DONE to terminate the current IDLE session gracefully.
|
||||
$this->done();
|
||||
} catch (Exception) {
|
||||
$this->reconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconnect the client and restart the idle session.
|
||||
*/
|
||||
protected function reconnect(): void
|
||||
{
|
||||
$this->mailbox->disconnect();
|
||||
|
||||
$this->connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect the client and select the folder to idle.
|
||||
*/
|
||||
protected function connect(): void
|
||||
{
|
||||
$this->mailbox->connect();
|
||||
|
||||
$this->mailbox->select($this->folder(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect the client.
|
||||
*/
|
||||
protected function disconnect(): void
|
||||
{
|
||||
try {
|
||||
// Attempt to terminate IDLE gracefully.
|
||||
$this->done();
|
||||
} catch (Exception) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
$this->mailbox->disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* End the current IDLE session.
|
||||
*/
|
||||
protected function done(): void
|
||||
{
|
||||
$this->mailbox->connection()->done();
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin a new IDLE session as a generator.
|
||||
*/
|
||||
protected function idle(CarbonInterface $ttl): Generator
|
||||
{
|
||||
yield from $this->mailbox->connection()->idle(
|
||||
(int) Carbon::now()->diffInSeconds($ttl, true)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next timeout as a Carbon instance.
|
||||
*/
|
||||
protected function getNextTimeout(): CarbonInterface|false
|
||||
{
|
||||
if (is_numeric($seconds = value($this->timeout))) {
|
||||
return Carbon::now()->addSeconds(abs($seconds));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user