1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10: 11: 12:
13:
14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24:
25: class Jyxo_Mail_Parser
26: {
27: 28: 29: 30: 31: 32: 33:
34: const BODY = 0;
35:
36: 37: 38: 39: 40: 41:
42: const BODY_INFO = 1;
43:
44: 45: 46: 47: 48: 49:
50: const BODY_LITERAL = 2;
51:
52: 53: 54: 55: 56: 57:
58: const BODY_LITERAL_DECODE = 3;
59:
60: 61: 62: 63: 64:
65: private $connection = null;
66:
67: 68: 69: 70: 71:
72: private $uid = null;
73:
74: 75: 76: 77: 78:
79: private $structure = array();
80:
81: 82: 83: 84: 85:
86: private $defaultPid = null;
87:
88: 89: 90: 91: 92:
93: private $parts = array();
94:
95: 96: 97: 98: 99:
100: private static $dataTypes = array(
101: TYPETEXT => 'text',
102: TYPEMULTIPART => 'multipart',
103: TYPEMESSAGE => 'message',
104: TYPEAPPLICATION => 'application',
105: TYPEAUDIO => 'audio',
106: TYPEIMAGE => 'image',
107: TYPEVIDEO => 'video',
108: TYPEMODEL => 'model',
109: TYPEOTHER => 'other'
110: );
111:
112: 113: 114: 115: 116:
117: private static $encodingTypes = array(
118: ENC7BIT => '7bit',
119: ENC8BIT => '8bit',
120: ENCBINARY => 'binary',
121: ENCBASE64 => 'base64',
122: ENCQUOTEDPRINTABLE => 'quoted-printable',
123: ENCOTHER => 'other',
124: 6 => 'other'
125: );
126:
127: 128: 129: 130: 131: 132:
133: public function __construct($connection, $uid)
134: {
135: $this->connection = $connection;
136: $this->uid = (int) $uid;
137: }
138:
139: 140: 141: 142: 143:
144: private function checkIfParsed()
145: {
146: try {
147: if (empty($this->structure)) {
148: $this->setStructure();
149: }
150:
151: if (empty($this->defaultPid)) {
152: $this->defaultPid = $this->getDefaultPid();
153: }
154: } catch (Jyxo_Mail_Parser_EmailNotExistException $e) {
155: throw $e;
156: }
157: }
158:
159: 160: 161: 162: 163: 164: 165: 166: 167:
168: private function setStructure(array $subparts = null, $parentPartId = null, $skipPart = false, $lastWasSigned = false)
169: {
170:
171: if (null === $subparts) {
172: $this->structure['obj'] = imap_fetchstructure($this->connection, $this->uid, FT_UID);
173: if (!$this->structure['obj']) {
174: throw new Jyxo_Mail_Parser_EmailNotExistException('Email does not exist');
175: }
176: }
177:
178:
179: if (empty($this->structure['obj']->type)) {
180: $this->structure['obj']->type = TYPETEXT;
181: }
182:
183:
184: if (($this->structure['obj']->type != TYPETEXT)
185: && ($this->structure['obj']->type != TYPEMULTIPART)) {
186: $temp = $this->structure['obj'];
187:
188:
189: $this->structure['obj'] = new stdClass();
190: $this->structure['obj']->type = TYPEMULTIPART;
191: $this->structure['obj']->ifsubtype = 1;
192: $this->structure['obj']->subtype = 'MIXED';
193: $this->structure['obj']->ifdescription = 0;
194: $this->structure['obj']->ifid = 0;
195: $this->structure['obj']->bytes = isset($temp->bytes) ? $temp->bytes : 0;
196: $this->structure['obj']->ifdisposition = 1;
197: $this->structure['obj']->disposition = 'inline';
198: $this->structure['obj']->ifdparameters = 0;
199: $this->structure['obj']->dparameters = array();
200: $this->structure['obj']->ifparameters = 0;
201: $this->structure['obj']->parameters = array();
202: $this->structure['obj']->parts = array($temp);
203: }
204:
205:
206: if ((null === $subparts) && (null === $parentPartId)) {
207: $ftype = empty($this->structure['obj']->type)
208: ? $this->getMajorMimeType(0) . '/' . strtolower($this->structure['obj']->subtype)
209: : $this->getMajorMimeType($this->structure['obj']->type) . '/' . strtolower($this->structure['obj']->subtype);
210:
211:
212:
213: $this->structure['pid'][0] = 0;
214: $this->structure['ftype'][0] = $ftype;
215: $this->structure['encoding'][0] = !empty($this->structure['obj']->encoding) ? self::$encodingTypes[$this->structure['obj']->encoding] : self::$encodingTypes[0];
216: $this->structure['fsize'][0] = !empty($this->structure['obj']->bytes) ? $this->structure['obj']->bytes : 0;
217: $this->structure['disposition'][0] = 'inline';
218:
219: }
220:
221:
222: if (isset($this->structure['obj']->parts) || is_array($subparts)) {
223: if (is_array($subparts)) {
224: $parts = $subparts;
225: } else {
226: $parts = $this->structure['obj']->parts;
227: }
228:
229: $count = 1;
230: foreach ($parts as $part) {
231:
232:
233: $ftype = empty($part->type)
234: ? $this->getMajorMimeType(0) . '/' . strtolower($part->subtype)
235: : $this->getMajorMimeType($part->type) . '/' . strtolower($part->subtype);
236:
237: $thisIsSigned = ('multipart/signed' == $ftype);
238: $skipNext = ('message/rfc822' == $ftype);
239:
240: $no = isset($this->structure['pid']) ? count($this->structure['pid']) : 0;
241:
242:
243: if ((('multipart/mixed' == $ftype) && ($lastWasSigned || $skipPart))
244: || ('multipart/signed' == $ftype)
245: || ($skipPart && ('multipart/alternative' == $ftype))
246: || ($skipPart && ('multipart/report' == $ftype))
247: || (('multipart/related' == $ftype) && (1 === count($parts)))) {
248: $skipped = true;
249:
250:
251: $this->structure['pid'][$no] = $parentPartId;
252: $this->structure['ftype'][$no] = $ftype;
253: $this->structure['encoding'][$no] = !empty($this->structure['obj']->encoding) ? self::$encodingTypes[$this->structure['obj']->encoding] : self::$encodingTypes[0];
254: $this->structure['fsize'][$no] = !empty($this->structure['obj']->bytes) ? $this->structure['obj']->bytes : 0;
255: $this->structure['disposition'][$no] = 'inline';
256: } else {
257: $skipped = false;
258:
259: $this->structure['pid'][$no] = (!is_array($subparts)) ? strval($count) : $parentPartId . '.' . $count;
260: $this->structure['ftype'][$no] = $ftype;
261: $this->structure['encoding'][$no] = !empty($part->encoding) ? self::$encodingTypes[$part->encoding] : self::$encodingTypes[0];
262: $this->structure['fsize'][$no] = !empty($part->bytes) ? $part->bytes : 0;
263:
264:
265: if ($part->ifdparameters) {
266: foreach ($part->dparameters as $param) {
267: $this->structure[strtolower($param->attribute)][$no] = strtolower($param->value);
268: }
269: }
270: if ($part->ifparameters) {
271: foreach ($part->parameters as $param) {
272: $this->structure[strtolower($param->attribute)][$no] = strtolower($param->value);
273: }
274: }
275:
276:
277: if ($part->ifparameters) {
278: foreach ($part->parameters as $param) {
279: if (0 === stripos($param->attribute, 'name')) {
280: if (!isset($this->structure['fname'][$no])) {
281: $this->structure['fname'][$no] = $param->value;
282: } else {
283: $this->structure['fname'][$no] .= $param->value;
284: }
285: }
286: }
287: }
288: if (($part->ifdparameters)
289: && ((!isset($this->structure['fname'][$no])) || (empty($this->structure['fname'][$no])))) {
290: foreach ($part->dparameters as $param) {
291: if (0 === stripos($param->attribute, 'filename')) {
292: if (!isset($this->structure['fname'][$no])) {
293: $this->structure['fname'][$no] = $param->value;
294: } else {
295: $this->structure['fname'][$no] .= $param->value;
296: }
297: }
298: }
299: }
300:
301: if (isset($this->structure['fname'][$no])) {
302: $this->structure['fname'][$no] = $this->decodeFilename($this->structure['fname'][$no]);
303: }
304:
305:
306: if ('message/rfc822' == $ftype) {
307: $rfcHeader = $this->getHeaders($this->structure['pid'][$no]);
308: $this->structure['fname'][$no] = !empty($rfcHeader['subject']) ? $rfcHeader['subject'] . '.eml' : '';
309: }
310:
311:
312: if ($part->ifid) {
313: $this->structure['cid'][$no] = substr($part->id, 1, -1);
314: }
315:
316:
317: list($type, $subtype) = explode('/', $ftype);
318: if (($part->ifdisposition) && ('attachment' == strtolower($part->disposition))) {
319: $this->structure['disposition'][$no] = 'attachment';
320: } elseif ((isset($this->structure['cid'][$no])) && ('image' == $type)) {
321: $this->structure['disposition'][$no] = 'inline';
322: } elseif (('message' == $type) || ('application' == $type) || ('image' == $type) || ('audio' == $type)
323: || ('video' == $type) || ('model' == $type) || ('other' == $type)) {
324: $this->structure['disposition'][$no] = 'attachment';
325: } elseif (('text' == $type) && (('html' != $subtype) && ('plain' != $subtype))) {
326: $this->structure['disposition'][$no] = 'attachment';
327: } elseif (('text' == $type) && (isset($this->structure['fname'][$no]))) {
328: $this->structure['disposition'][$no] = 'attachment';
329: } else {
330: $this->structure['disposition'][$no] = 'inline';
331: }
332: }
333:
334: if ((isset($part->parts)) && (is_array($part->parts))) {
335: if (!$skipped) {
336: $this->structure['hasAttach'][$no] = true;
337: }
338:
339: $this->setStructure($part->parts, end($this->structure['pid']), $skipNext, $thisIsSigned);
340: } elseif (!$skipped) {
341: $this->structure['hasAttach'][$no] = false;
342: }
343:
344: $count++;
345: }
346: } else {
347:
348:
349: $this->structure['pid'][0] = 1;
350:
351: $this->structure['ftype'][0] = $this->getMajorMimeType($this->structure['obj']->type) . '/' . strtolower($this->structure['obj']->subtype);
352:
353:
354: if (($this->structure['ftype'][0] != 'text/plain') && ($this->structure['ftype'][0] != 'text/html')) {
355: $this->structure['ftype'][0] = 'text/plain';
356: }
357:
358: if (empty($this->structure['obj']->encoding)) {
359: $this->structure['obj']->encoding = 0;
360: }
361:
362: $this->structure['encoding'][0] = self::$encodingTypes[$this->structure['obj']->encoding];
363:
364: if (isset($this->structure['obj']->bytes)) {
365: $this->structure['fsize'][0] = $this->structure['obj']->bytes;
366: }
367:
368: $this->structure['disposition'][0] = 'inline';
369: $this->structure['hasAttach'][0] = false;
370:
371:
372: if ((isset($this->structure['obj']->ifparameters)) && ($this->structure['obj']->ifparameters)) {
373: foreach ($this->structure['obj']->parameters as $param) {
374: $this->structure[strtolower($param->attribute)][0] = $param->value;
375: }
376: }
377: }
378: }
379:
380: 381: 382: 383: 384: 385: 386:
387: private function getDefaultPid($mimeType = 'text/html', $attempt = 1)
388: {
389: $mimeCheck = ('text/html' == $mimeType) ? array('text/html', 'text/plain') : array('text/plain', 'text/html');
390:
391:
392: foreach ($mimeCheck as $mime) {
393: $parts = array_keys($this->structure['ftype'], $mime);
394: foreach ($parts as $part) {
395: if (('inline' == $this->structure['disposition'][$part])
396: && (false === strpos($this->structure['pid'][$part], '.'))) {
397: return $this->structure['pid'][$part];
398: }
399: }
400: }
401:
402:
403: $partLevel = 1;
404: $pidLength = 1;
405: foreach ($this->structure['pid'] as $partNo => $pid) {
406: $level = count(explode('.', $pid));
407:
408: if (!isset($multipartPid)) {
409: if ((1 === $level) && (isset($this->structure['ftype'][$partNo])) && ($this->isPartMultipart($partNo, 'related'))) {
410: $partLevel = 2;
411: $pidLength = 3;
412: continue;
413: }
414:
415: if (($level == $partLevel) && (isset($this->structure['ftype'][$partNo]))
416: && (($this->isPartMultipart($partNo, 'alternative'))
417: || ($this->isPartMultipart($partNo, 'report')) || ($this->isPartMultipart($partNo, 'mixed')))) {
418: $multipartPid = $pid;
419: continue;
420: }
421: }
422:
423: if ((isset($multipartPid)) && ($level == ($partLevel + 1))
424: && ($this->structure['ftype'][$partNo] == $mimeType) && ($multipartPid == substr($pid, 0, $pidLength))) {
425: return $pid;
426: }
427: }
428:
429:
430: if (1 === $attempt) {
431: if ('text/html' == $mimeType) {
432: return $this->getDefaultPid('text/plain', 2);
433: } else {
434: return $this->getDefaultPid('text/html', 2);
435: }
436: } else {
437:
438: return 1;
439:
440: }
441: }
442:
443: 444: 445: 446: 447: 448: 449:
450: public function getHeaders($pid = null)
451: {
452:
453: $rawHeaders = $this->getRawHeaders($pid);
454: if (null === $pid) {
455: $msgno = imap_msgno($this->connection, $this->uid);
456: if (0 === $msgno) {
457: throw new Jyxo_Mail_Parser_EmailNotExistException('Email does not exist');
458: }
459: $headerInfo = imap_headerinfo($this->connection, $msgno);
460: } else {
461: $headerInfo = imap_rfc822_parse_headers($rawHeaders);
462: }
463:
464:
465: if (preg_match("~Disposition-Notification-To:(.+?)(?=\r?\n(?:\S|\r?\n))~is", $rawHeaders, $matches)) {
466: $addressList = imap_rfc822_parse_adrlist($matches[1], '');
467:
468: $headerInfo->{'disposition_notification_toaddress'} = substr(trim($matches[1]), 0, 1024);
469: $headerInfo->{'disposition_notification_to'} = array($addressList[0]);
470: }
471:
472: $headers = array();
473: static $mimeHeaders = array(
474: 'toaddress', 'ccaddress', 'bccaddress', 'fromaddress', 'reply_toaddress', 'senderaddress',
475: 'return_pathaddress', 'subject', 'fetchfrom', 'fetchsubject', 'disposition_notification_toaddress'
476: );
477: foreach ($headerInfo as $key => $value) {
478: if ((!is_object($value)) && (!is_array($value))) {
479: if (in_array($key, $mimeHeaders)) {
480: $headers[$key] = $this->decodeMimeHeader($value);
481: } else {
482: $headers[$key] = $this->convertToUtf8($value);
483: }
484: }
485: }
486:
487:
488: if (!empty($headerInfo->udate)) {
489: $headers['udate'] = $headerInfo->udate;
490: } elseif (!empty($headerInfo->date)) {
491: $headers['udate'] = strtotime($headerInfo->date);
492: } else {
493: $headers['udate'] = time();
494: }
495:
496:
497: $headers['references'] = explode('> <', trim($headers['references'], '<>'));
498:
499: static $types = array('to', 'cc', 'bcc', 'from', 'reply_to', 'sender', 'return_path', 'disposition_notification_to');
500: for ($i = 0; $i < count($types); $i++) {
501: $type = $types[$i];
502: $headers[$type] = array();
503: if (isset($headerInfo->$type)) {
504: foreach ($headerInfo->$type as $object) {
505: $newHeader = array();
506: foreach ($object as $attributeName => $attributeValue) {
507: if (!empty($attributeValue)) {
508: $newHeader[$attributeName] = ('personal' === $attributeName)
509: ? $this->decodeMimeHeader($attributeValue)
510: : $this->convertToUtf8($attributeValue);
511: }
512: }
513:
514: if (!empty($newHeader)) {
515: if (isset($newHeader['mailbox'], $newHeader['host'])) {
516: $newHeader['email'] = $newHeader['mailbox'] . '@' . $newHeader['host'];
517: } elseif (isset($newHeader['mailbox'])) {
518: $newHeader['email'] = $newHeader['mailbox'];
519: } else {
520: $newHeader['email'] = 'undisclosed-recipients';
521: }
522:
523: $headers[$type][] = $newHeader;
524: }
525: }
526: }
527: }
528:
529:
530: if (preg_match_all("~(X(?:[-]\w+)+):(.+?)(?=\r?\n(?:\S|\r?\n))~is", $rawHeaders, $matches) > 0) {
531: for ($i = 0; $i < count($matches[0]); $i++) {
532:
533: $key = str_replace('-', '_', strtolower($matches[1][$i]));
534:
535: $value = strtr(trim($matches[2][$i]), array("\r" => '', "\n" => '', "\t" => ' '));
536: $headers[$key] = $value;
537: }
538: }
539:
540: return $headers;
541: }
542:
543: 544: 545: 546: 547: 548: 549:
550: private function getRawHeaders($pid = null)
551: {
552: if (null === $pid) {
553: return imap_fetchheader($this->connection, $this->uid, FT_UID);
554: }
555:
556: $rawHeaders = imap_fetchbody($this->connection, $this->uid, $pid, FT_UID);
557: if (empty($rawHeaders)) {
558: throw new Jyxo_Mail_Parser_PartNotExistException('No such part exists.');
559: }
560:
561: $headersEnd = (false !== strpos($rawHeaders, "\n\n"))
562: ? strpos($rawHeaders, "\n\n")
563: : strpos($rawHeaders, "\n\r\n");
564:
565: return substr($rawHeaders, 0, $headersEnd);
566: }
567:
568: 569: 570: 571: 572: 573: 574: 575: 576:
577: public function parseBody($pid = null, $mimeType = 'text/html', $alternative = true, $all = false)
578: {
579: try {
580: $this->checkIfParsed();
581: } catch (Jyxo_Mail_Parser_EmailNotExistException $e) {
582: throw $e;
583: }
584:
585: if (null === $pid) {
586: $pid = $this->defaultPid;
587: }
588:
589:
590: if (count($this->structure['pid']) > 1) {
591: $key = array_search($pid, $this->structure['pid']);
592: if (false !== $key) {
593: if ($all) {
594: $this->parseMultiparts($pid, $mimeType, 'all', 2, $alternative);
595: } else {
596: if ($pid == $this->defaultPid) {
597: $this->parseMultiparts($pid, $mimeType, 'top', 2, $alternative);
598: } elseif ('message/rfc822' == $this->structure['ftype'][$i]) {
599: $this->parseMultiparts($pid, $mimeType, 'subparts', 1, $alternative);
600: }
601: }
602: }
603: }
604: }
605:
606: 607: 608: 609: 610: 611: 612: 613: 614:
615: private function parseMultiparts($pid, $mimeType, $lookFor = 'all', $pidAdd = 1, $getAlternative = true)
616: {
617:
618:
619: $excludeMime = $mimeType;
620: $mimeType = ('text/plain' == $excludeMime) ? 'text/html' : 'text/plain';
621:
622: $partLevel = count(explode('.', $pid));
623: $pidLength = strlen($pid);
624: foreach ($this->structure['pid'] as $partNo => $id) {
625: $level = count(explode('.', $this->structure['pid'][$partNo]));
626:
627: switch ($lookFor) {
628: case 'all':
629: $condition = true;
630: break;
631: case 'subparts':
632: $condition = (($level == ($partLevel + 1)) && ($pid == substr($this->structure['pid'][$partNo], 0, $pidLength)));
633: break;
634: case 'top':
635:
636: default:
637: if ($this->isMultipart('related') || $this->isMultipart('mixed')) {
638:
639: $condition = ((false === strpos($this->structure['pid'][$partNo], '.'))
640: || ((2 == $level) && substr($this->defaultPid, 0, 1) == substr($this->structure['pid'][$partNo], 0, 1)));
641: } else {
642:
643: $condition = (false === strpos($this->structure['pid'][$partNo], '.'));
644: }
645: break;
646: }
647:
648: if ($condition) {
649: if (($this->isPartMultipart($partNo, 'alternative')) || ($this->isPartMultipart($partNo, 'report')) || ($this->isPartMultipart($partNo, 'mixed'))) {
650: $subLevel = count(explode('.', $this->structure['pid'][$partNo]));
651: foreach ($this->structure['pid'] as $multipartNo => $multipartPid) {
652:
653: if (($this->structure['ftype'][$multipartNo] == $mimeType) && $getAlternative && ($subLevel == ($partLevel + $pidAdd))
654: && ($pid == substr($multipartPid, 0, strlen($this->structure['pid'][$partNo])))) {
655: $this->addPart($partNo, 'inline');
656: break;
657: }
658: }
659: } elseif (('inline' == $this->structure['disposition'][$partNo])
660: && (!$this->isPartMultipart($partNo, 'related')) && (!$this->isPartMultipart($partNo, 'mixed'))) {
661:
662:
663: if ((($this->structure['ftype'][$partNo] != $excludeMime) && ($pid != $this->structure['pid'][$partNo]) && ($getAlternative || !$this->isParentAlternative($partNo)))
664: || (($this->structure['ftype'][$partNo] == $excludeMime) && (isset($this->structure['fname'][$partNo])) && ($pid != $this->structure['pid'][$partNo]))) {
665: $this->addPart($partNo, 'inline');
666: }
667: } elseif ('attachment' == $this->structure['disposition'][$partNo]) {
668:
669:
670: $this->addPart($partNo, 'attach');
671: }
672: }
673: }
674: }
675:
676: 677: 678: 679: 680: 681:
682: private function isParentAlternative($partNo)
683: {
684:
685: if (($this->structure['ftype'][$partNo] != 'text/plain') && ($this->structure['ftype'][$partNo] != 'text/plain')) {
686: return false;
687: }
688:
689: $partId = $this->structure['pid'][$partNo];
690: $partLevel = count(explode('.', $partId));
691: if (1 === $partLevel) {
692: return $this->isPartMultipart(0, 'alternative');
693: }
694:
695: $parentId = substr($partId, 0, strrpos($partId, '.'));
696: for ($i = 0; $i < count($this->structure['pid']); $i++) {
697:
698: if (($parentId == $this->structure['pid'][$i]) && ($this->isPartMultipart($i, 'alternative'))) {
699: return true;
700: }
701: }
702:
703: return false;
704: }
705:
706: 707: 708: 709: 710: 711:
712: private function isMultipart($subtype)
713: {
714: return (count($this->getMime(array('multipart/' . $subtype))) > 0);
715: }
716:
717: 718: 719: 720: 721: 722: 723:
724: private function isPartMultipart($partNo, $subtype)
725: {
726: return ($this->structure['ftype'][$partNo] == ('multipart/' . $subtype));
727: }
728:
729: 730: 731: 732: 733: 734:
735: private function addPart($structureNo, $partType)
736: {
737: $fields = array('fname', 'pid', 'ftype', 'fsize', 'hasAttach', 'charset');
738:
739: $no = isset($this->parts[$partType]['pid']) ? count($this->parts[$partType]['pid']) : 0;
740: foreach ($fields as $field) {
741: if (!empty($this->structure[$field][$structureNo])) {
742: $this->parts[$partType][$field][$no] = $this->structure[$field][$structureNo];
743: }
744: }
745: }
746:
747: 748: 749: 750: 751: 752: 753: 754:
755: private function getMultipartPid($pid, $mimeType, $lookFor)
756: {
757: $partLevel = count(explode('.', $pid));
758: $pidLength = strlen($pid);
759: $pidAdd = 1;
760: foreach ($this->structure['pid'] as $partNo => $id) {
761: $level = count(explode('.', $this->structure['pid'][$partNo]));
762:
763: switch ($lookFor) {
764: case 'subparts':
765: $condition = (($level == ($partLevel + 1)) && ($pid == substr($this->structure['pid'][$partNo], 0, $pidLength)));
766: break;
767: case 'multipart':
768: $condition = (($level == ($partLevel + 1)) && ($pid == substr($this->structure['pid'][$partNo])));
769: break;
770: default:
771: $condition = false;
772: break;
773: }
774:
775: if ($condition) {
776: if (($this->isPartMultipart($partNo, 'alternative')) || ($this->isPartMultipart($partNo, 'report')) || ($this->isPartMultipart($partNo, 'mixed'))) {
777: foreach ($this->structure['pid'] as $multipartNo => $multipartPid) {
778:
779: $subLevel = count(explode('.', $this->structure['pid'][$partNo]));
780:
781: if (($this->structure['ftype'][$multipartNo] == $mimeType) && ($subLevel == ($partLevel + $pidAdd))
782: && ($pid == substr($multipartPid, 0, strlen($this->structure['pid'][$partNo])))) {
783: if (empty($this->structure['fname'][$multipartNo])) {
784: return $this->structure['pid'][$multipartNo];
785: }
786: } elseif (($this->isPartMultipart($multipartNo, 'alternative')) || ($this->isPartMultipart($multipartNo, 'report'))) {
787:
788: $pid = $this->structure['pid'][$multipartNo];
789: $pidLength = strlen($pid);
790: $partLevel = count(explode('.', $pid));
791: $pidAdd = 2;
792: continue;
793: }
794: }
795: } elseif (('inline' == $this->structure['disposition'][$partNo])
796: && (!$this->isPartMultipart($multipartNo, 'related')) && (!$this->isPartMultipart($multipartNo, 'mixed'))) {
797:
798:
799: if (($this->structure['ftype'][$partNo] == $mimeType) && (!isset($this->structure['fname'][$partNo]))) {
800: return $this->structure['pid'][$partNo];
801: }
802: }
803: }
804: }
805: }
806:
807: 808: 809: 810: 811:
812: public function getAttachments()
813: {
814: return isset($this->parts['attach']['pid']) ? $this->parts['attach']['pid'] : array();
815: }
816:
817: 818: 819: 820: 821:
822: public function getInlines()
823: {
824: return isset($this->parts['inline']['pid']) ? $this->parts['inline']['pid'] : array();
825: }
826:
827: 828: 829: 830: 831: 832: 833: 834: 835:
836: public function getRelatedParts($pid, array $types, $all = false)
837: {
838: try {
839: $this->checkIfParsed();
840: } catch (Jyxo_Mail_Parser_EmailNotExistException $e) {
841: throw $e;
842: }
843:
844: $related = array();
845: if (!empty($this->structure['pid'])) {
846:
847: if (0 === $pid) {
848: for ($i = 1; $i < count($this->structure['pid']); $i++) {
849:
850: if ((false === strpos($this->structure['pid'][$i], '.'))
851: && (($all) || (in_array($this->structure['ftype'][$i], $types)))) {
852: $related['pid'][] = $this->structure['pid'][$i];
853: $related['ftype'][] = $this->structure['ftype'][$i];
854: }
855: }
856: } else {
857: $level = count(explode('.', $pid));
858: foreach ($this->structure['pid'] as $i => $rpid) {
859:
860: if ((count(explode('.', $rpid)) == ($level + 1)) && ($pid == substr($rpid, 0, strrpos($rpid, '.')))) {
861: if (($all) || (in_array($this->structure['ftype'][$i], $types))) {
862: $related['pid'][] = $this->structure['pid'][$i];
863: $related['ftype'][] = $this->structure['ftype'][$i];
864: }
865: }
866: }
867: }
868: }
869:
870: return $related;
871: }
872:
873: 874: 875: 876: 877: 878: 879:
880: public function getAllRelatedParts($pid)
881: {
882: try {
883: return $this->getRelatedParts($pid, array(), true);
884: } catch (Jyxo_Mail_Parser_EmailNotExistException $e) {
885: throw $e;
886: }
887: }
888:
889: 890: 891: 892: 893: 894: 895: 896: 897: 898: 899:
900: public function getBody($pid = '1', $mode = self::BODY, $mimeType = 'text/html', $attempt = 1)
901: {
902: try {
903: $this->checkIfParsed();
904: } catch (Jyxo_Mail_Parser_EmailNotExistException $e) {
905: throw $e;
906: }
907:
908: $key = array_search($pid, $this->structure['pid']);
909: if (false === $key) {
910: throw new Jyxo_Mail_Parser_PartNotExistException('Requested part does not exist');
911: }
912:
913: $output['encoding'] = $this->structure['encoding'][$key];
914: $output['type'] = $this->structure['ftype'][$key];
915: $output['size'] = $this->structure['fsize'][$key];
916:
917: if (isset($this->structure['fname'][$key])) {
918: $output['filename'] = $this->structure['fname'][$key];
919: }
920: if (isset($this->structure['charset'][$key])) {
921: $output['charset'] = $this->structure['charset'][$key];
922: }
923: if (isset($this->structure['cid'][$key])) {
924: $output['cid'] = $this->structure['cid'][$key];
925: }
926:
927: if (self::BODY_INFO === $mode) {
928: return $output;
929: }
930:
931: if (self::BODY_LITERAL === $mode) {
932: $output['content'] = imap_fetchbody($this->connection, $this->uid, $pid, FT_UID);
933: return $output;
934: }
935:
936: if (self::BODY_LITERAL_DECODE === $mode) {
937: $output['content'] = self::decodeBody(imap_fetchbody($this->connection, $this->uid, $pid, FT_UID), $output['encoding']);
938:
939:
940: if ((0 === strpos($output['type'], 'text/')) || (0 === strpos($output['type'], 'message/'))) {
941: $output['content'] = $this->convertToUtf8($output['content'], isset($output['charset']) ? $output['charset'] : '');
942: }
943:
944: return $output;
945: }
946:
947:
948: if (('message/rfc822' == $this->structure['ftype'][$key]) || ($this->isPartMultipart($key, 'related'))
949: || ($this->isPartMultipart($key, 'alternative')) || ($this->isPartMultipart($key, 'report'))) {
950:
951: $newPid = ('message/rfc822' == $this->structure['ftype'][$key])
952: || ($this->isPartMultipart($key, 'related'))
953: || ($this->isPartMultipart($key, 'alternative'))
954: || ($this->isPartMultipart($key, 'report'))
955: ? $this->getMultipartPid($pid, $mimeType, 'subparts')
956: : $this->getMultipartPid($pid, $mimeType, 'multipart');
957:
958:
959: if (!empty($newPid)) {
960: $pid = $newPid;
961: } elseif (empty($newPid) && ('text/html' == $mimeType)) {
962: if (1 === $attempt) {
963: return $this->getBody($pid, $mode, 'text/plain', 2);
964: }
965: } elseif (empty($newPid) && ('text/plain' == $mimeType)) {
966: if (1 === $attempt) {
967: return $this->getBody($pid, $mode, 'text/html', 2);
968: }
969: }
970: }
971:
972: if (!empty($newPid)) {
973: $key = array_search($pid, $this->structure['pid']);
974: if (false === $key) {
975: throw new Jyxo_Mail_Parser_PartNotExistException('Requested part does not exist');
976: }
977: }
978:
979: $output['encoding'] = $this->structure['encoding'][$key];
980: $output['type'] = $this->structure['ftype'][$key];
981: $output['size'] = $this->structure['fsize'][$key];
982:
983: if (isset($this->structure['fname'][$key])) {
984: $output['filename'] = $this->structure['fname'][$key];
985: }
986: if (isset($this->structure['charset'][$key])) {
987: $output['charset'] = $this->structure['charset'][$key];
988: }
989:
990: $output['content'] = self::decodeBody(imap_fetchbody($this->connection, $this->uid, $pid, FT_UID), $output['encoding']);
991:
992:
993: if ((0 === strpos($output['type'], 'text/')) || (0 === strpos($output['type'], 'message/'))) {
994: $output['content'] = $this->convertToUtf8($output['content'], isset($output['charset']) ? $output['charset'] : '');
995: }
996:
997: return $output;
998: }
999:
1000: 1001: 1002: 1003: 1004: 1005: 1006:
1007: public static function decodeBody($body, $encoding)
1008: {
1009: switch ($encoding) {
1010: case 'quoted-printable':
1011: return quoted_printable_decode($body);
1012: case 'base64':
1013: return imap_base64($body);
1014: default:
1015: return $body;
1016: }
1017: }
1018:
1019: 1020: 1021: 1022: 1023: 1024: 1025:
1026: public function getMime(array $types)
1027: {
1028: try {
1029: $this->checkIfParsed();
1030: } catch (Jyxo_Mail_Parser_EmailNotExistException $e) {
1031: throw $e;
1032: }
1033:
1034: $parts = array();
1035: if (is_array($this->structure['ftype'])) {
1036: foreach ($types as $type) {
1037: foreach (array_keys($this->structure['ftype'], $type) as $key) {
1038: $parts[] = $this->structure['pid'][$key];
1039: }
1040: }
1041: }
1042:
1043: return $parts;
1044: }
1045:
1046: 1047: 1048: 1049: 1050: 1051: 1052:
1053: public function getMimeExcept(array $exceptTypes)
1054: {
1055: try {
1056: $this->checkIfParsed();
1057: } catch (Jyxo_Mail_Parser_EmailNotExistException $e) {
1058: throw $e;
1059: }
1060:
1061: $parts = array();
1062: if (is_array($this->structure['ftype'])) {
1063: $allExcept = array_diff($this->structure['ftype'], $types);
1064: foreach (array_keys($allExcept) as $key) {
1065: $parts[] = $this->structure['pid'][$key];
1066: }
1067: }
1068:
1069: return $parts;
1070: }
1071:
1072: 1073: 1074: 1075: 1076: 1077:
1078: private function getMajorMimeType($mimetypeNo)
1079: {
1080: if (isset(self::$dataTypes[$mimetypeNo])) {
1081: return self::$dataTypes[$mimetypeNo];
1082: }
1083:
1084:
1085: return self::$dataTypes[max(array_keys(self::$dataTypes))];
1086: }
1087:
1088: 1089: 1090: 1091: 1092: 1093:
1094: private function decodeMimeHeader($header)
1095: {
1096: $headerDecoded = imap_mime_header_decode($header);
1097:
1098: if (false === $headerDecoded) {
1099: return trim($header);
1100: }
1101:
1102: $header = '';
1103: for ($i = 0; $i < count($headerDecoded); $i++) {
1104: $header .= $this->convertToUtf8($headerDecoded[$i]->text, $headerDecoded[$i]->charset);
1105: }
1106: return trim($header);
1107: }
1108:
1109: 1110: 1111: 1112: 1113: 1114:
1115: private function decodeFilename($filename)
1116: {
1117: if (preg_match('~(?P<charset>[^\']+)\'(?P<lang>[^\']*)\'(?P<filename>.+)~i', $filename, $parts)) {
1118: $filename = $this->convertToUtf8(rawurldecode($parts['filename']), $parts['charset']);
1119: } elseif (0 === strpos($filename, '=?')) {
1120: $filename = $this->decodeMimeHeader($filename);
1121: }
1122: return $filename;
1123: }
1124:
1125: 1126: 1127: 1128: 1129: 1130: 1131:
1132: private function convertToUtf8($string, $charset = '')
1133: {
1134:
1135: if ('default' === $charset || 'us-ascii' === $charset || empty($charset)) {
1136: $charset = Jyxo_Charset::detect($string);
1137: }
1138:
1139: return Jyxo_Charset::convert2utf($string, $charset);
1140: }
1141: }
1142: