PHPのお勉強!

PHP TOP

imap_fetchbody

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

imap_fetchbodyメッセージ本文中の特定のセクションを取り出す

説明

imap_fetchbody(
    IMAP\Connection $imap,
    int $message_num,
    string $section,
    int $flags = 0
): string|false

指定されたメッセージ本文中の特定のセクションを取得します。 本文パートは、この関数ではデコードされません。

パラメータ

imap

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

message_num

メッセージ番号。

section

パート番号。ピリオドで区切られた整数文字列を指定します。 これは、IMAP4 仕様における本文パートのリストへのインデックスとなります。

flags

ビットマスクであり、以下の組合わせとなります。

  • FT_UID - message_numは UID である
  • FT_PEEK - 既に設定されていない場合、 \Seen フラグを設定しない
  • FT_INTERNAL - 内部フォーマットで文字列を返す。CRLF に正規化しない。

戻り値

指定されたメッセージ本文中の特定のセクションをテキスト文字列で返します。 失敗した場合に false を返します

変更履歴

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

参考

add a note

User Contributed Notes 35 notes

up
74
atamido at gmail dot remove dot com
15 years ago
imap-fetchbody() will decode attached email messages inline with the rest of the email parts, however the way it works when handling attached email messages is inconsistent with the main email message.

With an email message that only has a text body and does not have any mime attachments, imap-fetchbody() will return the following for each requested part number:

(empty) - Entire message
0 - Message header
1 - Body text

With an email message that is a multi-part message in MIME format, and contains the message text in plain text and HTML, and has a file.ext attachment, imap-fetchbody() will return something like the following for each requested part number:

(empty) - Entire message
0 - Message header
1 - MULTIPART/ALTERNATIVE
1.1 - TEXT/PLAIN
1.2 - TEXT/HTML
2 - file.ext

Now if you attach the above email to an email with the message text in plain text and HTML, imap_fetchbody() will use this type of part number system:

(empty) - Entire message
0 - Message header
1 - MULTIPART/ALTERNATIVE
1.1 - TEXT/PLAIN
1.2 - TEXT/HTML
2 - MESSAGE/RFC822 (entire attached message)
2.0 - Attached message header
2.1 - TEXT/PLAIN
2.2 - TEXT/HTML
2.3 - file.ext

Note that the file.ext is on the same level now as the plain text and HTML, and that there is no way to access the MULTIPART/ALTERNATIVE in the attached message.

Here is a modified version of some of the code from previous posts that will build an easily accessible array that includes accessible attached message parts and the message body if there aren't multipart mimes. The $structure variable is the output of the imap_fetchstructure() function. The returned $part_array has the field 'part_number' which contains the part number to be fed directly into the imap_fetchbody() function.

<?php
function create_part_array($structure, $prefix="") {
//print_r($structure);
if (sizeof($structure->parts) > 0) { // There some sub parts
foreach ($structure->parts as $count => $part) {
add_part_to_array($part, $prefix.($count+1), $part_array);
}
}else{
// Email does not have a seperate mime attachment for text
$part_array[] = array('part_number' => $prefix.'1', 'part_object' => $obj);
}
return
$part_array;
}
// Sub function for create_part_array(). Only called by create_part_array() and itself.
function add_part_to_array($obj, $partno, & $part_array) {
$part_array[] = array('part_number' => $partno, 'part_object' => $obj);
if (
$obj->type == 2) { // Check to see if the part is an attached email message, as in the RFC-822 type
//print_r($obj);
if (sizeof($obj->parts) > 0) { // Check to see if the email has parts
foreach ($obj->parts as $count => $part) {
// Iterate here again to compensate for the broken way that imap_fetchbody() handles attachments
if (sizeof($part->parts) > 0) {
foreach (
$part->parts as $count2 => $part2) {
add_part_to_array($part2, $partno.".".($count2+1), $part_array);
}
}else{
// Attached email does not have a seperate mime attachment for text
$part_array[] = array('part_number' => $partno.'.'.($count+1), 'part_object' => $obj);
}
}
}else{
// Not sure if this is possible
$part_array[] = array('part_number' => $prefix.'.1', 'part_object' => $obj);
}
}else{
// If there are more sub-parts, expand them out.
if (sizeof($obj->parts) > 0) {
foreach (
$obj->parts as $count => $p) {
add_part_to_array($p, $partno.".".($count+1), $part_array);
}
}
}
}
?>
up
72
tom at tomwardrop dot com
16 years ago
If text has been encoded as quoted-printable (most body text is encoded as this), it must be decoded for it to be displayed correctly (without '=', '=20' and other strange text chunks all through the string).

