imap_fetchstructure

(PHP 4, PHP 5, PHP 7, PHP 8)

imap_fetchstructure特定のメッセージの構造を読み込む

説明

imap_fetchstructure(IMAP\Connection $imap, int $message_num, int $flags = 0): stdClass|false

この関数は、指定したメッセージに関するすべての構造化された情報を取り出します。

パラメータ

imap

IMAP\Connection クラスのインスタンス。

message_num

メッセージ番号。

flags

オプションのパラメータで、FT_UID のみが指定可能です。 これは、message_numUID として処理することを関数に指定するためのものです。

戻り値

以下の表に示すプロパティを持つオブジェクトを返します。 失敗した場合に false を返します

imap_fetchstructure() が返すオブジェクト
type 最初の body 部の型
encoding body 部を転送する際のエンコード法
ifsubtype subtype 文字列がある場合に true
subtype MIME の subtype
ifdescription description 文字列がある場合に true
description 内容を記述する文字列
ifid identification 文字列がある場合に true
id Identification 文字列
lines 行数
bytes バイト数
ifdisposition disposition 文字列がある場合に true
disposition Disposition 文字列
ifdparameters dparameters 配列が存在する場合に true
dparameters オブジェクトの配列。各オブジェクトは "attribute" および "value" というプロパティを保持し、それぞれ Content-disposition MIME ヘッダの 対応するパラメータを表す。
ifparameters 配列 parameters が存在する場合に true
parameters オブジェクトの配列。各オブジェクトは "attribute" および "value" というプロパティを保持する。
parts オブジェクトの配列であり、その構造はトップレベルオブジェクトと 同じです。それぞれが MIME body 部に対応しています。

最初の body 部の型 (使用するライブラリによって異なるため、定数を使うことを推奨します)
定数
0textTYPETEXT
1multipartTYPEMULTIPART
2messageTYPEMESSAGE
3applicationTYPEAPPLICATION
4audioTYPEAUDIO
5imageTYPEIMAGE
6videoTYPEVIDEO
7modelTYPEMODEL
8otherTYPEOTHER

転送時のエンコーディング (使用するライブラリによって異なるため、定数を使うことを推奨します)
定数
07bitENC7BIT
18bitENC8BIT
2BinaryENCBINARY
3Base64ENCBASE64
4Quoted-PrintableENCQUOTEDPRINTABLE
5otherENCOTHER

変更履歴

バージョン 説明
8.1.0 引数 imap は、IMAP\Connection クラスのインスタンスを期待するようになりました。 これより前のバージョンでは、有効な imap リソース が期待されていました。

参考

  • imap_fetchbody() - メッセージ本文中の特定のセクションを取り出す
  • imap_bodystruct() - 指定したメッセージの指定した body セクションの構造を読み込む

add a note add a note

User Contributed Notes 33 notes

up
3
mkknapp at quadrapod dot com
23 years ago
Assuming $struct = imap_fetchstructure($x,$y);

It is important to note that if a message has NO attachements, $struct->parts is an empty array, and $struct->bytes has a value.  If a message as ANY attachements, $struct->bytes ALWAYS = 0. To get the size of the primary body, you have to call structure->part[0]->bytes.  To get the size of the whole message, either strlen(imap_body) or add up the ->bytes of all the parts.

Another interesting note:
When there is body text and no attachements:
count($struct->parts) = 0
When there is body text and 1 attachement:
count($struct->parts) = 2

These imap functions could really use better documentation. Like giving the meathods for the dparameter and parameter classes...
up
47
david at hundsness dot com
16 years ago
Here is code to parse and decode all types of messages, including attachments. I've been using something like this for awhile now, so it's pretty robust.

