From 223c36251ab1c48888d22aa6bdf2f7d73dbd0bf4 Mon Sep 17 00:00:00 2001 From: Dominique Fournier Date: Wed, 21 Oct 2015 12:49:10 +0000 Subject: [PATCH] Add imap and authimap support git-svn-id: https://svn.fournier38.fr/svn/ProgSVN/trunk@2370 bf3deb0d-5f1a-0410-827f-c0cc1f45334c --- authimap.php | 83 +++++++ imap.php | 677 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 760 insertions(+) create mode 100644 authimap.php create mode 100644 imap.php diff --git a/authimap.php b/authimap.php new file mode 100644 index 0000000..ea111f3 --- /dev/null +++ b/authimap.php @@ -0,0 +1,83 @@ + */ + +require_once ("domframework/auth.php"); +require_once ("domframework/imap.php"); + +/** User authentication against IMAP server */ +class authimap extends auth +{ + /** IMAP server */ + public $imapServer = "localhost"; + /** IMAP TCP port (143 by default) */ + public $imapPort = 143; + /** IMAP SSL connection */ + public $imapSSL = false; + /** IMAP SSL CheckCertificate */ + public $imapSSLCheckCertificates = true; + + /** Check the availablity of the IMAP support in PHP */ + function __construct () + { + if (!function_exists ("imap_open")) + throw new Exception ("IMAP support unavailable in PHP", 500); + } + + /** Establish the connection to IMAP server. Don't do anything as the + needed parameters are username and password */ + public function connect () + { + // Nothing to do + } + + /** Try to authenticate the email/password of the user + @param string $email Email to authenticate + @param string $password Password to authenticate */ + public function authentication ($email, $password) + { + $imap = new imap ($this->imapServer, $this->imapPort, + $email, $password, + $this->imapSSL, $this->imapSSLCheckCertificates); + // Let the throw Exception be catched by the parent + return true; + } + + /** Return all the parameters recorded for the authenticate user */ + public function getdetails () + { + throw new Exception (dgettext("domframework", + "Can't get details for IMAP users"), 405); + } + + /** Method to change the password + @param string $oldpassword The old password (to check if the user have the + rights to change the password) + @param string $newpassword The new password to be recorded */ + public function changepassword ($oldpassword, $newpassword) + { + throw new Exception (dgettext("domframework", + "The password can't be change for IMAP users"), 405); + } + + /** Method to overwrite the password (without oldpassword check) + Must be reserved to the administrators. For the users, use changepassword + method + @param string $email the user identifier to select + @param string $newpassword The new password to be recorded */ + public function overwritepassword ($email, $newpassword) + { + throw new exception (dgettext("domframework", + "The password can't be overwrite for IMAP users"), + 405); + } + + /** List all the users available in the database + Return firstname, lastname, mail, with mail is an array */ + public function listusers () + { + throw new Exception (dgettext("domframework", + "Can't get list of users for IMAP server"), 405); + } +} diff --git a/imap.php b/imap.php new file mode 100644 index 0000000..de3b14e --- /dev/null +++ b/imap.php @@ -0,0 +1,677 @@ + */ + +/** IMAP connection abstraction + In the IMAP terminology, "mailbox" is a folder in the mailbox of the user */ +class imap +{ + /** The mailbox string */ + private $mailbox; + /** The current folder in UTF-8 */ + private $curDir = "INBOX"; + /** The auto expunge feature, after deleting/moving an email */ + public $autoexpunge = true; + + /** Limit to one instance of the connection to the same database */ + // Based on an idea of http://tonylandis.com/php/php5-pdo-singleton-class/ + private static $instance = array (); + + /** The constructor + The IMAP standard port is 143, but tunnelled is 993 */ + public function __construct ($imapserver = "localhost", $imapport = 143, + $username = null, $password = null, + $imapssl = false, $imapcertvalidate = true) + { + if (! function_exists ("imap_open")) + throw new Exception ("PHP don't support IMAP. Please add it !", 500); + if (! function_exists ("mb_convert_encoding")) + throw new Exception ("PHP don't support MBString. Please add it !", 500); + if ($username === null) + throw new Exception ("No username provided for IMAP server", 500); + if ($password === null) + throw new Exception ("No password provided for IMAP server", 500); + $imapssl = ($imapssl !== false) ? "/ssl" : ""; + $imapcertvalidate = ($imapcertvalidate !== false) ? "/novalidate-cert" : ""; + $this->mailbox = "{"."$imapserver:$imapport/imap$imapssl$imapcertvalidate". + "/user=$username}"; + if (! array_key_exists ($this->mailbox, self::$instance)) + { + try + { + // Timeout authentication error to 1s (can't be less). By default, IMAP + // wait 10s before returning an auth error + imap_timeout (IMAP_READTIMEOUT, 1); + self::$instance[$this->mailbox] = @imap_open ($this->mailbox, $username, + $password); + if (self::$instance[$this->mailbox] === FALSE) + throw new Exception (imap_last_error()); + } + catch (Exception $e) + { + // imap_errors() takes the errors and clear the error stack + $errors = imap_errors(); + throw new Exception ("IMAP error : ".$e->getMessage(), 500); + } + } + } + + /////////////////// + /// FOLDERS /// + /////////////////// + /** Return an array of the existing folders. The sub-folders are with slash + separator + The names of folders are converted in UTF-8 */ + public function foldersList () + { + $list = array_keys ($this->foldersListWithAttr ()); + natsort ($list); + return $list; + } + + /** Return an array with folder name in key and attributes in value. The + attributes allow to see if there is new mails in folders */ + public function foldersListWithAttr () + { + $list = imap_getmailboxes (self::$instance[$this->mailbox], $this->mailbox, + "*"); + $res = array (); + foreach ($list as $val) + { + $dir = substr ($val->name, strlen ($this->mailbox)); + $dir = mb_convert_encoding ($dir, "UTF8", "UTF7-IMAP"); + if (isset ($val->delimiter)) + $dir = str_replace ($val->delimiter, "/", $dir); + $res[$dir] = $val->attributes; + } + return ($res); + } + + /** Change to provided folder + The folder name must be in UTF-8. The folder must be absolute */ + public function changeFolder ($folder) + { + if (! in_array ($folder, $this->foldersList ())) + throw new Exception ("Folder not found", 404); + $folderUTF7 = mb_convert_encoding ($folder, "UTF7-IMAP","UTF-8"); + $rc = @imap_reopen (self::$instance[$this->mailbox], + $this->mailbox.$folderUTF7); + if ($rc === true) + $this->curDir = $folder; + else + throw new Exception ("Can't go in provided folder", 500); + return $rc; + } + + /** Return the current folder in UTF-8 */ + public function getFolder () + { + return $this->curDir; + } + + /** Create a new folder, provided in UTF-8. The folder must be absolute */ + public function createFolder ($folder) + { + if ( in_array ($folder, $this->foldersList ())) + throw new Exception ("Folder already exists", 406); + $folderUTF7 = mb_convert_encoding ($folder, "UTF7-IMAP","UTF-8"); + return imap_createmailbox (self::$instance[$this->mailbox], + $this->mailbox.$folderUTF7); + } + + /** Delete an existing folder provided in UTF-8. The folder must be absolute + */ + public function deleteFolder ($folder) + { + if (! in_array ($folder, $this->foldersList ())) + throw new Exception ("Folder not found", 404); + $folderUTF7 = mb_convert_encoding ($folder, "UTF7-IMAP","UTF-8"); + return imap_deletemailbox (self::$instance[$this->mailbox], + $this->mailbox.$folderUTF7); + } + + /** Return the list of the folders substcribed by the user. The folders are + in UTF-8 */ + public function getSubscribe () + { + $subs = imap_getsubscribed (self::$instance[$this->mailbox], $this->mailbox, + "*"); + $res = array (); + foreach ($subs as $sub) + { + $res [] = str_replace ($sub->delimiter, "/", + substr ($sub->name, strlen ($this->mailbox))); + } + $res = array_map (function ($folder) { + return mb_convert_encoding ($folder, "UTF-8", "UTF7-IMAP"); + }, $res); + return $res; + } + + /** Add a subscription for a folder. The folder must be in UTF-8 */ + public function addSubscribe ($folder) + { + $folderUTF7 = mb_convert_encoding ($folder, "UTF7-IMAP","UTF-8"); + return imap_subscribe (self::$instance[$this->mailbox], + $this->mailbox.$folder); + } + + /** Remove a subscription for a folder. The folder must be in UTF-8 */ + public function delSubscribe ($folder) + { + $folderUTF7 = mb_convert_encoding ($folder, "UTF7-IMAP","UTF-8"); + return imap_unsubscribe (self::$instance[$this->mailbox], + $this->mailbox.$folder); + } + + /** Return the informations concerning a folder. It return an object with the + following properties : + Date date of last change (current datetime) + Driver driver + Mailbox name of the mailbox + Nmsgs number of messages + Recent number of recent messages + Unread number of unread messages + Deleted number of deleted messages + Size mailbox size */ + public function getFolderInfo ($folder) + { + $oldFolder = $this->curDir; + $this->changeFolder ($folder); + $rc = imap_mailboxmsginfo (self::$instance[$this->mailbox]); + $this->changeFolder ($oldFolder); + if ($rc === false) + throw new Exception ("Can't read information for folder $folder", 500); + return $rc; + } + + ////////////////////// + /// LIST MAILS /// + ////////////////////// + /** Return an array of mailHeaders order by $field and by order ASC or DESC */ + public function imapSortMail ($mailHeaders, $field, $orderAsc = TRUE) + { + $this->changeFolder ($this->curDir); + $sortList = array (); + $sortInc = array (); + foreach ($mailHeaders as $mail) + { + // Permit to have to mails with the same comparator field. Add an + // increment at the end of field + if (!isset ($sortInc[$mail->$field])) + $inc = 1; + else + $inc = $sortInc[$mail->$field] + 1; + $sortInc[$mail->$field] = $inc; + $sortList[$mail->$field.$inc] = $mail; + } + ksort ($sortList, SORT_NATURAL); + return array_values ($sortList); + } + + /** Fetch the headers for all messages in the current folder sorted by date + Return an array of mail object containing information like the subject, + the date, if the message is already read (recent), answered... + (see http://www.php.net/manual/en/function.imap-fetch-overview.php) + If the $from is negative, take the LAST $from mails + If from is zero, it's value is override to 1 + For information, takes 0.4s to select 30 mails on 1552 **/ + public function mailsDate ($from = 1, $nbmails = 30) + { + $this->changeFolder ($this->curDir); + if ($from === null) + $from = 1; + $MC = imap_check (self::$instance[$this->mailbox]); + if ($MC->Nmsgs === 0) + return array (); + if ($nbmails > $MC->Nmsgs) + $nbmails = $MC->Nmsgs; + if ($from < 0) + { + $from = abs ($from); + if ($from < 1) + $from = 1; + if ($from > $MC->Nmsgs) + throw new Exception ("Mail start is higher than the number of mails", + 500); + $from = $MC->Nmsgs - $from + 1; + $to = $from + $nbmails - 1; + if ($to > $MC->Nmsgs) + $to = $MC->Nmsgs; + } + else + { + if ($from > $MC->Nmsgs) + throw new Exception ("Mail start is higher than the number of mails", + 500); + if ($from < 1) + $from = 1; + $to = $from + $nbmails - 1; + if ($to > $MC->Nmsgs) + $to = $MC->Nmsgs; + } + $headers = array (); + // Adding the FT_UID options cost 1.1s + $result = imap_fetch_overview (self::$instance[$this->mailbox], + "$from:$to", 0); + // imap_errors() takes the errors and clear the error stack + $errors = imap_errors(); + return $result; + } + + /** Return all the mails numbers order by thread in an array. + [] => array ("msgno"=>msgno, "depth"=>depth) */ + public function mailsThread () + { + $this->changeFolder ($this->curDir); + $threads = @imap_thread (self::$instance[$this->mailbox]); + // imap_errors() takes the errors and clear the error stack + $errors = imap_errors(); + $thread = array (); + $depth = 0; + foreach ($threads as $key => $val) + { + $tree = explode('.', $key); + if ($tree[1] == 'num') + { + // If the mail unknown (the mails starts at 1), skip the thread record + if ($val === 0) + continue; + $thread[] = array ("msgno"=>$val, "depth"=>$depth); + $depth++; + } + elseif ($tree[1] == 'branch' && $depth > 0) + { + $depth--; + } + } + return $thread; + } + + /** Send back the number of mails in the mailbox */ + public function mailsNumber () + { + $this->changeFolder ($this->curDir); + $MC = imap_check (self::$instance[$this->mailbox]); + return $MC->Nmsgs; + } + + /** Return an array containing the msgno corresponding to the criteria */ + public function mailsSearch ($criteria) + { + $this->changeFolder ($this->curDir); + return imap_search (self::$instance[$this->mailbox], $criteria); + } + + /** Move the mail provided in the $folder in UTF-8. + If $msgno is an array, all the mails with the contain msgno are deleted + Expunge automatically the current folder to remove the old emails */ + public function mailMove ($msgno, $folder) + { + $this->changeFolder ($this->curDir); + if (is_array ($msgno)) + $msgno = implode (",", $msgno); + $folderUTF7 = mb_convert_encoding ($folder, "UTF7-IMAP","UTF-8"); + $rc = imap_mail_move (self::$instance[$this->mailbox], $msgno, $folderUTF7); + if ($rc !== TRUE) + { + return FALSE; + } + if ($this->autoexpunge) + return imap_expunge (self::$instance[$this->mailbox]); + return true; + } + + /** Copy the mail provided in the $folder in UTF-8. + If $msgno is an array, all the mails with the contain msgno are copied */ + public function mailCopy ($msgno, $folder) + { + $this->changeFolder ($this->curDir); + if (is_array ($msgno)) + $msgno = implode (",", $msgno); + $folderUTF7 = mb_convert_encoding ($folder, "UTF7-IMAP","UTF-8"); + $rc = imap_mail_copy (self::$instance[$this->mailbox], $msgno, $folderUTF7); + if ($rc !== TRUE) + { + return FALSE; + } + return true; + } + + /** Expunge the mailbox. If the autoexpunge is activated, it is normally not + needed */ + public function expunge () + { + $this->changeFolder ($this->curDir); + return imap_expunge (self::$instance[$this->mailbox]); + } + + ///////////////////////////// + /// GET/SET/DEL EMAIL /// + ///////////////////////////// + /** Get an existing email in the current folder in raw format */ + public function getEmailRaw ($msgno) + { + $this->changeFolder ($this->curDir); + // Clear the errors + imap_errors(); + $content = @imap_fetchheader (self::$instance[$this->mailbox], $msgno)."\n". + @imap_body (self::$instance[$this->mailbox], $msgno); + $errors = imap_errors (); + if ($errors !== false) + throw new Exception ("Mail not found", 404); + return $content; + } + + /** Get the headers of the email (in raw format) */ + public function getEmailHeadersRaw ($msgno) + { + $this->changeFolder ($this->curDir); + // Clear the errors + imap_errors(); + $content = @imap_fetchheader (self::$instance[$this->mailbox], $msgno); + $errors = imap_errors (); + if ($errors !== false) + throw new Exception ("Mail not found", 404); + return $content; + } + + /** Get all the body (and attached files) of an email in raw format */ + public function getEmailBodyRaw ($msgno) + { + $this->changeFolder ($this->curDir); + // Clear the errors + imap_errors(); + $content = @imap_body (self::$instance[$this->mailbox], $msgno); + $errors = imap_errors (); + if ($errors !== false) + throw new Exception ("Mail not found", 404); + return $content; + } + + /** Return email structure of the body */ + public function getStructure ($msgno) + { + $this->changeFolder ($this->curDir); + // Clear the errors + imap_errors(); + $structure = @imap_fetchstructure (self::$instance[$this->mailbox], $msgno); + $errors = imap_errors (); + if ($errors !== false) + throw new Exception ("Mail not found", 404); + return $structure; + } + + /** Return the structure of the mail body with the associated content */ + public function getStructureWithContent ($msgno) + { + $this->changeFolder ($this->curDir); + // Clear the errors + imap_errors(); + $structure = @imap_fetchstructure (self::$instance[$this->mailbox], $msgno); + $errors = imap_errors (); + if ($errors !== false) + throw new Exception ("Mail not found", 404); + if (! isset ($structure->parts)) + { + // In case of PLAIN text, there is no parts + $content = imap_fetchbody (self::$instance[$this->mailbox], $msgno, 1); + if ($structure->encoding === 4) + $content = quoted_printable_decode ($content); + elseif ($structure->encoding === 3) + $content = base64_decode ($content); + foreach ($structure->parameters as $param) + { + if ($param->attribute === "charset") + $content = iconv ($param->value, "utf-8", $content); + } + $structure->content = $content; + return $structure; + } + foreach ($structure->parts as $part1=>$struct1) + { + if (isset ($struct1->parts)) + { + foreach ($struct1->parts as $part2=>$struct2) + { + $content = imap_fetchbody (self::$instance[$this->mailbox], $msgno, + ($part1+1).".".($part2+1)); + if ($struct2->encoding === 4) + $content = quoted_printable_decode ($content); + elseif ($struct2->encoding === 3) + $content = base64_decode ($content); + foreach ($struct2->parameters as $param) + { + if ($param->attribute === "charset") + $content = iconv ($param->value, "utf-8", $content); + } + $structure->parts[$part1]->parts[$part2]->content = $content; + // Add the MIME type + if ($struct2->type === 0) + $structure->parts[$part1]->parts[$part2]->mimetype = "text/". + strtolower ($struct2->subtype); + elseif ($struct2->type === 1) + $structure->parts[$part1]->parts[$part2]->mimetype = "multipart/". + strtolower ($struct2->subtype); + elseif ($struct2->type === 2) + $structure->parts[$part1]->parts[$part2]->mimetype = "message/". + strtolower ($struct2->subtype); + elseif ($struct2->type === 3) + $structure->parts[$part1]->parts[$part2]->mimetype = "application/". + strtolower ($struct2->subtype); + elseif ($struct2->type === 4) + $structure->parts[$part1]->parts[$part2]->mimetype = "audio/". + strtolower ($struct2->subtype); + elseif ($struct2->type === 5) + $structure->parts[$part1]->parts[$part2]->mimetype = "image/". + strtolower ($struct2->subtype); + elseif ($struct2->type === 6) + $structure->parts[$part1]->parts[$part2]->mimetype = "video/". + strtolower ($struct2->subtype); + elseif ($struct2->type === 7) + $structure->parts[$part1]->parts[$part2]->mimetype = "other/". + strtolower ($struct2->subtype); + else + throw new Exception (sprintf ( + _("Unknown type in imap_fetchstructure : %s"), + $struct2->type), 500); + } + } + else + { + $content = imap_fetchbody (self::$instance[$this->mailbox], $msgno, + $part1+1); + if ($struct1->encoding === 4) + $content = quoted_printable_decode ($content); + elseif ($struct1->encoding === 3) + $content = base64_decode ($content); + foreach ($struct1->parameters as $param) + { + if ($param->attribute === "charset") + $content = iconv ($param->value, "utf-8", $content); + } + $structure->parts[$part1]->content = $content; + // Add the MIME type + if ($struct1->type === 0) + $structure->parts[$part1]->mimetype = "text/". + strtolower ($struct1->subtype); + elseif ($struct1->type === 1) + $structure->parts[$part1]->mimetype = "multipart/". + strtolower ($struct1->subtype); + elseif ($struct1->type === 2) + $structure->parts[$part1]->mimetype = "message/". + strtolower ($struct1->subtype); + elseif ($struct1->type === 3) + $structure->parts[$part1]->mimetype = "application/". + strtolower ($struct1->subtype); + elseif ($struct1->type === 4) + $structure->parts[$part1]->mimetype = "audio/". + strtolower ($struct1->subtype); + elseif ($struct1->type === 5) + $structure->parts[$part1]->mimetype = "image/". + strtolower ($struct1->subtype); + elseif ($struct1->type === 6) + $structure->parts[$part1]->mimetype = "video/". + strtolower ($struct1->subtype); + elseif ($struct1->type === 7) + $structure->parts[$part1]->mimetype = "other/". + strtolower ($struct1->subtype); + else + throw new Exception (sprintf ( + _("Unknown type in imap_fetchstructure : %s"), + $struct1->type), 500); + } + } + return $structure; + } + + /** Return the content of a part of the mail body defined in the structure in + an object, with the associated mimetype, the parameters like the charset + if they are defined, the number of lines associated to this part in the + mail and some other info */ + public function getStructureContent ($msgno, $part) + { + $structure = $this->getStructureWithContent ($msgno); + if (isset ($structure->parts[$part])) + return $structure->parts[$part]; + throw new Exception ("Part not found in the mail", 404); + } + + /** Return the part identifiers of the structure of the mail body. To be used + in getStructureContent */ + public function getStructureParts ($msgno) + { + $structure = $this->getStructure ($msgno); + if (! isset ($structure->parts)) + return array (); + return array_keys ($structure->parts); + } + + /** Delete all the mailIDs (msgno) provided in an array or a single mail if + $msgno is not an array + DO NOT MOVE THE MAIL IN TRASH, DESTROY THE MAIL REALLY + Expunge the mails at the end of the operation */ + public function mailsDel ($msgno) + { + $this->changeFolder ($this->curDir); + if (is_array ($msgno)) + $msgno = implode (",", $msgno); + $rc = @imap_delete (self::$instance[$this->mailbox], $msgno); + imap_errors(); + if ($rc === FALSE) + throw new Exception ("No mailID provided can be found : ABORT"); + if ($this->autoexpunge) + return imap_expunge(self::$instance[$this->mailbox]); + return $rc; + } + + /** Add a new mail in the current folder. The content must be a string + containing all the mail (header and body). If the content is invalid, the + directory listing can provide erroneous data */ + public function mailAdd ($content) + { + $folderUTF7 = mb_convert_encoding ($this->curDir, "UTF7-IMAP","UTF-8"); + $rc = imap_append (self::$instance[$this->mailbox], + $this->mailbox.$folderUTF7, + $content); + $errors = imap_errors(); + if ($rc === FALSE) + throw new Exception ("Error when saving the mail in folder : ". + implode (" ", $errors), 500); + return true; + } + + ///////////////// + /// QUOTA /// + ///////////////// + /** Return the quota used by the user in Mo */ + public function getQuota () + { + $quota = @imap_get_quotaroot (self::$instance[$this->mailbox], "INBOX"); + imap_errors(); + if (! isset ($quota["STORAGE"])) + return array (); + + return array_map (function ($n) {return intval ($n/1000);}, + $quota["STORAGE"]); + } + + ///////////////// + /// FLAGS /// + ///////////////// + /** Set the flags of the msgno. If msgno is an array, the flags will be write + on the list of mails. The others flags of the email are not modified. + The flags must be an array containing : + \Seen Message has been read + \Answered Message has been answered + \Flagged Message is "flagged" for urgent/special attention + \Deleted Message is "deleted" for removal by later EXPUNGE + \Draft Message has not completed composition (marked as a draft). */ + public function setFlag ($msgno, $flags) + { + $this->changeFolder ($this->curDir); + if (is_array ($msgno)) + $msgno = implode (",", $msgno); + $rc = @imap_setflag_full (self::$instance[$this->mailbox], $msgno, + implode (" ", $flags)); + imap_errors(); + if ($rc === FALSE) + throw new Exception ("Can't define the flags", 500); + return true; + } + + /** Unset the flags of the msgno. If msgno is an array, the flags will be + write on the list of mails. The others flags of the email are not + modified. + The flags must be an array containing : + \Seen Message has been read + \Answered Message has been answered + \Flagged Message is "flagged" for urgent/special attention + \Deleted Message is "deleted" for removal by later EXPUNGE + \Draft Message has not completed composition (marked as a draft). */ + public function unsetFlag ($msgno, $flags) + { + $this->changeFolder ($this->curDir); + if (is_array ($msgno)) + $msgno = implode (",", $msgno); + $rc = @imap_clearflag_full (self::$instance[$this->mailbox], $msgno, + implode (" ", $flags)); + imap_errors(); + if ($rc === FALSE) + throw new Exception ("Can't define the flags", 500); + return true; + } + + /** Mark mail(s) as read. + If msgno is an array, a list of mails will be modified. + If msgno is an integer, only one mail will be modified */ + public function markMailAsRead ($msgno) + { + $this->changeFolder ($this->curDir); + if (is_array ($msgno)) + $msgno = implode (",", $msgno); + $rc = @imap_setflag_full (self::$instance[$this->mailbox], $msgno, + "\\Seen"); + imap_errors(); + if ($rc === FALSE) + throw new Exception ("Can't mark mail as read", 500); + return true; + } + + /** Mark mail(s) as unread. + If msgno is an array, a list of mails will be modified. + If msgno is an integer, only one mail will be modified */ + public function markMailAsUnread ($msgno) + { + $this->changeFolder ($this->curDir); + if (is_array ($msgno)) + $msgno = implode (",", $msgno); + $rc = @imap_clearflag_full (self::$instance[$this->mailbox], $msgno, + "\\Seen"); + imap_errors(); + if ($rc === FALSE) + throw new Exception ("Can't mark mail as read", 500); + return true; + } +}