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