<?
function getmsg($mbox,$mid) {
   
// input $mbox = IMAP stream, $mid = message id
    // output all the following:
   
global $charset,$htmlmsg,$plainmsg,$attachments;
   
$htmlmsg = $plainmsg = $charset = '';
   
$attachments = array();

   
// HEADER
   
$h = imap_header($mbox,$mid);
   
// add code here to get date, from, to, cc, subject...

    // BODY
   
$s = imap_fetchstructure($mbox,$mid);
    if (!
$s->parts// simple
       
getpart($mbox,$mid,$s,0);  // pass 0 as part-number
   
else {  // multipart: cycle through each part
       
foreach ($s->parts as $partno0=>$p)
           
getpart($mbox,$mid,$p,$partno0+1);
    }
}

function
getpart($mbox,$mid,$p,$partno) {
   
// $partno = '1', '2', '2.1', '2.1.3', etc for multipart, 0 if simple
   
global $htmlmsg,$plainmsg,$charset,$attachments;

   
// DECODE DATA
   
$data = ($partno)?
       
imap_fetchbody($mbox,$mid,$partno):  // multipart
       
imap_body($mbox,$mid);  // simple
    // Any part may be encoded, even plain text messages, so check everything.
   
if ($p->encoding==4)
       
$data = quoted_printable_decode($data);
    elseif (
$p->encoding==3)
       
$data = base64_decode($data);

   
// PARAMETERS
    // get all parameters, like charset, filenames of attachments, etc.
   
$params = array();
    if (
$p->parameters)
        foreach (
$p->parameters as $x)
           
$params[strtolower($x->attribute)] = $x->value;
    if (
$p->dparameters)
        foreach (
$p->dparameters as $x)
           
$params[strtolower($x->attribute)] = $x->value;

   
// ATTACHMENT
    // Any part with a filename is an attachment,
    // so an attached text file (type 0) is not mistaken as the message.
   
if ($params['filename'] || $params['name']) {
       
// filename may be given as 'Filename' or 'Name' or both
       
$filename = ($params['filename'])? $params['filename'] : $params['name'];
       
// filename may be encoded, so see imap_mime_header_decode()
       
$attachments[$filename] = $data// this is a problem if two files have same name
   
}

   
// TEXT
   
if ($p->type==0 && $data) {
       
// Messages may be split in different parts because of inline attachments,
        // so append parts together with blank row.
       
if (strtolower($p->subtype)=='plain')
           
$plainmsg. = trim($data) ."\n\n";
        else
           
$htmlmsg. = $data ."<br><br>";
       
$charset = $params['charset'];  // assume all parts are same charset
   
}

   
// EMBEDDED MESSAGE
    // Many bounce notifications embed the original message as type 2,
    // but AOL uses type 1 (multipart), which is not handled here.
    // There are no PHP functions to parse embedded messages,
    // so this just appends the raw source to the main message.
   
elseif ($p->type==2 && $data) {
       
$plainmsg. = $data."\n\n";
    }

   
// SUBPART RECURSION
   
if ($p->parts) {
        foreach (
$p->parts as $partno0=>$p2)
           
getpart($mbox,$mid,$p2,$partno.'.'.($partno0+1));  // 1.2, 1.2.1, etc.
   
}
}
?>
up
3
kmakaveev(at)yahoo(dot)com
22 years ago
There's a little mistake in the code above!
The second echo must return "The message has no attachment!".
And that code must have some some additional  thing to work properly! Maybe I must use ->parts to determine how many parts has a particular message!
up
2
hans at lintoo dot dk
19 years ago
To fetch a single part (for example to investigate a charset or something like that)

<?php
class StructureEngine {
    private
$structureObject;
    public function
__construct($structureInfo) {
       
$this->structureObject = $structureInfo;
       
    }

   
// snip....

   
public function getPart($partNum) {
       
$path = split("[.]",$partNum);
       
$currentPart = $this->structureObject;
        foreach (
$path as $key => $num) {
           
$currentPart = $currentPart->parts[$num-1];
        }
        return
$currentPart;
    }
}
?>
up
2
php AT firstnetimpressions.com
22 years ago
Point of clarification:

The seventh primary body type is not "Other" as documented, but actually "Model".  This encompasses IGES, VRML, MESH, DWF, etc.

http://www.isi.edu/in-notes/iana/assignments/media-types/media-types

"Other" is the eigth primary body type.
up
2
sirber at detritus dot qc dot ca
18 years ago
"Primary body type" of "unknown/unknown" will be int(9).
up
2
aperez at informatica dot 24ruedas dot com
20 years ago
About above comment and source code I wrote: certainly, plain/html text files are attachments, but I've written my code thinking about web IMAP clients, where text parts are not for downloading, simply they are shown. That's the reason.

Thanks anyway. :)
up
2
chrislhill at "O_o" hotmail dot com
21 years ago
For people just beging this may help alot as I spent a couple hours just trying to relize how exact the array was stored. (at the time I did not know the print_r function :P)

$struct = imap_fetchstructure($mbox, $msgnumber);
print_r($struct);

Will give you a better example for your messages but they are called as $struct using the varible method above.

$struct->type; //would return the type
$struct->encoding //would return the encoding

and etc..

This can be done many different ways but that is the basics on pulling the info out of the structure of fetchstructure itself, I hope it helps someone starting because it wouldve helped me :/.
up
3
raulggonzalez at gmail dot com
14 years ago
Hello eveybody,

After reading plenty of posts (some good, some others not so good) I want to post a function to find out whether an email is coming with attachments

The function can be called passing the object returned by imap_fetchstructure() or any of its parts (recursively)

<?php
$inbox
= imap_open($hostname,$username,$password) or die('Cannot connect to mail: ' . imap_last_error());
$struct = imap_fetchstructure($inbox,$uid,FT_UID);
$existAttachments = existAttachment($struct);

function
existAttachment($part){
    if (isset(
$part->parts)){
        foreach (
$part->parts as $partOfPart){
           
existAttachment($partOfPart);
        }
    }
    else{
        if (isset(
$part->disposition)){
            if (
$part->disposition == 'attachment'){
                echo
'<p>' . $part->dparameters[0]->value . '</p>';
// here you can create a link to the file whose name is  $part->dparameters[0]->value to download it
               
return true;
            }
        }
    }
}
?>