To decode, you can use the following built-in php function...

quoted_printable_decode($string)

Hopefully I've just saved a few people from having to do a preg_replace on there email bodies.
up
15
jab_creations at yahoo dot com
4 years ago
The third parameter, the section parameter is not properly documented and I've seen a dozen or two developers in my research to figure this issue out come to hopeless conclusions. It's critical to understand that various email providers (e.g. AOL, Gmail, Microsoft, Yahoo and client programs like Thunderbird and...yes, the five thousand versions of Outlook) do NOT follow some "standard".

- Unlike JavaScript and how PHP normally function all index numbers start at 1, NOT 0.
- Your third/section parameter will be a whole number if there is not a second 'parts' array (I am NOT using classes here).
- If the 'parts' array contains a 'parts' subarray you will have floating-point numbers (e.g. 1.1 or 2.1 instead of 1 or 2). If you have two parts in the 'parts' subarray you will have something such as 1.1 and 1.2 or 2.1 and 2.2 - REMEMBER: these vary by provider and there is no "STANDARD" as this is a PHP-specific issue!
- None, some or all 'parts' arrays may be singular or contain child 'parts' subarrays, this is completely dynamic and may NOT be consistent from even the same provider!

The following presumes you have a $mail_connection and know which $email_number number you're working with:

<?php
$i
= 0;//Level 1 part references.
$j = 0;//Level 2 part references.
$mail_message_structure = json_decode(json_encode(imap_fetchstructure($mail_connection, $email_number)), true);//Convert AWAY from class style.

while ($i < count($mail_message_structure['parts']))
{
if (isset(
$mail_message_structure['parts'][$i]['parts']))
{
//Multiple, contains secondary 'parts' array.
while ($j < count($mail_message_structure['parts'][$i]['parts']))
{
//Remove subjectivity of "Png/PNG/png" with always lower-case.
$mail_message_structure['parts'][$i]['parts'][$j]['subtype'] = strtolower($mail_message_structure['parts'][$i]['parts'][$j]['subtype']);
$mail_message_structure['parts'][$i]['parts'][$j]['id_imap_fetchbody'] = ($i + 1).'.'.($j + 1);
echo
'<p>'.$i.': multi, '.$j.' '.$mail_message_structure['parts'][$i]['parts'][$j]['subtype'].': '.($i + 1).'.'.($j + 1).'</p>';
$j++;
}
}
else
{
//Single, first level.
//Remove subjectivity of "Png/PNG/png" with always lower-case.
$mail_message_structure['parts'][$i]['subtype'] = strtolower($mail_message_structure['parts'][$i]['subtype']);
$mail_message_structure['parts'][$i]['id_imap_fetchbody'] = ($i + 1);
echo
'<p>'.$i.': single: '.$mail_message_structure['parts'][$i]['subtype'].': '.($i + 1).'</p>';
}
$i++;
}
?>

Now that we can actually understand HOW to access specific PARTS of the email body it is a simple manner to iterate over the structure array:

