I'm just experimenting a little with the IMAP-Extension for PHP and I build the connection using an object like this:
function __construct($user, $pass, $server = 'my.mailserver.ch', $port = 143) {
$this->server = $server;
$this->user = $user;
$this->pass = $pass;
$this->port = $port;
if($this->conn = imap_open('{'.$this->server.'}', $this->user, $this->pass)){
$this->success = 1;
}else{
$this->success = -1;
}
$this->inbox();
}
I then use the following function to get the neccessary data from my mails.
(Which works)
function inbox() {
$this->msg_cnt = imap_num_msg($this->conn);
$in = array();
for($i = 1; $i <= $this->msg_cnt; $i++) {
$in[] = array(
'index' => $i,
'header' => imap_headerinfo($this->conn, $i),
'body' => imap_fetchbody($this->conn, $i),
'structure' => imap_fetchstructure($this->conn, $i)
);
}
$this->inbox = $in;
}
But if I now look into the body of the message, I get multiple different things besides the message, like:
--001a113ec946082fc705210e59c7 Content-Type: multipart/alternative; boundary=001a113ec946082fc005210e59c5 --001a113ec946082fc005210e59c5 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding
And so on. I checked for ways to get a clean body from that "multipart/alternative" but I couldn't find anything that helped me. Also there are several different Content-Types in the same body.
How do I get the clean body of the message, and do I have to handle it differently for differen content-types?
Thank you for your help.
Related
I found this PHP code to extract the emails. Now, I want to move this email into Custom Post type in WordPress. I have created the custom post type name as E-mail Inboxes.
Here is the code below of how I extracted the emails:
class Email_reader {
// imap server connection
public $conn;
// inbox storage and inbox message count
private $inbox;
private $msg_cnt;
// email login credentials
private $server = 'myserver.com';
private $user = 'myserver#mail.com';
private $pass = 'PASSWORD';
private $port = 993; // adjust according to server settings
// connect to the server and get the inbox emails
function __construct() {
$this->connect();
$this->inbox();
}
// close the server connection
function close() {
$this->inbox = array();
$this->msg_cnt = 0;
imap_close($this->conn);
}
// open the server connection
// the imap_open function parameters will need to be changed for the particular server
// these are laid out to connect to a Dreamhost IMAP server
function connect() {
$this->conn = imap_open('{'.$this->server.'/notls}', $this->user, $this->pass);
}
// move the message to a new folder
function move($msg_index, $folder='INBOX.Processed') {
// move on server
imap_mail_move($this->conn, $msg_index, $folder);
imap_expunge($this->conn);
// re-read the inbox
$this->inbox();
}
// get a specific message (1 = first email, 2 = second email, etc.)
function get($msg_index=NULL) {
if (count($this->inbox) <= 0) {
return array();
}
elseif ( ! is_null($msg_index) && isset($this->inbox[$msg_index])) {
return $this->inbox[$msg_index];
}
return $this->inbox[0];
}
// read the inbox
function inbox() {
$this->msg_cnt = imap_num_msg($this->conn);
$in = array();
for($i = 1; $i <= $this->msg_cnt; $i++) {
$in[] = array(
'index' => $i,
'header' => imap_headerinfo($this->conn, $i),
'body' => imap_body($this->conn, $i),
'structure' => imap_fetchstructure($this->conn, $i)
);
}
$this->inbox = $in;
}
}
$emails = new Email_reader;
echo "<pre>";
var_dump($emails);
Now I want to add this email to my WordPress Custom Post Type.
Thanks in advance.
$total = $emails->total_msg();
for ($j=1; $j <= $total; $j++) {
$mail = $emails->get($j);
$post_array = array(
'post_content' => $mail['body'],
'post_title' => $mail['header']->subject,
'post_type' => 'faqpress_email',
'post_status' => 'publish',
'meta_input' => array(
'from' => $mail['header'] ->fromaddress,
'email_date'=> $mail['header'] ->Date, // add post meta as many as you want
'ticket_id' => $mail['header']->Msgno,
),
);
wp_insert_post($post_array);
}
Using the following class:
class Email_reader {
// imap server connection
public $conn;
// inbox storage and inbox message count
private $inbox;
private $msg_cnt;
// email login credentials
private $server = 'xxxxxxxxxxxx.com';
private $user = 'admin#xxxxxxxxx.com';
private $pass = 'xxxxxxxxx';
private $port = 143; // adjust according to server settings
// connect to the server and get the inbox emails
function __construct() {
$this->connect();
$this->inbox();
}
// close the server connection
function close() {
$this->inbox = array();
$this->msg_cnt = 0;
imap_close($this->conn);
}
// open the server connection
// the imap_open function parameters will need to be changed for the particular server
// these are laid out to connect to a Dreamhost IMAP server
function connect() {
$this->conn = imap_open('{'.$this->server.'/notls}', $this->user, $this->pass);
}
// move the message to a new folder
function move($msg_index, $folder='INBOX.Processed') {
// move on server
imap_mail_move($this->conn, $msg_index, $folder);
imap_expunge($this->conn);
// re-read the inbox
$this->inbox();
}
// get a specific message (1 = first email, 2 = second email, etc.)
function get($msg_index=NULL) {
if (count($this->inbox) <= 0) {
return array();
}
elseif ( ! is_null($msg_index) && isset($this->inbox[$msg_index])) {
return $this->inbox[$msg_index];
}
return $this->inbox[0];
}
// read the inbox
function inbox() {
$this->msg_cnt = imap_num_msg($this->conn);
$in = array();
for($i = 1; $i <= $this->msg_cnt; $i++) {
$in[] = array(
'index' => $i,
'header' => imap_headerinfo($this->conn, $i),
'body' => imap_body($this->conn, $i),
'structure' => imap_fetchstructure($this->conn, $i)
);
}
$this->inbox = $in;
}
}
And the following usage code:
$email = new Email_reader();
$msg = $email->get(1);
echo "message body is [".$msg['body']."]<br />"; //prints body //good
echo "message index is [".$msg['index']."]<br />"; //prints "2" //good
echo "message subject is [".$msg['header']->Subject."]<br />"; //prints strangness
echo "message toaddress is [".$msg['header']->toaddress."]<br />"; //prints strangeness
the attempt to print subject line prints "=?utf-8?B?TWljcm9zb2Z0IE9mZmljZSBPdXRsb29rIFRlc3QgTWVzc2FnZQ==?="
and the toaddress also something similar.
I looked at some other examples online but i dont see anything different that they do than what im doing.
You have to decode the RFC 2047 encoding of the Unicode data. Check the imap_utf8 function.
I have written/copied a script that reads emails from an inbox and updates a ticket and then moves the email to a proccessed folder. This all works perfectly on new emails to the inbox but when someone replys to an email and it ends up in the inbox my scrtipt reads nothing on the email.
Is there something different to the way an email is structured when its a reply? I need a way of reading whats in the email so I can update a ticket with the contents. Knowing which email it is to update is all taken care of its just purely reading the content Im struggling with.
Here is the code
class Email
{
// imap server connection
public $conn;
// inbox storage and inbox message count
public $inbox;
private $msg_cnt;
// email login credentials
private $server = '????????????';
private $user = '????????';
private $pass = '?????????????';
private $port = ??;
// connect to the server and get the inbox emails
function __construct()
{
$this->connect();
$this->inbox();
}
function getdecodevalue($message,$coding)
{
switch($coding) {
case 0:
case 1:
$message = imap_8bit($message);
break;
case 2:
$message = imap_binary($message);
break;
case 3:
case 5:
$message=imap_base64($message);
break;
case 4:
$message = imap_qprint($message);
break;
}
return $message;
}
// close the server connection
function close()
{
$this->inbox = array();
$this->msg_cnt = 0;
imap_close($this->conn);
}
// open the server connection
// the imap_open function parameters will need to be changed for the particular server
// these are laid out to connect to a Dreamhost IMAP server
function connect()
{
$this->conn = imap_open("{".$this->server.":".$this->port."/imap/novalidate-cert}INBOX", $this->user, $this->pass);
}
// move the message to a new folder
function move($msg_index, $folder='Read')
{
// move on server
imap_mail_move($this->conn, $msg_index, $folder);
// re-read the inbox
//$this->inbox();
}
// get a specific message (1 = first email, 2 = second email, etc.)
function get($msg_index=NULL)
{
if(count($this->inbox) <= 0)
{
return array();
}
elseif( ! is_null($msg_index) && isset($this->inbox[$msg_index]))
{
return $this->inbox[$msg_index];
}
return $this->inbox[0];
}
// read the inbox
function inbox()
{
$this->msg_cnt = imap_num_msg($this->conn);
$in = array();
for($i = 1; $i <= $this->msg_cnt; $i++)
{
$in[] = array(
'index' => $i,
'header' => imap_headerinfo($this->conn, $i),
'body' => $this->cleanBody(imap_fetchbody($this->conn, $i,1)),
'structure' => imap_fetchstructure($this->conn, $i)
);
}
$this->inbox = $in;
}
function cleanBody($body)
{
$delimiter = '#';
$startTag = '----------START REPLY----------';
$endTag = '----------END REPLY----------';
$regex = $delimiter . preg_quote($startTag, $delimiter)
. '(.*?)'
. preg_quote($endTag, $delimiter)
. $delimiter
. 's';
preg_match($regex,$body,$matches);
$ret = trim($matches[1]);
return $ret;
}
}
$emails = new Email();
$email = array();
$emailCount = 1;
foreach($emails->inbox as $ems => $em)
{
$email[$emailCount]['subject'] = $sub = $em['header']->subject;
//echo $sub;
$subParts = explode('-',$sub);
$ticketUniqueCode = trim($subParts[1]);
$sql = "SELECT * FROM ticket_main WHERE uniquecode = '".mysql_escape_string($ticketUniqueCode)."' LIMIT 1";
$query = mysql_query($sql);
if(mysql_num_rows($query))
{
$res = mysql_fetch_object($query);
$ticketBody = $em['body'];
$customerID = $res->customerID;
$sql2 = "INSERT INTO ticket_updates SET ticketID = '".$res->ticketID."' , submitted = NOW() , submittedBy = '".$res->customerID."' , message = '".mysql_escape_string($ticketBody)."' , inhouse = 0";
$query = mysql_query($sql2);
// attachment section
$message_number = $em['index'];
$attachments = array();
if(isset($em['structure']->parts) && count($em['structure']->parts))
{
//echo 'hi';
for($i = 0; $i < count($em['structure']->parts); $i++)
{
$attachments[$i] = array(
'is_attachment' => false,
'filename' => '',
'name' => '',
'attachment' => ''
);
if($em['structure']->parts[$i]->ifdparameters) {
foreach($em['structure']->parts[$i]->dparameters as $object)
{
if(strtolower($object->attribute) == 'filename')
{
$attachments[$i]['is_attachment'] = true;
$attachments[$i]['filename'] = $object->value;
}
}
}
if($em['structure']->parts[$i]->ifparameters) {
foreach($em['structure']->parts[$i]->parameters as $object)
{
if(strtolower($object->attribute) == 'name')
{
$attachments[$i]['is_attachment'] = true;
$attachments[$i]['name'] = $object->value;
}
}
}
if($attachments[$i]['is_attachment'])
{
$attachments[$i]['attachment'] = imap_fetchbody($emails->conn, $message_number, $i+1);
if($em['structure']->parts[$i]->encoding == 3)
{ // 3 = BASE64
$attachments[$i]['attachment'] = base64_decode($attachments[$i]['attachment']);
}
elseif($em['structure']->parts[$i]->encoding == 4)
{ // 4 = QUOTED-PRINTABLE
$attachments[$i]['attachment'] = quoted_printable_decode($attachments[$i]['attachment']);
}
}
if(isset($em['structure']->parts[$i]->disposition) && $em['structure']->parts[$i]->disposition == "attachment")
{
$filename = $attachments[$i]['name'];
$mege="";
$data="";
$mege = imap_fetchbody($emails->conn, $message_number, $i+1);
$filename= $filename;
$fp=fopen('???????????????'.$filename,"w");
$data=$emails->getdecodevalue($mege,$em['structure']->parts[$i]->type);
fputs($fp,$data);
fclose($fp);
$email[$emailCount]['attachment'] = $attachments;
}
}
}
}
$emailCount++;
}
$emailNumbers = imap_search($emails->conn,'ALL');
if(!empty($emailNumbers))
{
foreach($emailNumbers as $a)
{
$emails->move($a);
}
imap_expunge($emails->conn);
}
$emails->close();
Hope that makes some sense and someone can actually help.
Many many thanks in advance
Jon
Well the most obvious thing is your code assumes a message is always in part 1, your line:
'body' => $this->cleanBody(imap_fetchbody($this->conn, $i,1)),
Means it's only looking at body 1. If it's a pure text email body 1 will be right, but if it's multipart/alternative (text & html) body 1 would be the base MIME message which tells you that there are sub bodies (body 1.1, 1.2) that actually contain the content.
Further if the reply includes embedded images, or includes the message it's replying to as an attachment then you could have even more bodies/locations.
So how do I find the body? (you ask)... well you can use imap_fetchstructure to learn about all the body parts, then search through it to find a piece with type=0 (text) and then download that body part. (The first found text should be the right one, but note there could be more than one text body type in an email).
I'm trying to fetch all profiles for some Google Analytics account in PHP. I'm using HTTP_Request2 class from PEAR (with cURL adapter, but I've also tried with Socket) and I keep getting "Target feed is read-only" error when I try to fetch data from https://www.google.com/analytics/feeds/accounts/default
I'm using ClientLogin auth method and as far as I can see correct Authorization header is sent with each API request (I've used observer class to test for headers which are being sent).
Here is the code I use (stripped-down, test version):
require 'HTTP/Request2.php';
class GA {
protected $email;
protected $passwd;
protected $auth_code;
public function __construct($email = '', $passwd = '') {
$this->email = $email;
$this->passwd = $passwd;
}
public function authorize($email = '', $password = '', $force = false) {
if (!$force and !empty($this->auth_code) and $email == $this->email and $password == $this->passwd) {
return true;
}
unset($this->auth_code);
!empty($email) or $email = $this->email;
!empty($password) or $password = $this->passwd;
if (empty($email) or empty($password)) {
return false;
}
try {
$response = $this->post(
'https://www.google.com/accounts/ClientLogin',
array(
'accountType' => 'GOOGLE',
'Email' => $this->email = $email,
'Passwd' => $this->passwd = $password,
'service' => 'analytics'
)
);
if ($response->getStatus() == 200 and preg_match('/(?:^|[\n\r])Auth=(.*?)(?:[\n\r]|$)/', $response->getBody(), $match)) {
$this->auth_code = $match[1];
echo $this->auth_code;
return true;
}
} catch (HTTP_Request2_Exception $e) {
return false;
}
}
public function call($url, array $params = array(), array $headers = array()) {
if (!$this->auth_code && !$this->authorize($this->email, $this->passwd, true)) {
return false;
}
$headers['Authorization'] = 'GoogleLogin auth=' . $this->auth_code;
return $this->post($url, $params, $headers);
}
protected function post($url, array $params = array(), array $headers = array()) {
$headers['GData-Version'] = '2';
$request = new HTTP_Request2($url);
$request->setAdapter('curl');
$request->setConfig('ssl_verify_peer', false);
$request->setHeader($headers);
$request->setMethod(HTTP_Request2::METHOD_POST);
$request->addPostParameter($params);
return $request->send();
}
}
$ga = new GA('*********#gmail.com', '*********');
var_dump($ga->call('https://www.google.com/analytics/feeds/accounts/default'));
Thanks in advance!
to answer my own question: https://www.google.com/analytics/feeds/accounts/default must be accessed trough GET method. My code was always using POST.
I'm having a problem with curl_multi_*, I want to create a class / function that receives, lets say 1000 URLs, and processes all those URLs 5 at a time, so when a URL finishes downloading it will allocate the now available slot to a new URL that hasn't been processed yet.
I've seen some implementations of curl_multi, but none of them allows me to do what I want, I believe the solution lies somewhere in the usage of curl_multi_select but the documentation isn't very clear and the user notes don't help much.
Can anyone please provide me with some examples how I can implement such a feature?
Here's one way to do it. This script will fetch any number of urls at a time, and add a new one as each is finished (so it's always fetching $maxConcurrent pages).
$sites = array('http://example.com', 'http://google.com', 'http://stackoverflow.com');
$concurrent = 2; // Any number.
$mc = new MultiCurl($sites, $concurrent);
$mc->process();
echo '</pre>';
class MultiCurl
{
private $allToDo;
private $multiHandle;
private $maxConcurrent = 2;
private $currentIndex = 0;
private $info = array();
private $options = array(CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 3,
CURLOPT_TIMEOUT => 3);
public function __construct($todo, $concurrent)
{
$this->allToDo = $todo;
$this->maxConcurrent = $concurrent;
$this->multiHandle = curl_multi_init();
}
public function process()
{
$running = 0;
do {
$this->_addHandles(min(array($this->maxConcurrent - $running, $this->_moreToDo())));
while ($exec = curl_multi_exec($this->multiHandle, $running) === -1) {
}
curl_multi_select($this->multiHandle);
while ($multiInfo = curl_multi_info_read($this->multiHandle, $msgs)) {
$this->_showData($multiInfo);
curl_multi_remove_handle($this->multiHandle, $multiInfo['handle']);
curl_close($multiInfo['handle']);
}
} while ($running || $this->_moreTodo());
return $this;
}
private function _addHandles($num)
{
while ($num-- > 0) {
$handle = curl_init($this->allToDo[$this->currentIndex]);
curl_setopt_array($handle, $this->options);
curl_multi_add_handle($this->multiHandle, $handle);
$this->info[$handle]['url'] = $this->allToDo[$this->currentIndex];
$this->currentIndex++;
}
}
private function _moreToDo()
{
return count($this->allToDo) - $this->currentIndex;
}
private function _showData($multiInfo)
{
$this->info[$multiInfo['handle']]['multi'] = $multiInfo;
$this->info[$multiInfo['handle']]['curl'] = curl_getinfo($multiInfo['handle']);
//print_r($this->info[$multiInfo['handle']]);
$content = curl_multi_getcontent($multiInfo['handle']);
echo $this->info[$multiInfo['handle']]['url'] . ' - ' . strlen($content) . ' bytes<br />';
//echo htmlspecialchars($content);
}
}