Hopefully somebody can use it as well
up
1
jbrandt at europa dot com
23 years ago
Parsing the recursive object returned from imap_fetchstructure is easy. However trying to figure out the name of the section to pass to imap_fetchbody() isn't quite so straight forward because the naming conventions for the MIME sections are odd. If you copy and paste the following function you can pass an UID of an IMAP message and it will print out a hierarchy of the message and label each section.

The only required parameters are $mbox and $em_uid. The other parameters are used when the function needs to call itself.

Two functions mime_encoding() and mime_type() are not included. All they do is return string for the integer represention of a mime type/encoding.

<?php
function imap_dbody($mbox, $em_uid, $mimeobj, $depth = 0, $section = 0)
{
       
// If this is the first run of the function then grab a object of the MIME structure of the message

       
if($section == 0) $mimeobj = imap_fetchstructure($mbox, $em_uid, FT_UID);

        for(
$y = 0; $y < $depth; $y++) echo("       ");

        echo(
mime_type($mimeobj->type) . "/{$mimeobj->subtype}, ");
        echo(
mime_encoding($mimeobj->encoding) . " (<B>$section</B>)<BR>");

        for(
$x = 0; $x < count($mimeobj->parts); $x++)
        {
               
// If we are in the root of the object increment by whole integers

               
if($section == 0) $nsection = $x + 1;

               
// If we are in the object and the first sub-object of our object isn't multipart
                // then increment the postfix by ".1" otherwise we are multipart or a message
                // and leave the section id alone to be handled by the next code block

               
else if(($pos = strrpos($section, ".")) && $mimeobj->parts[0]->type != TYPEMULTIPART)
                       
$nsection = substr($section, 0, $pos) . "." . ($x + 1);
                else
                       
$nsection = $section;

               
// If there are more parts to the part about to be processed reference it as a header with ".0"
                // but only if the child of this child isn't MULTIPART

               
if(count($mimeobj->parts[$x]->parts))
                {
                       
// Funny really, if a mime section is a inline message that has a multipart body you reference the message
                        // mime section with "2" the inline message header with "2.0" and the subsections with "2.x"
                        // However if the mime section is a inline message with only 1 part then you reference the
                        // mime section in the message with 2.0 and the inline message body with 2.1

                       
if(!($mimeobj->parts[$x]->type == TYPEMESSAGE && $mimeobj->parts[$x]->parts[0]->type == TYPEMULTIPART))
                               
$nsection .= ".0";
                        else
                               
$nsection .= "";
                }

               
imap_dbody($mbox, $em_uid, $mimeobj->parts[$x], $depth + 1, $nsection);

        }

       
// If after processing the entire MIME object the $x variable is still zero then we didn't
        // process a multipart mime message, it's just normal email so say so here.

       
if($x == 0 && $section == 0)
        {
                echo(
mime_type($mimeobj->type) . "/{$mimeobj->subtype}, ");
                echo(
mime_encoding($mimeobj->encoding) . " (<B>1</B>) (<B>NOT MIME MULTIPART</B>)<BR>");
        }
}
?>
up
2
john at vetsurgeon dot org dot uk
19 years ago
A little script I threw together to break a message down and process it into a usable array

<?
//script will fetch an email identified by $msgid, and parse the its parts into an
//array $partsarray
//structure of array:
//$partsarray[<name of part>][<attachment/text>]
//if attachment- subarray is [filename][binary data]
//if text- subarray is [type of text(HTML/PLAIN)][text string]

//i.e.
//$partsarray[3.1][attachment][filename]=filename of attachment in part 3.1
//$partsarray[3.1][attachment][binary]=binary data of attachment in part 3.1
//$partsarray[2][text][type]=type of text in part 2
//$partsarray[2][text][string]=decoded text string in part 2
//$partsarray[not multipart][text][string]=decoded text string in message that isn't multipart