<?php
foreach ($mail_message_structure['parts'] as $k4 => $v4)
{
if (isset(
$v4['parts']))
{
foreach (
$v4['parts'] as $k5 => $v5) {mail_imap_body($mail_connection, $email_number, $v5);}//echo '<div>MULTI: '.$v5['id_imap_fetchbody'].'; <pre>'.htmlspecialchars(print_r($v4['parts'],1)).'</pre></div>';
}
else {
mail_imap_body($mail_connection, $email_number, $v4);}//echo '<div>SINGLE: '.$v4['id_imap_fetchbody'].'<pre>'.htmlspecialchars(print_r($v4,1)).'</pre></div>';
}
?>
up
10
red dot ender at yahoo dot com
11 years ago
I had some problems with the german umlauts from a gmail fetched text. I lost an hour trying to find a solution.

Hopefully this helps someone in need :)

<?php
$text
= trim( utf8_encode( quoted_printable_decode(
imap_fetchbody( $this->inbox, $emailNo, $section ) ) ) );

$section was previously defined:
$section = empty( $attachments ) ? 1 : 1.1;
?>
up
8
gesti at gmx dot com
12 years ago
How to give more then one option in the "options" parameter:
It took me some time to realize how easy it is, so just in case some one else would be puzzled at this point:
<?php
imap_fetchbody
($imap_stream, $msg_number, $section, FT_UID | FT_PEEK);
?>
up
4
mike at lathyrus dot net
9 years ago
I spent hours religously thinking that

imap_fetchbody($mbox,$email_number,1.2)

would fetch the html body, and in many cases this did not work. It turns out that simple message can have a simple [parts] stucture so this became true:

imap_fetchbody($mbox,$email_number,1) - PLAIN
imap_fetchbody($inbox,$email_number,2) - HTML

