3 # Copyright (c) STMicroelectronics, 2005. All Rights Reserved.
5 # Originally written by Jean-Philippe Giola, 2005
7 # This file is a part of codendi.
9 # codendi is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # codendi is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with codendi; if not, write to the Free Software
21 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 require_once 'ForumML_MessageDao.class.php';
27 // ForumML Database Query Class
35 function __construct($list_id) {
37 $this->id_list = $list_id;
38 $this->dao = new ForumML_MessageDao(CodendiDataAccess::instance());
41 // Insert values into forumml_messageheader table
42 function insertMessageHeader($id_header,$value) {
43 $this->dao->insertMessageHeader($this->id_message,$id_header,$value);
46 // Insert values into forumml_attachment table
47 function insertAttachment($id_message, $filename,$filetype,$filepath,$content_id="") {
48 if (is_file($filepath)) {
49 $filesize = filesize($filepath);
53 $this->dao->insertAttachment($id_message, $filename, $filetype, $filesize, $filepath,$content_id);
56 // Insert values into forumml_header table
57 function insertHeader($header) {
59 // Search if the header is already in the table
60 $result = $this->dao->searchHeader($header);
62 if ($result->rowCount()<1) {
63 return $this->dao->insertHeader($header);
65 $row=$result->getRow();
66 return $row['id_header'];
70 function getParentMessageFromHeader($messageIdHeader) {
71 $result = $this->dao->getParentMessageFromHeader($messageIdHeader) ;
72 if ($result && $result->rowCount() >= 1 ) {
73 $row = $result->getRow();
74 return $row['id_message'];
80 function updateParentDate($messageId, $date) {
81 if ($messageId != 0) {
82 $dar = $this->dao->getParents($messageId);
84 $row = $dar->getRow();
85 if ($date > $row['last_thread_update']) {
86 $this->dao->updateParentDate($messageId, $date);
88 $this->updateParentDate($row['id_parent'], $date);
94 // Insert values into forumml_message table
95 function insertMessage($structure,$body,$ctype="") {
97 $this->mail = $structure;
99 if (isset($structure["in-reply-to"])) {
100 // special case: 'in-reply-to' header may contain "Message from ... "
101 if (preg_match('/^Message from.*$/',$structure["in-reply-to"])) {
102 $arr = explode(" ",$structure["in-reply-to"]);
103 $reply_to = $arr[count($structure["in-reply-to"]) - 1];
105 $reply_to = $structure["in-reply-to"];
108 if (isset($structure["references"])) {
109 // special case: 'in-reply-to' header is not set, but 'references' - which contain list of parent messages ids - is set
110 $ref_arr = explode(" ",$structure["references"]);
111 $reply_to = $ref_arr[count($structure["references"]) - 1];
118 // Cannot rely on server's date because it might be different
119 // and it doesn't work when it comes to load mail archives!
120 $messageDate = strtotime($structure['date']);
123 // If the current message is an answer
124 if ($reply_to != "") {
125 $id_parent = $this->getParentMessageFromHeader($reply_to);
128 if ($id_parent != 0) {
129 $this->updateParentDate($id_parent, $messageDate);
131 $this->id_message = $this->dao->insertMessage($this->id_list, $id_parent , $body , $messageDate , $ctype);
133 // All headers of the current mail are stored in the forumml_messageheader table
135 foreach ($structure as $header => $value_header) {
138 if ($header != "received") {
139 $id_header = $this->insertHeader($header);
140 if (is_array($value_header)) {
141 $value_header = implode(",",$value_header);
143 $this->insertMessageHeader($id_header,$value_header);
148 return $this->id_message;
152 * Encode string in UTF8 if source charset given or if detected
154 function getUtf8String($string,$charset=null) {
155 if ($charset == null) {
156 $charset = mb_detect_encoding($string);
159 return mb_convert_encoding($string, 'UTF-8', $charset);
166 * Convert structure body to utf8 if charset defined in structure headers
168 function getUtf8Body($structure) {
170 if (isset($structure->headers["content-type"]) && isset($structure->ctype_parameters['charset'])) {
171 $charset = $structure->ctype_parameters['charset'];
173 if (isset($structure->body)) {
174 return $this->getUtf8String($structure->body, $charset);
181 * Extract from given structure the content and store it as an attachment of the given message
183 * @param Integer $messageId Message id
184 * @param Object $struct Subpart of a Mime message to treat
185 * @param Object $mailHeaders Headers of the message (not the subpart)
186 * @param ForumML_FileStorage $storage Object that manage the file storage on FS
188 function storePart($messageId, $struct, $mailHeaders, $storage) {
189 if (isset($struct->body) && trim($struct->body) != "") {
190 $body = $struct->body;
191 $filetype = $struct->headers["content-type"];
192 if ($struct->ctype_primary == 'text' && $struct->ctype_secondary == 'html') {
193 $filename = "message_".substr($mailHeaders["message-id"], 1, strpos($mailHeaders["message-id"], '@') - 1).".html";
195 if (! isset($struct->d_parameters["filename"])) {
196 // special case where a content is attached, without filename
197 $pos = strpos($filetype,"name=");
198 if ($pos === false) {
199 // set filename to 'attachment_<k>'
200 $filename = "attachment";
202 // get filename from 'name' section
203 $filename = substr(substr($filetype,$pos),6,-1);
206 $filename = $struct->d_parameters["filename"];
209 $basename = basename($filename);
211 // For multipart/related emails
213 if (isset($struct->headers['content-id'])) {
214 $content_id = $struct->headers['content-id'];
217 // store attachment in /var/lib/codendi/forumml/<listname>/<Y_M_D>
218 $date = date("Y_m_d",strtotime($mailHeaders["date"]));
219 $fpath = $storage->store($basename, $struct->body, $this->id_list, $date);
221 // insert attachment in the DB
222 $this->insertAttachment($messageId, $basename, $filetype, $fpath, $content_id);
227 * Parse recursively Mime message to create the message and it's attachments in DB
229 * A MIME message is a hierarchical organization that maybe very
230 * simple for a text message (just one structure with headers and
231 * a text body) to a very complex HTML mail with inline images,
232 * attachments sent in Text+HTML.
234 * The main challenge of this method is to find the "root" of the
235 * MIME message to store it as a message in the DB, all the other
236 * stuff will be attached to this message as an attachment.
238 * The root message can be either:
239 * - The text version of the message. This applies for
240 * -> mail in plain text (with or without attachments)
241 * -> mail in HTML sent in Text+HTML
242 * - If no text version available:
243 * -> if their is an HTML version of the mail, we store it
244 * (happens with mail sent in HTML only).
245 * -> if their is no HTML, we store an empty body.
247 * How do we detect the root message:
248 * -> We crawl the hierarchy and we take the first text/plain or
250 * -> Otherwise, if we are about to store an attachment (an
251 * attachment is everything but first text/plain or first
252 * text/html) we create a empty message.
254 * @see http://en.wikipedia.org/wiki/MIME
256 * @param Object $struct Subpart of a Mime message to treat
257 * @param Object $mailHeaders Headers of the message (not the subpart)
258 * @param ForumML_FileStorage $storage Object that manage the file storage on FS
259 * @param Integer $messageId Message id
261 function storeMime($struct, $mailHeaders, $storage, $messageId=0) {
262 if ($struct->ctype_primary == 'multipart') {
263 foreach ($struct->parts as $part) {
264 $messageId = $this->storeMime($part, $mailHeaders, $storage, $messageId);
268 if ($struct->ctype_primary == 'text') {
269 switch ($struct->ctype_secondary) {
272 if ($messageId == null) {
273 $body = $this->getUtf8Body($struct);
274 if (isset($struct->headers["content-type"])) {
275 $ctype = $struct->headers["content-type"];
279 $messageId = $this->insertMessage($mailHeaders, $body, $ctype);
286 if ($messageId == 0) {
287 if (isset($struct->headers["content-type"])) {
288 $ctype = $struct->headers["content-type"];
292 $messageId = $this->insertMessage($mailHeaders, "", $ctype);
296 $this->storePart($messageId, $struct, $mailHeaders, $storage);
303 * Abandon all hope you who enter here! Mail & MIME is at best a nightmare, take a couple of
304 * bottles before diving into this code...
305 * http://en.wikipedia.org/wiki/MIME
307 * List (not comprehensive) of email possibilities
310 * Text + attached files multipat/mixed (text/plain, other/mime)
311 * -> text_plus_attachment.mbox
312 * HTML (sent in Text + HTML) multipart/alternative (text/plain, text/html)
313 * -> pure_html_text_plus_html.mbox
314 * HTML (sent in HTML) text/html
315 * -> pure_html_in_html_only.mbox
316 * HTML + inline image (sent in Text + HTML) multipart/alternative(text/plain, multipart/related(text/html, image/png))
317 * -> html_with_inline_content_in_text_plus_html.mbox
318 * HTML + inline image (sent in HTML) multipart/related(text/html, image/png)
319 * -> html_with_inline_content_in_html_only.mbox
320 * HTML + attached file (sent in Text + HTML) multipart/mixed(multipart/alternative(text/plain, text/html), other/mime))
321 * HTML + attached file (sent in HTML) multipart/mixed(text/html, other/mime)
322 * HTML + inline image + attached file (sent in Text + HTML) multipart/mixed(multipart/alternative(text/plain, multipart/related(text/html, image/png)), other/mime)
323 * -> html_with_inline_content_and_attch_in_text_plus_html.mbox
324 * HTML + inline image + attached file (sent in HTML) multipart/mixed(multipart/related(text/html, image/png), other/mime)
325 * -> html_with_inline_content_and_attch_in_html_only.mbox
327 public function storeEmail($email, $storage) {
328 return $this->storeMime($email, $email->headers, $storage);