function parsepart($p,$i){
    global
$link,$msgid,$partsarray;
   
//where to write file attachments to:
   
$filestore = '[full/path/to/attachment/store/(chmod777)]';

   
//fetch part
   
$part=imap_fetchbody($link,$msgid,$i);
   
//if type is not text
   
if ($p->type!=0){
       
//DECODE PART       
        //decode if base64
       
if ($p->encoding==3)$part=base64_decode($part);
       
//decode if quoted printable
       
if ($p->encoding==4)$part=quoted_printable_decode($part);
       
//no need to decode binary or 8bit!
       
        //get filename of attachment if present
       
$filename='';
       
// if there are any dparameters present in this part
       
if (count($p->dparameters)>0){
            foreach (
$p->dparameters as $dparam){
                if ((
strtoupper($dparam->attribute)=='NAME') ||(strtoupper($dparam->attribute)=='FILENAME')) $filename=$dparam->value;
                }
            }
       
//if no filename found
       
if ($filename==''){
           
// if there are any parameters present in this part
           
if (count($p->parameters)>0){
                foreach (
$p->parameters as $param){
                    if ((
strtoupper($param->attribute)=='NAME') ||(strtoupper($param->attribute)=='FILENAME')) $filename=$param->value;
                    }
                }
            }
       
//write to disk and set partsarray variable
       
if ($filename!=''){
           
$partsarray[$i][attachment] = array('filename'=>$filename,'binary'=>$part);
           
$fp=fopen($filestore.$filename,"w+");
           
fwrite($fp,$part);
           
fclose($fp);
            }
   
//end if type!=0       
   
}
   
   
//if part is text
   
else if($p->type==0){
       
//decode text
        //if QUOTED-PRINTABLE
       
if ($p->encoding==4) $part=quoted_printable_decode($part);
       
//if base 64
       
if ($p->encoding==3) $part=base64_decode($part);
       
       
//OPTIONAL PROCESSING e.g. nl2br for plain text
        //if plain text

       
if (strtoupper($p->subtype)=='PLAIN')1;
       
//if HTML
       
else if (strtoupper($p->subtype)=='HTML')1;
       
$partsarray[$i][text] = array('type'=>$p->subtype,'string'=>$part);
    }
   
   
//if subparts... recurse into function and parse them too!
   
if (count($p->parts)>0){
        foreach (
$p->parts as $pno=>$parr){
           
parsepart($parr,($i.'.'.($pno+1)));           
            }
        }
return;
}

//open resource
$link=imap_open("{localhost:110/pop3}INBOX",'[YOUR USERNAME]','[YOUR PASSWORD]');

//fetch structure of message
$s=imap_fetchstructure($link,$msgid);

//see if there are any parts
if (count($s->parts)>0){
foreach (
$s->parts as $partno=>$partarr){
   
//parse parts of email
   
parsepart($partarr,$partno+1);
    }
}

//for not multipart messages
else{
   
//get body of message
   
$text=imap_body($link,$msgid);
   
//decode if quoted-printable
   
if ($s->encoding==4) $text=quoted_printable_decode($text);
   
//OPTIONAL PROCESSING
   
if (strtoupper($s->subtype)=='PLAIN') $text=$text;
    if (
strtoupper($s->subtype)=='HTML') $text=$text;
   
   
$partsarray['not multipart'][text]=array('type'=>$s->subtype,'string'=>$text);
}

print_r($partsarray);
?>
up
3
as1 at powersite dot com dot ar
21 years ago
Sorry, my before code is not complet:

$struct = imap_fetchstructure( $conn, $curMsg );

$i = 1;
while( imap_fetchbody( $conn, $curMsg, $i ) ){
  $struct->parts[$i-1] = imap_bodystruct( $conn, $curMsg, $i );
  $j = 1;
  while( imap_fetchbody( $conn, $curMsg, "$i.$j" ) ){
    $struct->parts[$i-1]->parts[$j-1] = imap_bodystruct( $conn, $curMsg, "$i.$j" );
    $j++;
    }
  $i++;
}
up
1
masterbassist
19 years ago
I think the following line (when building attachment information)

>>> "filename" => $parts[$i]->parameters[0]->value

needs to be

>>> "filename" => $parts[$i]->dparameters[0]->value

The first version generated a PHP warning under PHP 5.0.3.  The second version actually gets the filename.
up
1
y dot daradkeh at gmail dot com
20 years ago
Hey people, get the message attachments with this code:

<?php

$mbox
= imap_open("$imap_server".$f,$name,$pass);

// delibertely choose a message with an attachment

$info = imap_fetchstructure($mbox, $mno);

// find out how may parts the object has
$numparts = count($info->parts);

$i=0;
// find if multipart message
if ($numparts >1)
{
   echo
"<b>More than one part</b><br><br>";

   foreach (
$info->parts as $part)
   {
      if (
$part->disposition == "INLINE")
        
printf("Inline message has %s lines<BR>", $part->lines);
     elseif (
$part->disposition == "attachment")
     {
      
$i++;
       echo
$i." Attachment/s found!<br>";
       echo
"Filename: ".$part->dparameters[0]->value."<br><br>";
     }
   }
}
else
   echo
"Only one part";

imap_close($mbox);
?>
up
2
phpnet,a,emailaddress,cjb,net
17 years ago
Another comment to inform people about something that should really be in the function description:

imap_fetchstructure() downloads the entire email, attachments and all, rather than just the structure.
I guess it's an undocumented feature, not a bug.

I had assumed that the script would have only downloaded the amount of data that was returned, but my script downloaded a cumulative 2.5gig before i noticed.  Hopefully no-one else will have this happen.
up
2
aperez at informatica dot 24ruedas dot com
20 years ago
This is my function to check attachments:

<?php
  $est
