Merge branch '4.4' into master
[civicrm-core.git] / CRM / Utils / Mail / Incoming.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28 /**
29 *
30 * @package CRM
31 * @copyright CiviCRM LLC (c) 2004-2014
32 * $Id$
33 *
34 */
35 class CRM_Utils_Mail_Incoming {
36 CONST
37 EMAILPROCESSOR_CREATE_INDIVIDUAL = 1,
38 EMAILPROCESSOR_OVERRIDE = 2,
39 EMAILPROCESSOR_IGNORE = 3;
40
41 function formatMail($mail, &$attachments) {
42 $t = '';
43 $t .= "From: " . self::formatAddress($mail->from) . "\n";
44 $t .= "To: " . self::formatAddresses($mail->to) . "\n";
45 $t .= "Cc: " . self::formatAddresses($mail->cc) . "\n";
46 $t .= "Bcc: " . self::formatAddresses($mail->bcc) . "\n";
47 $t .= 'Date: ' . date(DATE_RFC822, $mail->timestamp) . "\n";
48 $t .= 'Subject: ' . $mail->subject . "\n";
49 $t .= "MessageId: " . $mail->messageId . "\n";
50 $t .= "\n";
51 $t .= self::formatMailPart($mail->body, $attachments);
52 return $t;
53 }
54
55 function formatMailPart($part, &$attachments) {
56 if ($part instanceof ezcMail) {
57 return self::formatMail($part, $attachments);
58 }
59
60 if ($part instanceof ezcMailText) {
61 return self::formatMailText($part, $attachments);
62 }
63
64 if ($part instanceof ezcMailFile) {
65 return self::formatMailFile($part, $attachments);
66 }
67
68 if ($part instanceof ezcMailRfc822Digest) {
69 return self::formatMailRfc822Digest($part, $attachments);
70 }
71
72 if ($part instanceof ezcMailMultiPart) {
73 return self::formatMailMultipart($part, $attachments);
74 }
75
76 CRM_Core_Error::fatal(ts("No clue about the %1", array(1 => get_class($part))));
77 }
78
79 function formatMailMultipart($part, &$attachments) {
80 if ($part instanceof ezcMailMultiPartAlternative) {
81 return self::formatMailMultipartAlternative($part, $attachments);
82 }
83
84 if ($part instanceof ezcMailMultiPartDigest) {
85 return self::formatMailMultipartDigest($part, $attachments);
86 }
87
88 if ($part instanceof ezcMailMultiPartRelated) {
89 return self::formatMailMultipartRelated($part, $attachments);
90 }
91
92 if ($part instanceof ezcMailMultiPartMixed) {
93 return self::formatMailMultipartMixed($part, $attachments);
94 }
95
96 if ($part instanceof ezcMailMultipartReport) {
97 return self::formatMailMultipartReport($part, $attachments);
98 }
99
100 CRM_Core_Error::fatal(ts("No clue about the %1", array(1 => get_class($part))));
101 }
102
103 function formatMailMultipartMixed($part, &$attachments) {
104 $t = '';
105 foreach ($part->getParts() as $key => $alternativePart) {
106 $t .= self::formatMailPart($alternativePart, $attachments);
107 }
108 return $t;
109 }
110
111 function formatMailMultipartRelated($part, &$attachments) {
112 $t = '';
113 $t .= "-RELATED MAIN PART-\n";
114 $t .= self::formatMailPart($part->getMainPart(), $attachments);
115 foreach ($part->getRelatedParts() as $key => $alternativePart) {
116 $t .= "-RELATED PART $key-\n";
117 $t .= self::formatMailPart($alternativePart, $attachments);
118 }
119 $t .= "-RELATED END-\n";
120 return $t;
121 }
122
123 function formatMailMultipartDigest($part, &$attachments) {
124 $t = '';
125 foreach ($part->getParts() as $key => $alternativePart) {
126 $t .= "-DIGEST-$key-\n";
127 $t .= self::formatMailPart($alternativePart, $attachments);
128 }
129 $t .= "-DIGEST END---\n";
130 return $t;
131 }
132
133 function formatMailRfc822Digest($part, &$attachments) {
134 $t = '';
135 $t .= "-DIGEST-ITEM-\n";
136 $t .= "Item:\n\n";
137 $t .= self::formatMailpart($part->mail, $attachments);
138 $t .= "-DIGEST ITEM END-\n";
139 return $t;
140 }
141
142 function formatMailMultipartAlternative($part, &$attachments) {
143 $t = '';
144 foreach ($part->getParts() as $key => $alternativePart) {
145 $t .= "-ALTERNATIVE ITEM $key-\n";
146 $t .= self::formatMailPart($alternativePart, $attachments);
147 }
148 $t .= "-ALTERNATIVE END-\n";
149 return $t;
150 }
151
152 function formatMailText($part, &$attachments) {
153 $t = "\n{$part->text}\n";
154 return $t;
155 }
156
157 function formatMailMultipartReport($part, &$attachments) {
158 $t = '';
159 foreach ($part->getParts() as $key => $reportPart) {
160 $t .= "-REPORT-$key-\n";
161 $t .= self::formatMailPart($reportPart, $attachments);
162 }
163 $t .= "-REPORT END---\n";
164 return $t;
165 }
166
167 function formatMailFile($part, &$attachments) {
168 $attachments[] = array(
169 'dispositionType' => $part->dispositionType,
170 'contentType' => $part->contentType,
171 'mimeType' => $part->mimeType,
172 'contentID' => $part->contentId,
173 'fullName' => $part->fileName,
174 );
175 return NULL;
176 }
177
178 function formatAddresses($addresses) {
179 $fa = array();
180 foreach ($addresses as $address) {
181 $fa[] = self::formatAddress($address);
182 }
183 return implode(', ', $fa);
184 }
185
186 function formatAddress($address) {
187 $name = '';
188 if (!empty($address->name)) {
189 $name = "{$address->name} ";
190 }
191 return $name . "<{$address->email}>";
192 }
193
194 function &parse(&$file) {
195
196 // check that the file exists and has some content
197 if (!file_exists($file) ||
198 !trim(file_get_contents($file))
199 ) {
200 return CRM_Core_Error::createAPIError(ts('%1 does not exists or is empty',
201 array(1 => $file)
202 ));
203 }
204
205 require_once 'ezc/Base/src/ezc_bootstrap.php';
206 require_once 'ezc/autoload/mail_autoload.php';
207
208 // explode email to digestable format
209 $set = new ezcMailFileSet(array($file));
210 $parser = new ezcMailParser();
211 $mail = $parser->parseMail($set);
212
213 if (!$mail) {
214 return CRM_Core_Error::createAPIError(ts('%1 could not be parsed',
215 array(1 => $file)
216 ));
217 }
218
219 // since we only have one fileset
220 $mail = $mail[0];
221
222 $mailParams = self::parseMailingObject($mail);
223 return $mailParams;
224 }
225
226 function parseMailingObject(&$mail) {
227
228 $config = CRM_Core_Config::singleton();
229
230 // get ready for collecting data about this email
231 // and put it in a standardized format
232 $params = array('is_error' => 0);
233
234 $params['from'] = array();
235 self::parseAddress($mail->from, $field, $params['from'], $mail);
236
237 // we definitely need a contact id for the from address
238 // if we dont have one, skip this email
239 if (empty($params['from']['id'])) {
240 return;
241 }
242
243 $emailFields = array('to', 'cc', 'bcc');
244 foreach ($emailFields as $field) {
245 $value = $mail->$field;
246 self::parseAddresses($value, $field, $params, $mail);
247 }
248
249 // define other parameters
250 $params['subject'] = $mail->subject;
251 $params['date'] = date("YmdHi00",
252 strtotime($mail->getHeader("Date"))
253 );
254 $attachments = array();
255 $params['body'] = self::formatMailPart($mail->body, $attachments);
256
257 // format and move attachments to the civicrm area
258 if (!empty($attachments)) {
259 $date = date('Ymdhis');
260 $config = CRM_Core_Config::singleton();
261 for ($i = 0; $i < count($attachments); $i++) {
262 $attachNum = $i + 1;
263 $fileName = basename($attachments[$i]['fullName']);
264 $newName = CRM_Utils_File::makeFileName($fileName);
265 $location = $config->uploadDir . $newName;
266
267 // move file to the civicrm upload directory
268 rename($attachments[$i]['fullName'], $location);
269
270 $mimeType = "{$attachments[$i]['contentType']}/{$attachments[$i]['mimeType']}";
271
272 $params["attachFile_$attachNum"] = array(
273 'uri' => $fileName,
274 'type' => $mimeType,
275 'upload_date' => $date,
276 'location' => $location,
277 );
278 }
279 }
280
281 return $params;
282 }
283
284 function parseAddress(&$address, &$params, &$subParam, &$mail) {
285 // CRM-9484
286 if (empty($address->email)) {
287 return;
288 }
289
290 $subParam['email'] = $address->email;
291 $subParam['name'] = $address->name;
292
293
294 $contactID = self::getContactID($subParam['email'],
295 $subParam['name'],
296 TRUE,
297 $mail
298 );
299 $subParam['id'] = $contactID ? $contactID : NULL;
300 }
301
302 function parseAddresses(&$addresses, $token, &$params, &$mail) {
303 $params[$token] = array();
304
305 foreach ($addresses as $address) {
306 $subParam = array();
307 self::parseAddress($address, $params, $subParam, $mail);
308 $params[$token][] = $subParam;
309 }
310 }
311
312 /**
313 * retrieve a contact ID and if not present
314 * create one with this email
315 */
316 function getContactID($email, $name = NULL, $create = TRUE, &$mail) {
317 $dao = CRM_Contact_BAO_Contact::matchContactOnEmail($email, 'Individual');
318
319 $contactID = NULL;
320 if ($dao) {
321 $contactID = $dao->contact_id;
322 }
323
324 $result = NULL;
325 CRM_Utils_Hook::emailProcessorContact($email, $contactID, $result);
326
327 if (!empty($result)) {
328 if ($result['action'] == self::EMAILPROCESSOR_IGNORE) {
329 return NULL;
330 }
331 if ($result['action'] == self::EMAILPROCESSOR_OVERRIDE) {
332 return $result['contactID'];
333 }
334
335 // else this is now create individual
336 // so we just fall out and do what we normally do
337 }
338
339 if ($contactID) {
340 return $contactID;
341 }
342
343 if (!$create) {
344 return NULL;
345 }
346
347 // contact does not exist, lets create it
348 $params = array(
349 'contact_type' => 'Individual',
350 'email-Primary' => $email,
351 );
352
353 CRM_Utils_String::extractName($name, $params);
354
355 return CRM_Contact_BAO_Contact::createProfileContact($params,
356 CRM_Core_DAO::$_nullArray
357 );
358 }
359 }
360