Merge pull request #7940 from seamuslee001/CRM-18181
[civicrm-core.git] / CRM / Utils / Mail / Incoming.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
7e9e8871 4 | CiviCRM version 4.7 |
6a488035 5 +--------------------------------------------------------------------+
fa938177 6 | Copyright CiviCRM LLC (c) 2004-2016 |
6a488035
TO
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 +--------------------------------------------------------------------+
d25dd0ee 26 */
6a488035
TO
27
28/**
29 *
30 * @package CRM
fa938177 31 * @copyright CiviCRM LLC (c) 2004-2016
6a488035
TO
32 */
33class CRM_Utils_Mail_Incoming {
7da04cde 34 const
6a488035
TO
35 EMAILPROCESSOR_CREATE_INDIVIDUAL = 1,
36 EMAILPROCESSOR_OVERRIDE = 2,
37 EMAILPROCESSOR_IGNORE = 3;
38
5bc392e6
EM
39 /**
40 * @param $mail
41 * @param $attachments
42 *
43 * @return string
44 */
00be9182 45 public function formatMail($mail, &$attachments) {
6a488035
TO
46 $t = '';
47 $t .= "From: " . self::formatAddress($mail->from) . "\n";
48 $t .= "To: " . self::formatAddresses($mail->to) . "\n";
49 $t .= "Cc: " . self::formatAddresses($mail->cc) . "\n";
50 $t .= "Bcc: " . self::formatAddresses($mail->bcc) . "\n";
51 $t .= 'Date: ' . date(DATE_RFC822, $mail->timestamp) . "\n";
52 $t .= 'Subject: ' . $mail->subject . "\n";
53 $t .= "MessageId: " . $mail->messageId . "\n";
54 $t .= "\n";
55 $t .= self::formatMailPart($mail->body, $attachments);
56 return $t;
57 }
58
5bc392e6
EM
59 /**
60 * @param $part
61 * @param $attachments
62 *
63 * @throws Exception
64 */
a5989abf 65 public static function formatMailPart($part, &$attachments) {
6a488035
TO
66 if ($part instanceof ezcMail) {
67 return self::formatMail($part, $attachments);
68 }
69
70 if ($part instanceof ezcMailText) {
71 return self::formatMailText($part, $attachments);
72 }
73
74 if ($part instanceof ezcMailFile) {
75 return self::formatMailFile($part, $attachments);
76 }
77
78 if ($part instanceof ezcMailRfc822Digest) {
79 return self::formatMailRfc822Digest($part, $attachments);
80 }
81
82 if ($part instanceof ezcMailMultiPart) {
83 return self::formatMailMultipart($part, $attachments);
84 }
85
86 CRM_Core_Error::fatal(ts("No clue about the %1", array(1 => get_class($part))));
87 }
88
5bc392e6
EM
89 /**
90 * @param $part
91 * @param $attachments
92 *
93 * @throws Exception
94 */
00be9182 95 public function formatMailMultipart($part, &$attachments) {
6a488035
TO
96 if ($part instanceof ezcMailMultiPartAlternative) {
97 return self::formatMailMultipartAlternative($part, $attachments);
98 }
99
100 if ($part instanceof ezcMailMultiPartDigest) {
101 return self::formatMailMultipartDigest($part, $attachments);
102 }
103
104 if ($part instanceof ezcMailMultiPartRelated) {
105 return self::formatMailMultipartRelated($part, $attachments);
106 }
107
108 if ($part instanceof ezcMailMultiPartMixed) {
109 return self::formatMailMultipartMixed($part, $attachments);
110 }
111
112 if ($part instanceof ezcMailMultipartReport) {
113 return self::formatMailMultipartReport($part, $attachments);
114 }
115
116 CRM_Core_Error::fatal(ts("No clue about the %1", array(1 => get_class($part))));
117 }
118
5bc392e6
EM
119 /**
120 * @param $part
121 * @param $attachments
122 *
123 * @return string
124 */
00be9182 125 public function formatMailMultipartMixed($part, &$attachments) {
6a488035
TO
126 $t = '';
127 foreach ($part->getParts() as $key => $alternativePart) {
128 $t .= self::formatMailPart($alternativePart, $attachments);
129 }
130 return $t;
131 }
132
5bc392e6
EM
133 /**
134 * @param $part
135 * @param $attachments
136 *
137 * @return string
138 */
00be9182 139 public function formatMailMultipartRelated($part, &$attachments) {
6a488035
TO
140 $t = '';
141 $t .= "-RELATED MAIN PART-\n";
142 $t .= self::formatMailPart($part->getMainPart(), $attachments);
143 foreach ($part->getRelatedParts() as $key => $alternativePart) {
144 $t .= "-RELATED PART $key-\n";
145 $t .= self::formatMailPart($alternativePart, $attachments);
146 }
147 $t .= "-RELATED END-\n";
148 return $t;
149 }
150
5bc392e6
EM
151 /**
152 * @param $part
153 * @param $attachments
154 *
155 * @return string
156 */
00be9182 157 public function formatMailMultipartDigest($part, &$attachments) {
6a488035
TO
158 $t = '';
159 foreach ($part->getParts() as $key => $alternativePart) {
160 $t .= "-DIGEST-$key-\n";
161 $t .= self::formatMailPart($alternativePart, $attachments);
162 }
163 $t .= "-DIGEST END---\n";
164 return $t;
165 }
166
5bc392e6
EM
167 /**
168 * @param $part
169 * @param $attachments
170 *
171 * @return string
172 */
00be9182 173 public function formatMailRfc822Digest($part, &$attachments) {
6a488035
TO
174 $t = '';
175 $t .= "-DIGEST-ITEM-\n";
176 $t .= "Item:\n\n";
177 $t .= self::formatMailpart($part->mail, $attachments);
178 $t .= "-DIGEST ITEM END-\n";
179 return $t;
180 }
181
5bc392e6
EM
182 /**
183 * @param $part
184 * @param $attachments
185 *
186 * @return string
187 */
00be9182 188 public function formatMailMultipartAlternative($part, &$attachments) {
6a488035
TO
189 $t = '';
190 foreach ($part->getParts() as $key => $alternativePart) {
191 $t .= "-ALTERNATIVE ITEM $key-\n";
192 $t .= self::formatMailPart($alternativePart, $attachments);
193 }
194 $t .= "-ALTERNATIVE END-\n";
195 return $t;
196 }
197
5bc392e6
EM
198 /**
199 * @param $part
200 * @param $attachments
201 *
202 * @return string
203 */
a5989abf 204 public static function formatMailText($part, &$attachments) {
6a488035
TO
205 $t = "\n{$part->text}\n";
206 return $t;
207 }
208
5bc392e6
EM
209 /**
210 * @param $part
211 * @param $attachments
212 *
213 * @return string
214 */
00be9182 215 public function formatMailMultipartReport($part, &$attachments) {
6a488035
TO
216 $t = '';
217 foreach ($part->getParts() as $key => $reportPart) {
218 $t .= "-REPORT-$key-\n";
219 $t .= self::formatMailPart($reportPart, $attachments);
220 }
221 $t .= "-REPORT END---\n";
222 return $t;
223 }
224
5bc392e6
EM
225 /**
226 * @param $part
227 * @param $attachments
228 *
229 * @return null
230 */
00be9182 231 public function formatMailFile($part, &$attachments) {
6a488035
TO
232 $attachments[] = array(
233 'dispositionType' => $part->dispositionType,
234 'contentType' => $part->contentType,
235 'mimeType' => $part->mimeType,
236 'contentID' => $part->contentId,
237 'fullName' => $part->fileName,
238 );
239 return NULL;
240 }
241
5bc392e6
EM
242 /**
243 * @param $addresses
244 *
245 * @return string
246 */
00be9182 247 public function formatAddresses($addresses) {
6a488035
TO
248 $fa = array();
249 foreach ($addresses as $address) {
250 $fa[] = self::formatAddress($address);
251 }
252 return implode(', ', $fa);
253 }
254
5bc392e6
EM
255 /**
256 * @param $address
257 *
258 * @return string
259 */
00be9182 260 public function formatAddress($address) {
6a488035
TO
261 $name = '';
262 if (!empty($address->name)) {
263 $name = "{$address->name} ";
264 }
265 return $name . "<{$address->email}>";
266 }
267
5bc392e6
EM
268 /**
269 * @param $file
270 *
271 * @return array
272 * @throws Exception
273 */
00be9182 274 public function &parse(&$file) {
6a488035
TO
275
276 // check that the file exists and has some content
277 if (!file_exists($file) ||
278 !trim(file_get_contents($file))
279 ) {
280 return CRM_Core_Error::createAPIError(ts('%1 does not exists or is empty',
353ffa53
TO
281 array(1 => $file)
282 ));
6a488035
TO
283 }
284
6a488035 285 // explode email to digestable format
353ffa53 286 $set = new ezcMailFileSet(array($file));
6a488035 287 $parser = new ezcMailParser();
353ffa53 288 $mail = $parser->parseMail($set);
6a488035
TO
289
290 if (!$mail) {
291 return CRM_Core_Error::createAPIError(ts('%1 could not be parsed',
353ffa53
TO
292 array(1 => $file)
293 ));
6a488035
TO
294 }
295
296 // since we only have one fileset
297 $mail = $mail[0];
298
299 $mailParams = self::parseMailingObject($mail);
300 return $mailParams;
301 }
302
5bc392e6
EM
303 /**
304 * @param $mail
305 *
306 * @return array
307 */
a5989abf 308 public static function parseMailingObject(&$mail) {
6a488035
TO
309
310 $config = CRM_Core_Config::singleton();
311
312 // get ready for collecting data about this email
313 // and put it in a standardized format
314 $params = array('is_error' => 0);
315
316 $params['from'] = array();
317 self::parseAddress($mail->from, $field, $params['from'], $mail);
318
319 // we definitely need a contact id for the from address
320 // if we dont have one, skip this email
321 if (empty($params['from']['id'])) {
408b79bf 322 return NULL;
6a488035
TO
323 }
324
325 $emailFields = array('to', 'cc', 'bcc');
326 foreach ($emailFields as $field) {
327 $value = $mail->$field;
328 self::parseAddresses($value, $field, $params, $mail);
329 }
330
331 // define other parameters
332 $params['subject'] = $mail->subject;
333 $params['date'] = date("YmdHi00",
334 strtotime($mail->getHeader("Date"))
335 );
336 $attachments = array();
337 $params['body'] = self::formatMailPart($mail->body, $attachments);
338
339 // format and move attachments to the civicrm area
340 if (!empty($attachments)) {
341 $date = date('Ymdhis');
342 $config = CRM_Core_Config::singleton();
343 for ($i = 0; $i < count($attachments); $i++) {
344 $attachNum = $i + 1;
353ffa53
TO
345 $fileName = basename($attachments[$i]['fullName']);
346 $newName = CRM_Utils_File::makeFileName($fileName);
347 $location = $config->uploadDir . $newName;
6a488035
TO
348
349 // move file to the civicrm upload directory
350 rename($attachments[$i]['fullName'], $location);
351
352 $mimeType = "{$attachments[$i]['contentType']}/{$attachments[$i]['mimeType']}";
353
354 $params["attachFile_$attachNum"] = array(
355 'uri' => $fileName,
356 'type' => $mimeType,
357 'upload_date' => $date,
358 'location' => $location,
359 );
360 }
361 }
362
363 return $params;
364 }
365
5bc392e6
EM
366 /**
367 * @param $address
c490a46a 368 * @param array $params
5bc392e6
EM
369 * @param $subParam
370 * @param $mail
371 */
a5989abf 372 public static function parseAddress(&$address, &$params, &$subParam, &$mail) {
6a488035
TO
373 // CRM-9484
374 if (empty($address->email)) {
375 return;
376 }
377
378 $subParam['email'] = $address->email;
379 $subParam['name'] = $address->name;
380
6a488035
TO
381 $contactID = self::getContactID($subParam['email'],
382 $subParam['name'],
383 TRUE,
384 $mail
385 );
386 $subParam['id'] = $contactID ? $contactID : NULL;
387 }
388
5bc392e6
EM
389 /**
390 * @param $addresses
391 * @param $token
c490a46a 392 * @param array $params
5bc392e6
EM
393 * @param $mail
394 */
a5989abf 395 public static function parseAddresses(&$addresses, $token, &$params, &$mail) {
6a488035
TO
396 $params[$token] = array();
397
398 foreach ($addresses as $address) {
399 $subParam = array();
400 self::parseAddress($address, $params, $subParam, $mail);
401 $params[$token][] = $subParam;
402 }
403 }
404
405 /**
fe482240 406 * Retrieve a contact ID and if not present.
ea3ddccf 407 *
408 * Create one with this email
409 *
410 * @param string $email
411 * @param string $name
412 * @param bool $create
413 * @param string $mail
414 *
415 * @return int|null
6a488035 416 */
a5989abf 417 public static function getContactID($email, $name = NULL, $create = TRUE, &$mail) {
6a488035
TO
418 $dao = CRM_Contact_BAO_Contact::matchContactOnEmail($email, 'Individual');
419
420 $contactID = NULL;
421 if ($dao) {
422 $contactID = $dao->contact_id;
423 }
424
425 $result = NULL;
426 CRM_Utils_Hook::emailProcessorContact($email, $contactID, $result);
427
428 if (!empty($result)) {
429 if ($result['action'] == self::EMAILPROCESSOR_IGNORE) {
430 return NULL;
431 }
432 if ($result['action'] == self::EMAILPROCESSOR_OVERRIDE) {
433 return $result['contactID'];
434 }
435
436 // else this is now create individual
437 // so we just fall out and do what we normally do
438 }
439
440 if ($contactID) {
441 return $contactID;
442 }
443
444 if (!$create) {
445 return NULL;
446 }
447
448 // contact does not exist, lets create it
449 $params = array(
450 'contact_type' => 'Individual',
451 'email-Primary' => $email,
452 );
453
454 CRM_Utils_String::extractName($name, $params);
455
456 return CRM_Contact_BAO_Contact::createProfileContact($params,
457 CRM_Core_DAO::$_nullArray
458 );
459 }
96025800 460
6a488035 461}