=imap_fetchstructure($con,$msg);

  if(
checkAttachments($est))
  {
    echo(
"You have attachment(s)");
  }
  else
  {
    echo(
"You don't have attachment(s)");
  }
 
  function
checkAttachments($estMsg)
  {
    if((
$estMsg->type!=0) && ($estMsg->type!=1))
    {
     
// Text and multipart parts will not be shown as attachments
     
return(true);
    }
    else
    {
     
// If there's no attachments, parts inside will be checked
     
if($estMsg->parts)
      {
       
$partMsg=$estMsg->parts;
       
$i=0;
       
// Parts will be checked while no attachments found or not all of them checked
       
while(!(checkAttachments($partMsg[$i])) && ($i<sizeof($estMsg->parts)))
        {
         
$i++;
        }

       
// If any 'checkAttachment' calls returned 'true', 'i' should be
        // equal to number of parts(after increased in while). So, no
        // attachments found
       
if($i==sizeof($estMsg->parts))
        {
          return(
false);
        }
        else
        {
          return(
true);
        }
      }
      else
      {
       
// If no parts and text or multipart type, no attachments
       
return(false); 
      }
    }
  }
 
imap_close($con);
?>

I hope this helps somebody else(if bugs, thanks for your fixes).
up
1
hello at ivanbogomolov dot ru
5 years ago
If you logic based on compare structure strings, you must compare it case insensetive.
<?php

$p
= imap_fetchstructure($this->_imap_resource, $mid);
//do not compare $p->disposition == 'INLINE'
if(preg_match('/inline/i', $p->disposition))
{
//this works
}
?>
up
1
alchrystal88 at web dot de
5 years ago
If you have errors with wrong attachment names like this:

correct name:
String -> Prüfbericht Hersteller.pdf

fetchstructure object name:
=?ISO-8859-1?Q?Pr=FCfbericht_Hersteller=2Epdf?=

Workaround to reconvert:

imap_mime_header_decode($fetchstructure->dparameters->value)[0]->text

imap_mime_header_decode($filename)[0]->text
up
2
es96 at hotmail dot com
21 years ago
If you are getting CHARSET as US-ASCII even if the header has a Content-Type: field, make sure the header also has a MIME-Version: field.

For example, the following header will correcty report charset as KOI8-R

MIME-Version: 1.0
Content-Type: text/plain; charset="koi8-r"

Without the MIME-Version it will be reported as US-ASII
up
2
jcastro at elnuevodia dot com
22 years ago
I think the above note about attachments is wrong. I tested sending files with and without attachments and I get the following<br>
with attachment: type=3 bytes=343648
no attachment: type=0 bytes=2
so checking for $struct->bytes == " " means nothing. At least in my test
running windows 2000, php4.2.1 using outlook and exchange. It seems that cheking the type will be more reliable
up
0
burninleo at gmx dot net
14 years ago
Use imap_errors() to get rid of sticky error messages about incorrect MIME headers.
up
2
hholzgra at media-engineering dot de
25 years ago
the parts objects are identical in
structure to those returned by imap_fetchstructure, describing one subfolder each

parameters and dparameters are MIME-specific, theese contain the
extra parameters provided in the Content-Type and Content-Disposion Header lines such as Charset or Filename
up
0
Rui Torre rui_sharkye at hotmail dot com
16 years ago
I've made this two functions to decode mime headers and so far they are working just fine decoding all headers. Please let me know if you find any error. Hope they are useful for you too.

<?php

function decode_headers($cabecalho){
        while (
preg_match('/^(.*)=\?([^?]*)\?(Q|B)\?(.*)$/Ui', $cabecalho)){
           
$cabecalho = preg_replace("/(\t|\r|\n)/", "", $cabecalho);
           
$cabecalho = preg_replace("/\?(Q|B)\?=/Ui", "?\\1? =", $cabecalho);
              
           
$partes_cabecalho = explode("?=", $cabecalho);
           
           
$resultado = "";
            foreach (
$partes_cabecalho as $texto){
               
$texto = preg_replace("/\?(Q|B)\?\s=/Ui", "?\\1?=", $texto);
               
$texto = preg_replace("/\?(Q|B)\?/Ui", "=?\\1=?", $texto);
               
                if (
preg_match('/^(.*)=\?([^?]*)=\?(Q|B)=\?(.*)$/Ui', $texto)){
                   
$partes_texto = explode ("=?", $texto); 
                   
                   
$parte = descodificar_parte($partes_texto[0], $partes_texto[1]);
                   
$parte .= descodificar_parte($partes_texto[3], $partes_texto[2]);
                } else {
                   
$parte = $texto
                } 
               
                if (
strtoupper($partes_texto[1]) == "UTF-8"){ $parte = utf8_decode($parte);} 
               
$resultado .= $parte
            }
           
$cabecalho = $resultado;
        }
       
        return
trim($cabecalho);      
    }
   
    function
