I'm trying to build a small webmail app. When I read all the emails in inbox I want to show for each mail if it has attachments. This works, but the problem is that it takes to long to do that, about 0.5 secs for 1Mb email attach. Multiply that with all emails in inbox that have big attach files :|
My question is: How to check if an email has attach withouth loading the whole email ? Is that possible ?
Bellow is the code I'm using now:
function existAttachment($part)
{
if (isset($part->parts))
{
foreach ($part->parts as $partOfPart)
{
$this->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;
}
}
}
return false;
}
function hasAttachments($msgno)
{
$struct = imap_fetchstructure($this->_connection,$msgno,FT_UID);
$existAttachments = $this->existAttachment($struct);
return $existAttachments;
}
To check whether the email has attachment, use $structure->parts[0]->parts.
$inbox = imap_open($mailserver,$username, $password, null, 1, ['DISABLE_AUTHENTICATOR' => 'PLAIN']) or die(var_dump(imap_errors()));
$unreadEmails = imap_search($inbox, 'UNSEEN');
$email_number = $unreadEmails[0];
$structure = imap_fetchstructure($inbox, $email_number);
if(isset($structure->parts[0]->parts))
{
// has attachment
}else{
// no attachment
}
imap_fetchstructure does fetch the whole email content in order to analyze it. Sadly there is no other way to check for attachment.
Maybe you can use the message size info from imap_headerinfo to get a prediction if the message will have attachments.
Another way is to fetch the emails in an regular interval in the background and store them with their content and UID for later lookup in a database. You need to do that later anyway when you want so search for specific messages. (You do not want to scan the while imap account when searching for "dinner")
Related
I am trying to mask emails. Basically give an email to a client like "RandomName#MyDomain.com" and have it forward "MyRealEmail#MyDomain.com".
I am pipe forwarding the emails to a php script on my server, where I want to use the "To" and "From" to find the real recipient of the message and forward the message to them removing any identifiable information from the Sender (From) section.
I can parse almost all the data right now from the header, but my problem is with the body. The html body portion can vary so much from different origins. Outlook have a <html> and <body> section, while Gmail just has <div>s. Regardless, I get these strange "=" signs in my raw email too, in both text and html sections, like <=div>!
I just want to change the "From" and "To" and keep the rest of the email pretty much exactly as it is so it doesn't have anomalies in its text or html section.
How can I do this? Should I just parse the raw email and change the occurrences of the emails? how can I send it then? or should I remake the email using phpmailer or some other class? how can i get the body correct then?
My hosting provider doesn't have MailParse extension installed, since I have seen some solutions on the site using that extension, so I am having to do this using available extensions in PHP 5.5
UPDATE
I managed to figure out the = issue, it was quoted-printable, so now I am calling quoted_printable_decode() to resolve that issue. Still trying to figure the best way to forward the email after altering the header though.
After a lot of failed attempts, finally have a solution I can live with. The host server didn't want to allow MailParse because it was an issue on their shared hosting environment, so I went with Mail_mimeDecode and Mail_MIME PEAR extensions.
// Read the message from STDIN
$fd = fopen("php://stdin", "r");
$input = "";
while (!feof($fd)) {
$input .= fread($fd, 1024);
}
fclose($fd);
$params['include_bodies'] = true;
$params['decode_bodies'] = true;
$params['decode_headers'] = true;
$decoder = new Mail_mimeDecode($input);
$structure = $decoder->decode($params);
// get the header From and To email
$From = ExtractEmailAddress($structure->headers['from'])[0];
$To = ExtractEmailAddress($structure->headers['to'])[0];
$Subject = $structure->headers['subject'];
ExtractEmailAddress uses a solution from "In PHP, how do I extract multiple e-mail addresses from a block of text and put them into an array?"
For the Body I used the following to find the text and html portions:
$HTML = "";
$TEXT = "";
// extract email body details
foreach($structure as $K => $V){
if(is_array($V)){
foreach($V as $KK => $VV){
if(is_object($VV)){
$bodyHTML = false;
$bodyPLAIN = false;
foreach($VV as $KKK => $VVV){
if(!is_array($VVV)){
if($KKK === 'ctype_secondary'){
if($VVV === 'html') { $bodyHTML = true; }
if($VVV === 'plain') { $bodyPLAIN = true; }
}
if($KKK === 'body'){
if($bodyHTML){
$bodyHTML = false;
$HTML .= quoted_printable_decode($VVV);
}
if($bodyPLAIN){
$bodyPLAIN = false;
$TEXT .= quoted_printable_decode($VVV);
}
}
}
}
}
}
}
}
Finally, I had the parts I needed so I used Mail_MIME to get the message out. I do my database lookup logic here and find the real destination and masked From email address using the From and To I extracted from the header.
$mime = new Mail_mime(array('eol' => "\r\n"));
$mime->setTXTBody($TEXT);
$mime->setHTMLBody($HTML);
$mail = &Mail::factory('mail');
$hdrs = array(
'From' => $From,
'Subject' => $Subject
);
$mail->send($To, $mime->headers($hdrs), $mime->get());
I don't know if this will cover all cases of email bodies, but since my system is not using attachments I am ok for now.
Take not of quoted_printable_decode(), that how I fixed the issue with the = in the body.
The only issue is the delay in mail I am having now, but I'll deal with that
I'm currently building a php scripts which fetches the email from a server using imap functions and stores the details in the database.
My problem is I dont know how to identify new mails from old mails that already exists.
and how to get reply mails sent to the mail
use the UID message to determine the last message, you have to store the last UID in the table
$uidsArray = imap_sort($imapConnection, SORTARRIVAL, 1, SE_UID);
if ($uidsArray) {
// read UID last message, XEmailUID - table(mailbox, lastuid mailbox)
$lastUIDObject = new XEmailUID();
$lastUIDObject->setImap($mailbox->getId().'/'.$mailboxRef);
if (!$lastUIDObject->select()) {
$lastUIDObject->insert();
}
$uidMax = 0;
foreach ($uidsArray as $uid) {
if ($uid < $lastUIDObject->getUid()) {
continue;
}
if ($uid >= $uidMax) {
$uidMax = $uid;
}
// your function
$this->_readIMAPMessage(
$imapConnection,
$uid,
$mailboxRef
);
}
if ($uidMax > 0) {
$lastUIDObject->setUid($uidMax);
$lastUIDObject->update();
}
}
Im using the below php code to send an email to one address and bcc 2 other addresses. It sends to the recipient fine but I can only get it to send to one of the 2 bcc addresses. (see comments in code for what ive tried)
Oddly enough though, $result comes back as 3 so it seems that its trying to send the second bcc email but it never comes through.
<?php
$tracker='tracking#pnrbuilder.com';
$subject = $_POST['subject'];
$sender = $_POST['sender'];
$toEmail=$_POST['toEmail'];
$passedInEmail=stripslashes($_POST['message']);
$passedInEmail=preg_replace('/ /',' ',$passedInEmail);
require_once('swiftLib/simple_html_dom.php');
require_once('swiftLib/swift_required.php');
$transport = Swift_MailTransport::newInstance();
$mailer = Swift_Mailer::newInstance($transport);
// Create the message
$message = Swift_Message::newInstance();
//turn the meesage into an object using simple_html_dom
//so we can iterate through and embed each image
$content = str_get_html($passedInEmail);
// Retrieve all img src tags and replace them with embedded images
foreach($content->find('img') as $e)
{
if($e->src != "")
{
$value = $e->src;
$newValue = $message->embed(Swift_Image::fromPath($value));
$e->src = $newValue;
}
}
$message->setSubject($subject);
$message->setFrom($sender);
$message->setTo($toEmail);
//this is my problem
$message->setBcc(array('tracking#pnrbuilder.com',$sender));
//as it is above only "sender" gets the email
//if I change it like this:
//$message->setBcc($tracker,$sender);
//only "tracker" gets the email
//same if I change it like this:
//$message->setBcc($sender);
//$message->addBcc($tracker);
$message->setReplyTo(array('flights#pnrbuilder.com'));
$message->setBody($content,'text/html');
$result = $mailer->send($message);
if ($result=3) {
echo 'Email Sent!';
}
else {
echo 'Error!';
}
?>
What is the proper way to do this?
You can find the swiftmailer tutorial here
example:
$message->setBcc(array(array('some#address.tld' => 'The Name'),array('another#address.tld' => 'Another Name')));
Try setting the names for the email addresses and see if it makes any difference.
This ended up being an issue on the server side, I contacted my hosting provider (GoDaddy) who were able to make some changes on their end fixing the problem. Thank you to all those who tried to help!
I'm trying to move messages away from Inbox into Processed label with this code:
$inbox = imap_open($host,$user,$pass) or die('Error: ' . imap_last_error());
if( $emails = imap_search($inbox,'ALL') )
{
foreach($emails as $email_number) {
imap_mail_move($inbox, $email_number, 'Processed') or die('Error');
}
}
imap_expunge($inbox);
imap_close($inbox);
Unfortunately, while the messages get the Processed label, they're still left in Inbox too.
How would I make them go away from Inbox?
Actually... The reason why the emails were left in the inbox was that when imap_mail_move did it's thing, the IDs of all the leftover messages got decremented by one, so when the foreach loop moved to the next message, one message was left behind. This skipping a message repeated for every iteration. That's why it seemed that imap_mail_move was not working.
The solution is to use unique message UIDs instead of potentially repeating IDs:
$inbox = imap_open( $host, $user, $pass );
$emails = imap_search( $inbox, 'ALL', SE_UID );
if( $emails ) {
foreach( $emails as $email_uid ) {
imap_mail_move($inbox, $email_uid, 'processed', CP_UID);
}
}
You have to move the message to the "[Gmail]/All Mail" folder, after you "move it" to a tag folder which is not really a folder as Gmail see's it, just letting Gmail know to add that tag.
So through IMAP:
1) When a message is moved to "[Gmail]/TAG" folder it tells Gmail to add the "TAG" to the message, but does not do any sort of moving of the message.
2) When a message is moved to "[Gmail]/All Mail" folder it tells Gmail to remove it from the Inbox.
#Henno, your diagnosis is correct but you could have simply sorted the emails in descending order.
$inbox = imap_open($host,$user,$pass) or die('Error: ' . imap_last_error());
if( $emails = imap_search($inbox,'ALL') )
{
arsort($emails); //JUST DO ARSORT
foreach($emails as $email_number) {
imap_mail_move($inbox, $email_number, 'Processed') or die('Error');
}
}
imap_expunge($inbox);
imap_close($inbox);
Place this at the end of your file, after you have processed any emails, this will move all found in the inbox, and move them to a folder called 'done'.
$mbox = imap_open('{imap.gmail.com:993/imap/ssl}INBOX', 'emailaddress#gmail.com', 'password');
$countnum = imap_num_msg($mbox);
if($countnum > 0) {
//move the email to our saved folder
$imapresult=imap_mail_move($mbox,'1:'.$countnum,'done');
if($imapresult==false){die(imap_last_error());}
imap_close($mbox,CL_EXPUNGE);
}
use imap_expunge() or imap_close (..., CL_EXPUNGE); but check the return value if true or false if using imap_close (..., CL_EXPUNGE);
I'm trying to figure out how to get the latest 3 emails (SEEN and UNSEEN) using imap and php. It need to be ressource-efficient since the mailbox as 1 000 emails inside. Getting all header may need too much ressources I think.
I just need the sender, the subject and the date...
Any idea? Thanks for any syggestion/help/explaination/hint...
I did it like that:
$mbox = imap_open("{imap.myconnection.com:993/imap/ssl}INBOX", "username", "password");
// get information about the current mailbox (INBOX in this case)
$mboxCheck = imap_check($mbox);
// get the total amount of messages
$totalMessages = $mboxCheck->Nmsgs;
// select how many messages you want to see
$showMessages = 5;
// get those messages
$result = array_reverse(imap_fetch_overview($mbox,($totalMessages-$showMessages+1).":".$totalMessages));
// iterate trough those messages
foreach ($result as $mail) {
print_r($mail);
// if you want the mail body as well, do it like that. Note: the '1.1' is the section, if a email is a multi-part message in MIME format, you'll get plain text with 1.1
$mailBody = imap_fetchbody($mbox, $mail->msgno, '1.1');
// but if the email is not a multi-part message, you get the plain text in '1'
if(trim($mailBody)=="") {
$mailBody = imap_fetchbody($mbox, $mail->msgno, '1');
}
// just an example output to view it - this fit for me very nice
echo nl2br(htmlentities(quoted_printable_decode($mailBody)));
}
imap_close($mbox);
PHP-Ref IMAP: http://php.net/manual/en/ref.imap.php
Regards
Dominic
What about
imap_search($res, 'RECENT');
?
http://php.net/manual/en/function.imap-search.php
$msgnos = imap_search($mbox, "UNSEEN", SE_UID);
$i=0;
foreach($msgnos as $msgUID) {
$msgNo = imap_msgno($mbox, $msgUID);
$head = imap_headerinfo($mbox, $msgNo);
$mail[$i][] = $msgUID;
$mail[$i][] = $head->Recent;
$mail[$i][] = $head->Unseen;
$mail[$i][] = $head->from[0]->mailbox."#".$head->from[0]->host;
$mail[$i][] = utf8_decode(imap_utf8($head->subject));
$mail[$i][] = $head->udate;
}
return $mail;
imap_close($mbox);
Will do the job.