Check for empty string before using the latter.
up
1
rfinnie at kefta dot com
24 years ago
With regard to message/rfc822 parts, it appears that the IMAP c-client that PHP uses does not follow the IMAP RFC (#2060) correctly. The RFC specifies that to grab the headers for a message/rfc822 part, you would request part "2.HEADER" (assuming 2 is your part number), and the full text of the part would be "2.TEXT". Instead, "2.0" will grab the headers, and just "2" will get the text.
up
2
caevan at amkd dot com dot au
12 years ago
I tried using the function add_part_to_array below and noticed a few errors were thrown. If there are no 'parts' in the structure then if (sizeof($obj->parts) > 0) will throw an error. As well $prefix is not defined here is the code I have updated. I have left the original lines commented out. I have tested reading emails from a gmail account and so far so good.

<?php

// Sub function for create_part_array(). Only called by create_part_array() and itself.
function add_part_to_array($obj, $partno, & $part_array) {
$part_array[] = array('part_number' => $partno, 'part_object' => $obj);

if (
$obj->type == 2) { // Check to see if the part is an attached email message, as in the RFC-822 type
// if (sizeof($obj->parts) > 0) { // Check to see if the email has parts
if(array_key_exists('parts',$obj){
foreach (
$obj->parts as $count => $part) {
// Iterate here again to compensate for the broken way that imap_fetchbody() handles attachments
if (sizeof($part->parts) > 0) {
foreach (
$part->parts as $count2 => $part2) {
add_part_to_array($part2, $partno.".".($count2+1), $part_array);
}
}else{
// Attached email does not have a seperate mime attachment for text
$part_array[] = array('part_number' => $partno.'.'.($count+1), 'part_object' => $obj);
}
}
}else{
// Not sure if this is possible
// $part_array[] = array('part_number' => $prefix.'.1', 'part_object' => $obj);
$part_array[] = array('part_number' => $partno.'.1', 'part_object' => $obj);
}
}else{
// If there are more sub-parts, expand them out.
// if (sizeof($obj->parts) > 0) {
if(array_key_exists('parts',$obj)){
foreach (
$obj->parts as $count => $p) {
add_part_to_array($p, $partno.".".($count+1), $part_array);
}
}
}
}
?>
up
4
ulrich at kaldamar dot de
21 years ago
The function imap_fetchbody() seems uncapable of getting subordinate parts of a message/rfc822-part. I had problems with getting an attachment that was forwarded in such a part, because the object given by imap_fetchstructure() would assume that the part was represented by the string "2.1.2.1.2".

So I wrote this set of functions which parses the raw message-body and creates an array with the struture corresponding to the structure given by imap_fetchstructure(). The function mail_fetchpart() (see below) will work on the array and return even those parts that I could not get with imap_fetchbody().

Example usage of this function: mail_fetchpart($mbox, 2, "2.1.2.1.2");

Note: If the message does not contain multiple pars, the body of the message can be accessed by the part-string "1".
I have more functions for parsing and decoding messages, just email me.

// get the body of a part of a message according to the
// string in $part
function mail_fetchpart($mbox, $msgNo, $part) {
$parts = mail_fetchparts($mbox, $msgNo);

$partNos = explode(".", $part);

$currentPart = $parts;
while(list ($key, $val) = each($partNos)) {
$currentPart = $currentPart[$val];
}

if ($currentPart != "") return $currentPart;
else return false;
}

// splits a message given in the body if it is
// a mulitpart mime message and returns the parts,
// if no parts are found, returns false
function mail_mimesplit($header, $body) {
$parts = array();

$PN_EREG_BOUNDARY = "Content-Type:(.*)boundary=\"([^\"]+)\"";

if (eregi ($PN_EREG_BOUNDARY, $header, $regs)) {
$boundary = $regs[2];

$delimiterReg = "([^\r\n]*)$boundary([^\r\n]*)";
if (eregi ($delimiterReg, $body, $results)) {
$delimiter = $results[0];
$parts = explode($delimiter, $body);
$parts = array_slice ($parts, 1, -1);
}

return $parts;
} else {
return false;
}


}

// returns an array with all parts that are
// subparts of the given part
// if no subparts are found, return the body of
// the current part
function mail_mimesub($part) {
$i = 1;
$headDelimiter = "\r\n\r\n";
$delLength = strlen($headDelimiter);

// get head & body of the current part
$endOfHead = strpos( $part, $headDelimiter);
$head = substr($part, 0, $endOfHead);
$body = substr($part, $endOfHead + $delLength, strlen($part));

// check whether it is a message according to rfc822
if (stristr($head, "Content-Type: message/rfc822")) {
$part = substr($part, $endOfHead + $delLength, strlen($part));
$returnParts[1] = mail_mimesub($part);
return $returnParts;
// if no message, get subparts and call function recursively
} elseif ($subParts = mail_mimesplit($head, $body)) {
// got more subparts
while (list ($key, $val) = each($subParts)) {
$returnParts[$i] = mail_mimesub($val);
$i++;
}
return $returnParts;
} else {
return $body;
}
}

// get an array with the bodies all parts of an email
// the structure of the array corresponds to the
// structure that is available with imap_fetchstructure
function mail_fetchparts($mbox, $msgNo) {
$parts = array();
$header = imap_fetchheader($mbox,$msgNo);
$body = imap_body($mbox,$msgNo, FT_INTERNAL);

$i = 1;

if ($newParts = mail_mimesplit($header, $body)) {
while (list ($key, $val) = each($newParts)) {
$parts[$i] = mail_mimesub($val);
$i++;
}
} else {
$parts[$i] = $body;
}
return $parts;

}
up
1
web at i-ps dot net
17 years ago
I had an issue with the content that was returned by imap_fetchbody - either the function itself, or the mail-server, was inserting "=\r\n" at points into the text body returned. This may depend upon the content type (i.e. plain text / csv, as opposed to something like a Word document), but you may need to do something like:

$body = preg_replace("/=(\r?)\n/", '', imap_fetchbody($mailbox, $message, $part));
up
1
jbr at ya-right dot com
18 years ago
Musawir Ali comment is not totally correct, ifdisposition= 1 will never tell you 100% if there is an attachment. You must look at (ifdparameters, ifparameters) if (1) of them is greater then (0), then you need to look inside (parameters), looking at each ($obj->parameters) and check the (attribute) for (NAME, FILENAME), also be sure to set them to upper or lower case before doing your testing. That's the only way to know if you have an attachment. inline attachments will have $obj->ifid equal to 1, the $obj->id will contain the (cid). If $obj->ifid equals 0 then it's also an attachment (file type) if you have (NAME, FILENAME) as the current parameters (attribute).

<?php
$name
= '';

if (
$parent->ifdparameters && sizeof ( $parent->dparameters ) > 0 )
{
foreach (
$parent->dparameters as $child )
{
if (
strtolower ( $child->attribute ) == 'name' || strtolower ( $child->attribute ) == 'filename' )
{
$name = strtolower ( $child->value );
}
}
}

if ( empty (
$name ) )
{
if (
$parent->ifparameters && sizeof ( $parent->parameters ) > 0 )
{
foreach (
$parent->parameters as $child )
{
if (
strtolower ( $child->attribute ) == 'name' || strtolower ( $child->attribute ) == 'filename' )
{
$name = strtolower ( $child->value );
}
}
}
}
?>

$parent is derived from imap_fetchstructure(), as a child object if $obj->parts is set or as the $parent if $obj->parts is not set!
up
2
jsimlo yahoo com
20 years ago
this may be the way, how to obtain all those part_numbers.... even when a message contains another message attached, and it contains another message attached...

<?
$parttypes = array ("text", "multipart", "message", "application", "audio", "image", "video", "other");
function buildparts ($struct, $pno = "") {
global $parttypes;
switch ($struct->type):
case 1:
$r = array (); $i = 1;
foreach ($struct->parts as $part)
$r[] = buildparts ($part, $pno.".".$i++);

return implode (", ", $r);
case 2:
return "{".buildparts ($struct->parts[0], $pno)."}";
default:
return '<a href="?p='.substr ($pno, 1).'">'.$parttypes[$struct->type]."/".strtolower ($struct->subtype)."</a>";
endswitch;
}

$struct = imap_fetchstructure ($pop3mbox, $msguid, FT_UID);
echo buildparts ($struct);
?>

it will print something like:

<a href="?p=1">text/plain</a>, {<a href="?p=2.1">text/plain</a>, <a href="?p=2.2">text/html</a>}
up
1
atamido at gmail dot remove dot com
15 years ago
If you download an attachment labeled "winmail.dat" or "win.dat", or with the mime-type of "APPLICATION/MS-TNEF", this is a Microsoft Transport Neutral Encapsulation Format file. It is a proprietary method for encoding several files together in a single file. AFAIK only Outlook sends it with its default setting of sending emails in Rich Text Format.

As of PHP 5.2 there is no internal method of breaking apart these attachments.

There are external command line utilities that can be called from PHP. Alternately, it is possible to decode these files entirely in PHP. It appears that all current libraries are based on a plugin written by Graham Norbury for Squirrel Mail. The only ones I've seen are in IlohaMail, Telean, Horde-Imp, and NaSMail. The only one that I know that will also decode the RTF message is from NaSMail.

To use the NaSMail code, download the "TNEF Attachment Decoder" plugin and extract it to
plugins/attachment_tnef/

Then use this bit of code:
<?php
include_once('plugins/attachment_tnef/constants.php');
include_once(
'plugins/attachment_tnef/functions.php');
include_once(
'plugins/attachment_tnef/class/tnef.php');

// $tnef is a binary variable containing only the contents of winmail.dat
$attachment = &new TnefAttachment($tnef_debug);
$result = $attachment->decodeTnef($tnef);
$tnef_files = &$attachment->getFilesNested();
print_r($tnef_files); // See the format of the returned array
?>
up
1
php at NOSPAM dot sibyla dot cz
20 years ago
// $uid - msg number

function read_all_parts($uid){
global $mime,$ret_info,$enc;
$mime = array("text","multipart","message","application","audio",
"image","video","other","unknow");
$enc = array("7BIT","8BIT","BINARY","BASE64",
"QUOTED-PRINTABLE","OTHER");


$struct = imap_fetchstructure( $this -> Link, $uid );

$ret_info = array();

function scan($struct,$subkey){
global $mime,$enc,$ret_info;

foreach($struct as $key => $value){


if($subkey!=0){
$pid = $subkey.".".($key+1); }
else { $pid = ($key+1); }

$ret_info[]['pid'] = $pid;
$ret_info[key($ret_info)]['type'] = $mime["$value->type"];
$ret_info[key($ret_info)]['encoding'] = $enc["$value->encoding"];

next($ret_info);

if(($value->parts)!= null) {scan($value->parts,$pid); }
}

}

scan($struct->parts,0);

return $ret_info;

}
up
1
noose (at) nospam (dot) tweak (dot) pl
20 years ago
Sorry for my english....

for read the attachment's:

<?
foreach ($_GET as $k => $v)
{
$$k = $v;
}
$login = 'mylogin';
$password = 'mypassword';
$host = '{myhost:110/pop3}';

$mbox = imap_open("$host", "$login", "$password");
$struckture = imap_fetchstructure($mbox, $id);
$message = imap_fetchbody($mbox,$id,$part);
$name = $struckture->parts[$part]->dparameters[0]->value;
$type = $struckture->parts[$part]->typee;
############## type
if ($type == 0)
{
$type = "text/";
}
elseif ($type == 1)
{
$type = "multipart/";
}
elseif ($type == 2)
{
$type = "message/";
}
elseif ($type == 3)
{
$type = "application/";
}
elseif ($type == 4)
{
$type = "audio/";
}
elseif ($type == 5)
{
$type = "image/";
}
elseif ($type == 6)
{
$type = "video";
}
elseif($type == 7)
{
$type = "other/";
}
$type .= $struckture->parts[$part]->subtypee;
######## Type end

header("Content-typee: ".$type);
header("Content-Disposition: attachment; filename=".$name);

######## coding
$coding = $struckture->parts[$part]->encoding;
if ($coding == 0)
{
$message = imap_7bit($message);
}
elseif ($coding == 1)
{
$wiadomsoc = imap_8bit($message);
}
elseif ($coding == 2)
{
$message = imap_binary($message);
}
elseif ($coding == 3)
{
$message = imap_base64($message);
}
elseif ($coding == 4)
{
$message = quoted_printable($message);
}
elseif ($coding == 5)
{
$message = $message;
}
echo $message;
########## coding end
imap_close($mbox);
?>
up
0
tabaccoandcoffee at gmail dot com
10 years ago
Sorry for my English.
Rather than quoted_printable_decode, you can use the function imap_qprint ($body)
up
2
anonymous at coward dot village
17 years ago
A very easy way to handle attachments is just using munpack, with a subdirectory called attachments chmod it to 777 and then use the following bit of code (with mpack installed) on the MIME encoded body:

<?php
//email MIME body in $input
file_put_contents("attachments/body.eml",$input);
$command = "munpack -C attachments -fq body.eml";
exec($command,$output);
if(
$output[0]!='Did not find anything to unpack from body.eml') {
foreach (
$output as $attach) {
$pieces = explode(" ", $attach);
$part = $pieces[0];
echo
"<a href=\"attachments/$part\">$part</a><br>";
}
}
?>
up
1
atamido at gmail dot remove dot com
15 years ago
The previous post contains a copy/paste error and a little added complexity. The first function should look like this:

<?php
function create_part_array($struct) {
if (
sizeof($struct->parts) > 0) { // There some sub parts
foreach ($struct->parts as $count => $part) {
add_part_to_array($part, ($count+1), $part_array);
}
}else{
// Email does not have a seperate mime attachment for text
$part_array[] = array('part_number' => '1', 'part_object' => $struct);
}
return
$part_array;
}
?>