496 lines
18 KiB
PHP
496 lines
18 KiB
PHP
<?php declare(strict_types=1); namespace IR\App\Webservices; if (!defined('IR_START')) exit('<pre>No direct script access allowed</pre>');
|
|
|
|
/**
|
|
* Native PHP Mailbox Extractor - Replaces Java JAR
|
|
* Supports ALL ISPs with auto-detection
|
|
*/
|
|
class MailboxExtractorNative
|
|
{
|
|
// Known IMAP servers for popular ISPs
|
|
private static $imapServers = [
|
|
// Major providers
|
|
'gmail' => ['host' => 'imap.gmail.com', 'port' => 993],
|
|
'googlemail' => ['host' => 'imap.gmail.com', 'port' => 993],
|
|
'yahoo' => ['host' => 'imap.mail.yahoo.com', 'port' => 993],
|
|
'ymail' => ['host' => 'imap.mail.yahoo.com', 'port' => 993],
|
|
'hotmail' => ['host' => 'outlook.office365.com', 'port' => 993],
|
|
'outlook' => ['host' => 'outlook.office365.com', 'port' => 993],
|
|
'live' => ['host' => 'outlook.office365.com', 'port' => 993],
|
|
'msn' => ['host' => 'outlook.office365.com', 'port' => 993],
|
|
'onmicrosoft' => ['host' => 'outlook.office365.com', 'port' => 993],
|
|
'windowslive' => ['host' => 'outlook.office365.com', 'port' => 993],
|
|
'aol' => ['host' => 'imap.aol.com', 'port' => 993],
|
|
|
|
// European providers
|
|
'gmx' => ['host' => 'imap.gmx.net', 'port' => 993],
|
|
'web' => ['host' => 'imap.web.de', 'port' => 993],
|
|
't-online' => ['host' => 'secureimap.t-online.de', 'port' => 993],
|
|
'freenet' => ['host' => 'mx.freenet.de', 'port' => 993],
|
|
'orange' => ['host' => 'imap.orange.fr', 'port' => 993],
|
|
'wanadoo' => ['host' => 'imap.orange.fr', 'port' => 993],
|
|
'free' => ['host' => 'imap.free.fr', 'port' => 993],
|
|
'sfr' => ['host' => 'imap.sfr.fr', 'port' => 993],
|
|
'laposte' => ['host' => 'imap.laposte.net', 'port' => 993],
|
|
'libero' => ['host' => 'imapmail.libero.it', 'port' => 993],
|
|
'virgilio' => ['host' => 'in.virgilio.it', 'port' => 993],
|
|
'alice' => ['host' => 'in.alice.it', 'port' => 993],
|
|
'tiscali' => ['host' => 'imap.tiscali.it', 'port' => 993],
|
|
'bluewin' => ['host' => 'imaps.bluewin.ch', 'port' => 993],
|
|
'mail' => ['host' => 'imap.mail.ru', 'port' => 993],
|
|
'yandex' => ['host' => 'imap.yandex.com', 'port' => 993],
|
|
'rambler' => ['host' => 'imap.rambler.ru', 'port' => 993],
|
|
|
|
// Canadian providers
|
|
'videotron' => ['host' => 'imap.videotron.ca', 'port' => 993],
|
|
'bell' => ['host' => 'imap.bell.net', 'port' => 993],
|
|
'sympatico' => ['host' => 'imap.bell.net', 'port' => 993],
|
|
'rogers' => ['host' => 'imap.rogers.com', 'port' => 993],
|
|
'shaw' => ['host' => 'imap.shaw.ca', 'port' => 993],
|
|
'telus' => ['host' => 'imap.telus.net', 'port' => 993],
|
|
|
|
// US providers
|
|
'comcast' => ['host' => 'imap.comcast.net', 'port' => 993],
|
|
'xfinity' => ['host' => 'imap.comcast.net', 'port' => 993],
|
|
'att' => ['host' => 'imap.mail.att.net', 'port' => 993],
|
|
'sbcglobal' => ['host' => 'imap.mail.att.net', 'port' => 993],
|
|
'verizon' => ['host' => 'incoming.verizon.net', 'port' => 993],
|
|
'charter' => ['host' => 'mobile.charter.net', 'port' => 993],
|
|
'spectrum' => ['host' => 'mobile.charter.net', 'port' => 993],
|
|
'cox' => ['host' => 'imap.cox.net', 'port' => 993],
|
|
'earthlink' => ['host' => 'imap.earthlink.net', 'port' => 993],
|
|
'frontier' => ['host' => 'mail.frontier.com', 'port' => 993],
|
|
|
|
// Apple
|
|
'icloud' => ['host' => 'imap.mail.me.com', 'port' => 993],
|
|
'me' => ['host' => 'imap.mail.me.com', 'port' => 993],
|
|
'mac' => ['host' => 'imap.mail.me.com', 'port' => 993],
|
|
|
|
// Business
|
|
'zoho' => ['host' => 'imap.zoho.com', 'port' => 993],
|
|
'protonmail' => ['host' => 'imap.protonmail.ch', 'port' => 993],
|
|
'fastmail' => ['host' => 'imap.fastmail.com', 'port' => 993],
|
|
'mailbox' => ['host' => 'imap.mailbox.org', 'port' => 993],
|
|
'tutanota' => ['host' => 'imap.tutanota.com', 'port' => 993],
|
|
|
|
// UK
|
|
'btinternet' => ['host' => 'mail.btinternet.com', 'port' => 993],
|
|
'virginmedia' => ['host' => 'imap.virginmedia.com', 'port' => 993],
|
|
'sky' => ['host' => 'imap.tools.sky.com', 'port' => 993],
|
|
'talktalk' => ['host' => 'imap.talktalk.net', 'port' => 993],
|
|
|
|
// Australia
|
|
'bigpond' => ['host' => 'imap.telstra.com', 'port' => 993],
|
|
'optusnet' => ['host' => 'mail.optusnet.com.au', 'port' => 993],
|
|
|
|
// Internal (Plesk/cPanel)
|
|
'internal' => ['host' => 'mail.{domain}', 'port' => 993],
|
|
];
|
|
|
|
// Folder mappings per ISP type
|
|
private static $folderMappings = [
|
|
'gmail' => [
|
|
'inbox' => 'INBOX',
|
|
'spam' => '[Gmail]/Spam',
|
|
'draft' => '[Gmail]/Drafts',
|
|
'sent' => '[Gmail]/Sent Mail',
|
|
'trash' => '[Gmail]/Trash',
|
|
'archive' => '[Gmail]/All Mail',
|
|
],
|
|
'yahoo' => [
|
|
'inbox' => 'INBOX',
|
|
'spam' => 'Bulk Mail',
|
|
'draft' => 'Draft',
|
|
'sent' => 'Sent',
|
|
'trash' => 'Trash',
|
|
'archive' => 'Archive',
|
|
],
|
|
'outlook' => [
|
|
'inbox' => 'INBOX',
|
|
'spam' => 'Junk',
|
|
'draft' => 'Drafts',
|
|
'sent' => 'Sent',
|
|
'trash' => 'Deleted',
|
|
'archive' => 'Archive',
|
|
],
|
|
'default' => [
|
|
'inbox' => 'INBOX',
|
|
'spam' => 'Spam',
|
|
'draft' => 'Drafts',
|
|
'sent' => 'Sent',
|
|
'trash' => 'Trash',
|
|
'archive' => 'Archive',
|
|
],
|
|
];
|
|
|
|
/**
|
|
* Extract emails from mailboxes
|
|
*/
|
|
public static function extract(array $params): array
|
|
{
|
|
$mailboxes = $params['mailboxes'] ?? [];
|
|
$folder = $params['folder'] ?? 'inbox';
|
|
$maxEmails = intval($params['max-emails'] ?? 10);
|
|
$order = $params['order'] ?? 'new-to-old';
|
|
$startDate = $params['start-date'] ?? '';
|
|
$endDate = $params['end-date'] ?? '';
|
|
$separator = $params['separator'] ?? '_SEPARATOR_';
|
|
$returnType = $params['return-type'] ?? 'full-source';
|
|
$returnHeaderKey = $params['return-header-key'] ?? 'none';
|
|
$filters = $params['filters'] ?? [];
|
|
$filterType = $params['filter-type'] ?? 'and';
|
|
|
|
$results = [];
|
|
|
|
foreach ($mailboxes as $mailboxLine) {
|
|
$parts = preg_split('/\s+/', trim($mailboxLine), 4);
|
|
if (count($parts) < 2) continue;
|
|
|
|
$email = strtolower(trim($parts[0]));
|
|
$password = trim($parts[1]);
|
|
$proxyHost = $parts[2] ?? null;
|
|
$proxyPort = $parts[3] ?? null;
|
|
|
|
try {
|
|
$emailResults = self::extractFromMailbox(
|
|
$email,
|
|
$password,
|
|
$folder,
|
|
$maxEmails,
|
|
$order,
|
|
$startDate,
|
|
$endDate,
|
|
$separator,
|
|
$returnType,
|
|
$returnHeaderKey,
|
|
$filters,
|
|
$filterType
|
|
);
|
|
|
|
if (!empty($emailResults)) {
|
|
$results[$email] = implode("\n" . $separator . "\n", $emailResults);
|
|
}
|
|
} catch (\Exception $e) {
|
|
$results[$email] = "Error: " . $e->getMessage();
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Extract from a single mailbox
|
|
*/
|
|
private static function extractFromMailbox(
|
|
string $email,
|
|
string $password,
|
|
string $folder,
|
|
int $maxEmails,
|
|
string $order,
|
|
string $startDate,
|
|
string $endDate,
|
|
string $separator,
|
|
string $returnType,
|
|
string $returnHeaderKey,
|
|
array $filters,
|
|
string $filterType
|
|
): array {
|
|
// Get domain and ISP
|
|
$domain = substr($email, strpos($email, '@') + 1);
|
|
$ispParts = explode('.', $domain);
|
|
$isp = strtolower($ispParts[0]);
|
|
|
|
// Handle t-online special case
|
|
if (strpos($domain, 't-online') !== false) {
|
|
$isp = 't-online';
|
|
}
|
|
|
|
// Get IMAP settings
|
|
$imapSettings = self::getImapSettings($isp, $domain);
|
|
$folderName = self::getFolderName($isp, $folder);
|
|
|
|
// Build connection string
|
|
$host = $imapSettings['host'];
|
|
$port = $imapSettings['port'];
|
|
$connectionString = "{{$host}:{$port}/imap/ssl/novalidate-cert}{$folderName}";
|
|
|
|
// Connect
|
|
$mailbox = @imap_open($connectionString, $email, $password);
|
|
|
|
if (!$mailbox) {
|
|
$error = imap_last_error();
|
|
throw new \Exception("Connection failed: " . $error);
|
|
}
|
|
|
|
try {
|
|
// Build search criteria
|
|
$searchCriteria = 'ALL';
|
|
|
|
if (!empty($startDate) && !empty($endDate)) {
|
|
$start = date('d-M-Y', strtotime($startDate));
|
|
$end = date('d-M-Y', strtotime($endDate));
|
|
$searchCriteria = "SINCE \"$start\" BEFORE \"$end\"";
|
|
} elseif (!empty($startDate)) {
|
|
$start = date('d-M-Y', strtotime($startDate));
|
|
$searchCriteria = "SINCE \"$start\"";
|
|
} elseif (!empty($endDate)) {
|
|
$end = date('d-M-Y', strtotime($endDate));
|
|
$searchCriteria = "BEFORE \"$end\"";
|
|
}
|
|
|
|
// Search messages
|
|
$messageIds = imap_search($mailbox, $searchCriteria);
|
|
|
|
if (!$messageIds || count($messageIds) === 0) {
|
|
imap_close($mailbox);
|
|
return [];
|
|
}
|
|
|
|
// Sort by order
|
|
if ($order === 'new-to-old') {
|
|
rsort($messageIds);
|
|
} else {
|
|
sort($messageIds);
|
|
}
|
|
|
|
// Limit
|
|
$messageIds = array_slice($messageIds, 0, $maxEmails);
|
|
|
|
// Extract messages
|
|
$results = [];
|
|
foreach ($messageIds as $msgId) {
|
|
$content = self::extractMessage($mailbox, $msgId, $returnType, $returnHeaderKey);
|
|
|
|
// Apply filters if any
|
|
if (!empty($filters) && !self::passesFilters($content, $filters, $filterType)) {
|
|
continue;
|
|
}
|
|
|
|
if (!empty($content)) {
|
|
$results[] = $content;
|
|
}
|
|
}
|
|
|
|
imap_close($mailbox);
|
|
return $results;
|
|
|
|
} catch (\Exception $e) {
|
|
@imap_close($mailbox);
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extract a single message
|
|
*/
|
|
private static function extractMessage($mailbox, int $msgId, string $returnType, string $returnHeaderKey): string
|
|
{
|
|
switch ($returnType) {
|
|
case 'full-source':
|
|
// Get complete raw source
|
|
$header = imap_fetchheader($mailbox, $msgId);
|
|
$body = imap_body($mailbox, $msgId);
|
|
return $header . "\r\n" . $body;
|
|
|
|
case 'full-header':
|
|
// Get all headers
|
|
$headerInfo = imap_fetchheader($mailbox, $msgId);
|
|
return $headerInfo;
|
|
|
|
case 'full-body':
|
|
// Get body text only
|
|
return self::getBodyText($mailbox, $msgId);
|
|
|
|
case 'header-value':
|
|
// Get specific header values
|
|
if ($returnHeaderKey === 'none' || empty($returnHeaderKey)) {
|
|
return '';
|
|
}
|
|
$keys = explode('|', $returnHeaderKey);
|
|
$headers = imap_rfc822_parse_headers(imap_fetchheader($mailbox, $msgId));
|
|
$result = [];
|
|
|
|
foreach ($keys as $key) {
|
|
$key = strtolower(trim($key));
|
|
$headerObj = (array)$headers;
|
|
|
|
// Map common header names
|
|
$headerMap = [
|
|
'from' => 'fromaddress',
|
|
'to' => 'toaddress',
|
|
'subject' => 'subject',
|
|
'date' => 'date',
|
|
'reply-to' => 'reply_toaddress',
|
|
'cc' => 'ccaddress',
|
|
'bcc' => 'bccaddress',
|
|
];
|
|
|
|
if (isset($headerMap[$key]) && isset($headerObj[$headerMap[$key]])) {
|
|
$result[] = ucfirst($key) . ":" . $headerObj[$headerMap[$key]];
|
|
} else {
|
|
// Try to get from raw headers
|
|
$rawHeaders = imap_fetchheader($mailbox, $msgId);
|
|
if (preg_match('/^' . preg_quote($key, '/') . ':\s*(.*)$/mi', $rawHeaders, $matches)) {
|
|
$result[] = ucfirst($key) . ":" . trim($matches[1]);
|
|
}
|
|
}
|
|
}
|
|
return implode("\n", $result);
|
|
|
|
default:
|
|
return '';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get body text from message
|
|
*/
|
|
private static function getBodyText($mailbox, int $msgId): string
|
|
{
|
|
$structure = imap_fetchstructure($mailbox, $msgId);
|
|
|
|
if (!$structure->parts) {
|
|
// Simple message
|
|
$body = imap_body($mailbox, $msgId);
|
|
if ($structure->encoding == 3) {
|
|
$body = base64_decode($body);
|
|
} elseif ($structure->encoding == 4) {
|
|
$body = quoted_printable_decode($body);
|
|
}
|
|
return $body;
|
|
}
|
|
|
|
// Multipart message
|
|
return self::getPartText($mailbox, $msgId, $structure);
|
|
}
|
|
|
|
/**
|
|
* Recursively get text parts
|
|
*/
|
|
private static function getPartText($mailbox, int $msgId, $structure, string $partNumber = ''): string
|
|
{
|
|
$text = '';
|
|
|
|
if (!isset($structure->parts) || !$structure->parts) {
|
|
if ($structure->subtype === 'PLAIN') {
|
|
$body = $partNumber ? imap_fetchbody($mailbox, $msgId, $partNumber) : imap_body($mailbox, $msgId);
|
|
if ($structure->encoding == 3) {
|
|
$body = base64_decode($body);
|
|
} elseif ($structure->encoding == 4) {
|
|
$body = quoted_printable_decode($body);
|
|
}
|
|
return $body;
|
|
}
|
|
return '';
|
|
}
|
|
|
|
foreach ($structure->parts as $index => $part) {
|
|
$subPartNumber = $partNumber ? $partNumber . '.' . ($index + 1) : (string)($index + 1);
|
|
$text .= self::getPartText($mailbox, $msgId, $part, $subPartNumber) . "\n";
|
|
}
|
|
|
|
return $text;
|
|
}
|
|
|
|
/**
|
|
* Check if message passes filters
|
|
*/
|
|
private static function passesFilters(string $content, array $filters, string $filterType): bool
|
|
{
|
|
if (empty($filters)) return true;
|
|
|
|
$results = [];
|
|
foreach ($filters as $filter) {
|
|
$key = $filter['key'] ?? '';
|
|
$condition = $filter['condition'] ?? 'contains';
|
|
$value = $filter['value'] ?? '';
|
|
|
|
if (empty($key) || empty($value)) continue;
|
|
|
|
// Search for the header in content
|
|
$pattern = '/^' . preg_quote($key, '/') . ':\s*(.*)$/mi';
|
|
if (preg_match($pattern, $content, $matches)) {
|
|
$headerValue = trim($matches[1]);
|
|
|
|
switch ($condition) {
|
|
case 'equals':
|
|
$results[] = (strtolower($headerValue) === strtolower($value));
|
|
break;
|
|
case 'not-equals':
|
|
$results[] = (strtolower($headerValue) !== strtolower($value));
|
|
break;
|
|
case 'contains':
|
|
$results[] = (stripos($headerValue, $value) !== false);
|
|
break;
|
|
case 'not-contains':
|
|
$results[] = (stripos($headerValue, $value) === false);
|
|
break;
|
|
default:
|
|
$results[] = true;
|
|
}
|
|
} else {
|
|
$results[] = false;
|
|
}
|
|
}
|
|
|
|
if (empty($results)) return true;
|
|
|
|
if ($filterType === 'and') {
|
|
return !in_array(false, $results, true);
|
|
} else {
|
|
return in_array(true, $results, true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get IMAP settings for an ISP
|
|
*/
|
|
private static function getImapSettings(string $isp, string $domain): array
|
|
{
|
|
// Check if known ISP
|
|
if (isset(self::$imapServers[$isp])) {
|
|
$settings = self::$imapServers[$isp];
|
|
// Replace {domain} placeholder for internal/custom domains
|
|
$settings['host'] = str_replace('{domain}', $domain, $settings['host']);
|
|
return $settings;
|
|
}
|
|
|
|
// Try common patterns for unknown ISPs
|
|
$commonPatterns = [
|
|
'imap.' . $domain,
|
|
'mail.' . $domain,
|
|
'imap.mail.' . $domain,
|
|
$domain,
|
|
];
|
|
|
|
foreach ($commonPatterns as $host) {
|
|
if (@fsockopen($host, 993, $errno, $errstr, 3)) {
|
|
return ['host' => $host, 'port' => 993];
|
|
}
|
|
}
|
|
|
|
// Default fallback - try imap.domain
|
|
return ['host' => 'imap.' . $domain, 'port' => 993];
|
|
}
|
|
|
|
/**
|
|
* Get folder name for ISP
|
|
*/
|
|
private static function getFolderName(string $isp, string $folder): string
|
|
{
|
|
// Determine ISP type for folder mapping
|
|
$ispType = 'default';
|
|
|
|
if (in_array($isp, ['gmail', 'googlemail'])) {
|
|
$ispType = 'gmail';
|
|
} elseif (in_array($isp, ['yahoo', 'ymail'])) {
|
|
$ispType = 'yahoo';
|
|
} elseif (in_array($isp, ['hotmail', 'outlook', 'live', 'msn', 'onmicrosoft', 'windowslive'])) {
|
|
$ispType = 'outlook';
|
|
}
|
|
|
|
$mappings = self::$folderMappings[$ispType] ?? self::$folderMappings['default'];
|
|
|
|
return $mappings[$folder] ?? 'INBOX';
|
|
}
|
|
}
|