-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathThreadEmailExtractorEmailBody.php
More file actions
207 lines (177 loc) · 6.71 KB
/
Copy pathThreadEmailExtractorEmailBody.php
File metadata and controls
207 lines (177 loc) · 6.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
<?php
require_once __DIR__ . '/../Database.php';
require_once __DIR__ . '/../Extraction/ThreadEmailExtraction.php';
require_once __DIR__ . '/../Extraction/ThreadEmailExtractionService.php';
require_once __DIR__ . '/../Extraction/ThreadEmailExtractor.php';
require_once __DIR__ . '/../ThreadEmail.php';
require_once __DIR__ . '/../ThreadStorageManager.php';
require_once __DIR__ . '/../../error.php';
use ZBateson\MailMimeParser\MailMimeParser;
use ZBateson\MailMimeParser\Message;
/**
* Class for extracting text from email bodies
* Used as foundation for automatic classification and follow up
*/
class ThreadEmailExtractorEmailBody extends ThreadEmailExtractor {
/**
* Get the number of emails that need extraction
*
* @return int Number of emails to process
*/
public function getNumberOfEmailsToProcess() {
// Count the number of emails that need extraction
$query = "
SELECT COUNT(te.id) AS email_count
FROM thread_emails te
LEFT JOIN thread_email_extractions tee ON te.id = tee.email_id
AND tee.attachment_id IS NULL
AND tee.prompt_service = 'code'
AND tee.prompt_text = 'email_body'
WHERE tee.extraction_id IS NULL
";
$result = Database::queryOneOrNone($query, []);
return $result ? (int)$result['email_count'] : 0;
}
/**
* Find the next email that needs extraction
*
* @return array|null Email data or null if none found
*/
public function findNextEmailForExtraction() {
// Find emails that don't have a body text extraction yet
$query = "
SELECT te.id as email_id, te.thread_id, te.status_type, te.status_text
FROM thread_emails te
LEFT JOIN thread_email_extractions tee ON te.id = tee.email_id
AND tee.attachment_id IS NULL
AND tee.prompt_service = 'code'
AND tee.prompt_text = 'email_body'
WHERE tee.extraction_id IS NULL
ORDER BY te.datetime_received ASC
LIMIT 1
";
$row = Database::queryOneOrNone($query, []);
if (!$row) {
return null;
}
return $row;
}
public function processNextEmailExtraction() {
return $this->processNextEmailExtractionInternal(
'email_body',
'code',
function($email, $prompt_text, $prompt_service, $extraction_id) {
// Extract text from email body
$extractedTexts = $this->extractTextFromEmailBody($email['thread_id'], $email['email_id']);
$extractedText = '';
if (!empty($extractedTexts->plain_text)) {
$extractedText .= $extractedTexts->plain_text;
}
if (!empty($extractedTexts->html)) {
$extractedText = trim($extractedText . "\n\n" . $extractedTexts->html);
}
return $extractedText;
}
);
}
/**
* Extract text from email body
*
* @param string $emailId Email ID
* @return ExtractedEmailBody Extracted text
*/
protected function extractTextFromEmailBody($threadId, $emailId) {
$eml = ThreadStorageManager::getInstance()->getThreadEmailContent($threadId, $emailId);
$email_content = self::extractContentFromEmail($eml);
return $email_content;
}
/**
* Parse raw email content using Zbateson mail-mime-parser
*
* @param string $eml Raw email content
* @return Message Parsed message object
*/
public static function parseEmail(string $eml): Message {
$parser = new MailMimeParser();
return $parser->parse($eml, false);
}
/**
* Extract content from a raw email string
*
* @param string $eml Raw email content
* @return ExtractedEmailBody Extracted email body content
*/
public static function extractContentFromEmail($eml) {
if (empty($eml)) {
throw new Exception("Empty email content provided for extraction");
}
try {
$message = self::parseEmail($eml);
} catch (Exception $e) {
error_log("Error parsing email content: " . $e->getMessage() . " . EML length: " . strlen($eml));
$email_content = new ExtractedEmailBody();
$email_content->plain_text = "ERROR\n\n".$eml;
$email_content->html = '<pre>' . jTraceEx($e) . '</pre>';
return $email_content;
}
$email_content = new ExtractedEmailBody();
// Zbateson handles all encoding/decoding automatically
$plainText = $message->getTextContent();
$html = $message->getHtmlContent();
// Clean up extracted content
// Zbateson handles charset conversion and always returns valid UTF-8
if ($plainText !== null) {
$email_content->plain_text = self::cleanText($plainText);
} else {
$email_content->plain_text = '';
}
if ($html !== null) {
$email_content->html = self::convertHtmlToText($html);
} else {
$email_content->html = '';
}
return $email_content;
}
/**
* Convert HTML to plain text
*
* @param string $html HTML content
* @return string Plain text
*/
protected static function convertHtmlToText($html) {
// Remove scripts, styles, and comments
$html = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', '', $html);
$html = preg_replace('/<style\b[^>]*>(.*?)<\/style>/is', '', $html);
$html = preg_replace('/<!--(.*?)-->/is', '', $html);
// Replace common HTML elements with text equivalents
$html = preg_replace('/<br\s*\/?>/i', "\n", $html);
$html = preg_replace('/<\/p>/i', "\n\n", $html);
$html = preg_replace('/<\/h[1-6]>/i', "\n\n", $html);
$html = preg_replace('/<li>/i', "- ", $html);
$html = preg_replace('/<\/li>/i', "\n", $html);
// Remove all remaining HTML tags
$text = strip_tags($html);
// Decode HTML entities
$text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
return $text;
}
/**
* Clean up extracted text
*
* @param string $text Text to clean
* @return string Cleaned text
*/
protected static function cleanText($text) {
// Normalize line endings
$text = str_replace("\r\n", "\n", $text);
$text = str_replace("\r", "\n", $text);
// Remove excessive whitespace
$text = preg_replace('/\n{3,}/', "\n\n", $text);
$text = trim($text);
return $text;
}
}
class ExtractedEmailBody {
public $plain_text;
public $html;
}