descodificar_parte($texto, $codificacao){
        if (
trim($texto) == ""){ return "";}
       
        if (
ucfirst($codificacao) == "B"){
           
$texto = base64_decode($texto);
           
$texto = htmlspecialchars($texto);
        } else if (
ucfirst($codificacao) == "Q"){
           
$texto = str_replace("_", " ", $texto);
           
$texto = quoted_printable_decode($texto); 
        }
        return
$texto;
    }

?>
up
0
oersoep at gmail dot com
18 years ago
Please keep in mind that the "parameters" array is a stdClass object when there are no parameters, and NOT a zero-size array.
In PHP5 you'll get this error when iterating structure->parts[x]->parameters if there aren't any parameters for this part:

PHP Fatal error:  Cannot use object of type stdClass as array in file.php on line 100
up
0
spam at emielmols dot info
19 years ago
I've created a function which simply extracts basic message content as a string ($content) and attachments as an array ($attachments). I've tested it with messages from Outlook, Outlook Express and Hotmail. Perhaps it's useful to anyone.

<?php
        $struct
= imap_fetchstructure($mbox, $mid);
       
       
$parts = $struct->parts;
       
$i = 0;

        if (!
$parts) { /* Simple message, only 1 piece */
         
$attachment = array(); /* No attachments */
         
$content = imap_body($mbox, $mid);
        } else {
/* Complicated message, multiple parts */
       
         
$endwhile = false;
       
         
$stack = array(); /* Stack while parsing message */
         
$content = "";    /* Content of message */
         
$attachment = array(); /* Attachments */
       
         
while (!$endwhile) {
            if (!
$parts[$i]) {
              if (
count($stack) > 0) {
               
$parts = $stack[count($stack)-1]["p"];
               
$i     = $stack[count($stack)-1]["i"] + 1;
               
array_pop($stack);
              } else {
               
$endwhile = true;
              }
            }
         
            if (!
$endwhile) {
             
/* Create message part first (example '1.2.3') */
             
$partstring = "";
              foreach (
$stack as $s) {
               
$partstring .= ($s["i"]+1) . ".";
              }
             
$partstring .= ($i+1);
           
              if (
strtoupper($parts[$i]->disposition) == "ATTACHMENT") { /* Attachment */
               
$attachment[] = array("filename" => $parts[$i]->parameters[0]->value,
                                     
"filedata" => imap_fetchbody($mbox, $mid, $partstring));
              } elseif (
strtoupper($parts[$i]->subtype) == "PLAIN") { /* Message */
               
$content .= imap_fetchbody($mbox, $mid, $partstring);
              }
            }

            if (
$parts[$i]->parts) {
             
$stack[] = array("p" => $parts, "i" => $i);
             
$parts = $parts[$i]->parts;
             
$i = 0;
            } else {
             
$i++;
            }
          }
/* while */
       
} /* complicated message */

       
echo "Analyzed message $mid, result: <br />";
        echo
"Content: $content<br /><br />";
        echo
"Attachments:"; print_r ($attachment);
?>
up
0
passeniermaxime at hotmail dot com
20 years ago
I have written a code that check's for attachments.
There is a slight problem with the above code;
when you send a mail with a .txt document as attachment
the type will be 0 and so there won't be a attachment found.

I check for the disposition of the body part witch can be inline or attachment.
If this is an attachment he will display it.
You an also mix up both codes by checking on the type and the disposition.

//////////////////////////////////
///   $mbox = connection
///   $a_mails_sort[$i] = message id

$structuur = imap_fetchstructure($mbox,$a_mails_sort[$i]);
$bericht_delen=$structuur->parts;
for($i_delen = 0; $i_delen<=count($bericht_delen);$i_delen++)
  {
  if($bericht_delen[$i_delen]->disposition == "attachment")
    {
    $attachment = "OK";
    }// end if
  }//end for
up
0
as1 at powersite dot com dot ar
21 years ago
If your imap_fetchstructure not retrive all parts, write it:

$struct = imap_fetchstructure( $conn, $curMsg );

$i = 1;
while( imap_fetchbody( $conn, $curMsg, $i ) ){
   $struct->parts[$i-1] = imap_bodystruct( $conn, $curMsg, $i );
   $i++;
}
up
0
paradise at ml75 dot com
22 years ago
I'm using freeBSD and had lots of problems with all functions/mail applications i could find on the net...

So i'm writing my own now..

Most of the stuff works fine now.. but i'm still working on the chooser to select the best part to show. (plain or HTML).

The #1 problem is that most functions give the wrong ID numbers beck to fetch the body.

Here's a sollution to that problem.. :P

// Debug function.. always handy until it's 100% completed..
   $attachments = array(); $dispos=0;
   $debug=true;
   function doDebug($msg){
      global $debug, $debug_depth, $dispos;
      if($debug){
         for($y = 0; $y < $debug_depth; $y++) echo("&nbsp;&nbsp;&nbsp;&nbsp;");
         if($dispos == 0) echo($msg."<br/>"); else echo("<i>".$msg."</i><br/>");
      }
   }

   function Check_Part($part,$id){
      global $attachments, $debug, $debug_depth, $dispos; $ans=sizeof($attachments);
      doDebug("+Checking part $id");
      $debug_depth++;
      doDebug("Parttype=".$part->type ."/". mimetype($part->type) ."/". $part->subtype);

      if($part->ifdisposition){ $dispos++; doDebug("<b>Disposition</b>"); }
      if($part->ifdescription){ doDebug("<b>Description=".$part->description."</b>"); }

         switch($part->type){
           case 1: if((strtolower($part->subtype) == "mixed")
                        or (strtolower($part->subtype)  == "alternative")
                        or (strtolower($part->subtype) == "related")) break;

           default: $an = sizeof($attachments);
                    if($part->ifdparameters){
                       $dpara = $part->dparameters;
                       for ($v=0;$v<sizeof($dpara);$v++){
                          if (eregi("filename", $dpara[$v]->attribute))
                             $fname = $dpara[$v]->value;}
                    }
                    if($part->ifparameters){
                       if(empty($fname)){
                          $para = $part->parameters;
                          for ($v=0;$v<sizeof($para);$v++){
                             if (eregi("name", $para[$v]->attribute)) $fname = $para[$v]->value;
                          }
                       }
                    }
                    if(empty($fname))$fname = "Onbekend";

                  $attachments[$an]->id = ($an+1);
                  $attachments[$an]->part = $id;
                    $attachments[$an]->filename = $fname;
                   $attachments[$an]->type=$part->type;
                   $attachments[$an]->subtype=$part->subtype;
                   $attachments[$an]->dispos=$dispos;
                   $attachments[$an]->size=$part->bytes;
                  doDebug("Filename=".$attachments[$an]->filename); break;
         }

      for($x = 0; $x < count($part->parts); $x++){
         Check_Part($part->parts[$x], $id.".".($x+1));
      }
      doDebug("Dit deel had iets nuttigs.");
      if($part->ifdisposition) $dispos--;
      $debug_depth--;
      doDebug("-End of part $id");
   }

   function decode_mail($mbox, $msg){
      global $attachments, $debug, $debug_depth;
      doDebug("+Decoding message $msg"); $debug_depth++;
      $obj = imap_fetchstructure($mbox, $msg, FT_UID); $moet_werken=true; $id=1;
      while($moet_werken){
         Check_Part($obj, $id);

         $moet_werken=false;
      }
      $debug_depth--; doDebug("-End of message $msg");
   }

Now to get this to work..
Decode_Mail([Mailbox connection], [Message Number]);

this is the outpu attachment array..
1--1.1.1--Onbekend--0--PLAIN--0--31
2--1.1.2--Onbekend--0--HTML--0--29
3--1.2--Beschrijving Nieuw E.doc--3--MSWORD--1--613718
--------------------------------------

PS.. "Onbekend" is dutch for "Unknown".

Good luck.
Paradise (www.ml75.nl)
up
0
Anonymous
22 years ago
This function sets MIME charset
parameter value to US-ASCII when
either Content-Type header is missing
or Content-Type header doesn't have
charset parameter. This may be compliant
to RFC 822, but this poses
a problem for  a calling function that
has to deal with untagged messages
in MIME charset other than US-ASCII.
To work around this,  US-ASCII
has to be treated as the absence of MIME charset.
up
0
vksgeneric at hotmail dot com
24 years ago
It looks like some clients (e.g. outlook) can attach a file that has two mime-types associated with it, like:

Content-Type: model/vrml,x-world/x-vrml

This bombs the parsing procedure somewhere in c-client, I guess, and as a result ifparameters is set to false for the respective part, and you can not retrieve filename, mimetype, or any other parameters.
up
-1
hans at lintoo dot dk
19 years ago
A class for searching through a stucture object.
You can search matching 1-3 parameters or use the methods included.
Feel free to use it, correct it or extend it if you please. Enjoy!

http://lintoo.dk/public/structureengine.class.phps

The code was too wide for this manual, so I hope the link above will do. Here a quick overview instead:

<?php
class StructureEngine {
    private
$structureObject;
    public function
__construct($structureInfo) {}
    public function
locatePlain() {}
    public function
locateHTML() {}
    public function
locateAttachments() {}
    private function
checkParam($part, $type, $value) {}
    private function
findParts($partsArray, $prefix, $param1Type = 'type', $param1Value = 0, $param2Type = null, ...) {}
}
?>
up
-4
Anonymous
11 years ago
I improved the code from this post (
http://www.php.net/manual/en/function.imap-fetchstructure.php#51497) a bit, now it handles multi embedded email correctly, and gives you a content with not marking character, like the '=' (soft line break).

<?php
//return is structured as
// $mail = array ( 'body' => ...
//          'attachment' => array() );
function _get_body_attach($mbox, $mid) {
   
$struct = imap_fetchstructure($mbox, $mid);

   
$parts = $struct->parts;
   
$i = 0;
    if (!
$parts) { /* Simple message, only 1 piece */
       
$attachment = array(); /* No attachments */
       
$content = imap_body($mbox, $mid);
    } else {
/* Complicated message, multiple parts */

       
$endwhile = false;

       
$stack = array(); /* Stack while parsing message */
       
$content = "";    /* Content of message */
       
$attachment = array(); /* Attachments */

       
while (!$endwhile) {
            if (!
$parts[$i]) {
                if (
count($stack) > 0) {
                   
$parts = $stack[count($stack)-1]["p"];
                   
$i     = $stack[count($stack)-1]["i"] + 1;
                   
array_pop($stack);
                } else {
                   
$endwhile = true;
                }
            }

            if (!
$endwhile) {
               
/* Create message part first (example '1.2.3') */
               
$partstring = "";
                foreach (
$stack as $s) {
                   
$partstring .= ($s["i"]+1) . ".";
                }
               
$partstring .= ($i+1);

                if (
strtoupper($parts[$i]->disposition) == "ATTACHMENT" || strtoupper($parts[$i]->disposition) == "INLINE") { /* Attachment or inline images */
                   
$filedata = imap_fetchbody($mbox, $mid, $partstring);
                    if (
$filedata != '' ) {
                       
// handles base64 encoding or plain text
                       
$decoded_data = base64_decode($filedata);
                        if (
$decoded_data == false ) {
                           
$attachment[] = array("filename" => $parts[$i]->parameters[0]->value,
                               
"filedata" => $filedata);
                        } else {
                           
$attachment[] = array("filename" => $parts[$i]->parameters[0]->value,
                               
"filedata" => $decoded_data);
                        }
                    }
                } elseif (
strtoupper($parts[$i]->subtype) == "PLAIN" && strtoupper($parts[$i+1]->subtype) != "HTML") { /* plain text message */
                   
$content .= imap_fetchbody($mbox, $mid, $partstring);
                } elseif (
strtoupper($parts[$i]->subtype) == "HTML" ) {
                   
/* HTML message takes priority */
                   
$content .= imap_fetchbody($mbox, $mid, $partstring);
                }
            }

            if (
$parts[$i]->parts) {
                if (
$parts[$i]->subtype != 'RELATED' ) {
                   
// a glitch: embedded email message have one additional stack in the structure with subtype 'RELATED', but this stack is not present when using imap_fetchbody() to fetch parts.
                   
$stack[] = array("p" => $parts, "i" => $i);
                }
               
$parts = $parts[$i]->parts;
               
$i = 0;
            } else {
               
$i++;
            }
        }
/* while */
   
} /* complicated message */

   
$ret = array();
   
$ret['body'] = quoted_printable_decode($content);
   
$ret['attachment'] = $attachment;
    return
$ret;
}
?>
up
-1
richy at smilingsouls dot net
20 years ago
After many long hours of pouring over the object returned by imap_fetchstructure() I came up with the solution posted at the following url:

http://p2p.wrox.com/topic.asp?TOPIC_ID=9063

The url above provides an example of how to access variables in the object returned by imap_fetchstructure().  This class creates an object with member variables containing the correct part numbers which are compatible with the imap_fetchbody() function.

The structure returned looks something like this:

                       0 Raw Headers
$this->pid[$mid][0]  =  1 text/plain                        top level message
$this->pid[$mid][1]  =  2 message/rfc822                    entire unparsed message
                                                             multipart/mixed - part is skipped!
                           2.0 Raw Headers
$this->pid[$mid][2]  =      2.1 multipart/alternative       contains unparsed contents of next subpart (text/plain && text/html)
                                2.1.0 Raw Headers
$this->pid[$mid][3]  =          2.1.1 text/plain            2nd level inline embedded message
$this->pid[$mid][4]  =          2.1.2 text/html

$this->pid[$mid][5]  =      2.2 message/rfc822              entire unparsed message
                                                             multipart/mixed - part is skipped!
                                2.2.0 Raw Headers
$this->pid[$mid][6]  =          2.2.1 multipart/alternative
$this->pid[$mid][7]  =              2.2.1.1 text/plain      3rd level inline embedded message
$this->pid[$mid][8]  =              2.2.1.2 text/html
$this->pid[$mid][9]  =            2.2.2 image/jpeg
$this->pid[$mid][10] =             2.2.3 image/jpeg
$this->pid[$mid][11] =            2.2.4 image/jpeg
$this->pid[$mid][12] =            2.2.5 image/gif

Its important to note why multipart/related, multipart/mixed and certain multipart/alternative (if parent part is message/rfc822 && not multipart/mixed) parts are skipped, imap_fetchbody **does not** retreive a message part for these parts.  Which isn't a bug, as I have read in other places, these parts exist for the purpose of designing the viewer portion of a mail application.  Also, headers aren't assigned part numbers here because they are easy to pick out.

: )
Rich
To Top