Článek vyšel na serveru Root.cz.
V systému pro zpracování objednávek může být velice užitečné, pokud se u každé objednávky zobrazuje veškerá e-mailová korespondence, která se zákazníkem proběhla. K zajištění této funkčnosti lze přistupovat v zásadě dvěma způsoby:
Toto řešení vyžaduje nízkoúrovňové napojení na příjem (např. pomocí .procmailrc
) a odesílání zpráv, což může být někdy pracné zařídit a navíc to znamená zprávy ukládat dvakrát – jednou do standardního úložiště a jednou do databáze.
Protokol IMAP dovoluje ve zprávách snadno prohledávat např. podle adresy příjemce nebo odesílatele, pokud ale nejsou na poštovním serveru indexy, je vyhledávání ve velkých mailboxech nesnesitelně pomalé (desítky vteřin).
Pokud se tedy budeme chtít vydat touto cestou, budeme si muset informace pro vyhledávání ukládat u sebe a se serverem je synchronizovat. K tomu už je potřeba trochu vědět, jak IMAP funguje:
Zprávy v každém mailboxu mají přiřazené číslo UID, které se v průběhu času nemění a je vzestupné. Nové zprávy dostanou první dosud nepoužité UID. Kdo zná modifikátor AUTO_INCREMENT, je mu princip dobře známý. Kromě tohoto trvale platného identifikátoru může server vracet také pořadí zprávy v daném kontextu (např. na základě vyhledávání), to ale potřebovat nebudeme.
Plán práce: pro každý adresář si budeme ukládat počet zpráv, které v něm jsou uložené, a hodnotu uidnext, která uchovává UID příští zprávy, která bude do adresáře uložena. Pokud se změní uidnext, tak do adresáře nějaká zpráva přibyla, pokud se změní rozdíl uidnext – počet zpráv, tak z něj byla nějaká zpráva odstraněna. Pokud se změní hodnota uidvalidity, tak došlo ke změně identifikátorů zprávy a bude nutné je zaindexovat znovu. Tuto kontrolu provedeme postupně pro všechny adresáře:
<?php $root = "{imap.example.org}"; $imap = imap_open($root, $login, $password, OP_HALFOPEN); $imap_mailbox = array(); $result = mysql_query("SELECT * FROM imap_mailbox"); while ($row = mysql_fetch_assoc($result)) { $imap_mailbox[$row["mailbox"]] = $row; } mysql_free_result($result); // průchod přes všechny adresáře foreach (imap_list($imap, $root, "*") as $mailbox) { $mailbox_root = substr($mailbox, strlen($root)); $status = imap_status($imap, $mailbox, SA_UIDNEXT | SA_UIDVALIDITY | SA_MESSAGES); // zjištění počtu zpráv a hodnoty uidnext $reindex = ($status->uidvalidity != $imap_mailbox[$mailbox_root]["uidvalidity"]); if ($reindex || $status->uidnext != $imap_mailbox[$mailbox_root]["uidnext"] || $status->messages != $imap_mailbox[$mailbox_root]["messages"]) { // v adresáři došlo ke změně imap_reopen($imap, $mailbox); // aktualizace počtů if (isset($imap_mailbox[$mailbox_root])) { mysql_query("UPDATE imap_mailbox SET uidnext = $status->uidnext, uidvalidity = $status->uidvalidity, messages = $status->messages WHERE mailbox = '" . mysql_real_escape_string($mailbox_root) . "'"); if ($reindex) { // přeindexování zpráv mysql_query("DELETE FROM imap WHERE mailbox = '" . mysql_real_escape_string($mailbox_root) . "'"); } } else { mysql_query("INSERT INTO imap_mailbox (mailbox, uidnext, uidvalidity, messages) VALUES ('" . mysql_real_escape_string($mailbox_root) . "', $status->uidnext, $status->uidvalidity, $status->messages)"); } // přidané zprávy if ($status->uidnext != $imap_mailbox[$mailbox_root]["uidnext"]) { foreach (imap_fetch_overview($imap, ($reindex ? 1 : $imap_mailbox[$mailbox_root]["uidnext"]) . ":" . ($status->uidnext - 1), FT_UID) as $val) { $set = array( "mailbox" => "'" . mysql_real_escape_string($mailbox_root) . "'", "uid" => $val->uid, "subject" => "'" . mysql_real_escape_string(imap_utf8($val->subject)) . "'", "date" => "'" . date("Y-m-d H:i:s", strtotime($val->date)) . "'", ); // uložení všech kombinací příjemců a odesílatelů foreach (imap_rfc822_parse_adrlist($val->from, "localhost") as $from) { $set["addr_from"] = "'" . mysql_real_escape_string("$from->mailbox@$from->host") . "'"; foreach (imap_rfc822_parse_adrlist($val->to, "localhost") as $to) { $set["addr_to"] = "'" . mysql_real_escape_string("$to->mailbox@$to->host") . "'"; mysql_query("INSERT INTO imap (" . implode(", ", array_keys($set)) . ") VALUES (" . implode(", ", $set) . ")"); } } } } // smazané zprávy if (!$reindex && $status->messages - $imap_mailbox[$mailbox_root]["messages"] != $status->uidnext - $imap_mailbox[$mailbox_root]["uidnext"]) { mysql_query(" DELETE FROM imap WHERE mailbox = '" . mysql_real_escape_string($mailbox_root) . "' AND uid NOT IN (" . implode(", ", imap_search($imap, "ALL", SE_UID)) . ") "); } } } imap_close($imap); ?>
Protože otevření adresáře funkcí imap_reopen stojí nějaký čas, provádí se jen v případě změny zjištěné funkcí imap_status. Hlavičky všech nových zpráv získáme funkcí imap_fetch_overview, které předáme seznam požadovaných zpráv ve tvaru $od:$do
. Pokud skript zjistí, že z adresáře nějaké zprávy zmizely, smažou se všechny ty, jejichž UID nevrátí funkce imap_search s parametrem ALL
. Uživatelská funkce mysql_get_vals
vrátí pole, kde klíče tvoří první sloupec a hodnoty druhý.
Zprávu zobrazíme funkcí imap_fetchbody, kvůli zprávám v HTML formátu nebo s přílohami ale nejprve budeme muset zjistit její strukturu funkcí imap_fetchstructure. Skript očekává, že ID zobrazované zprávy dostane v parametru select
:
<?php /** Vrácení první části IMAP zprávy vyhovující požadovanému typu a podtypu * @param object struktura zprávy vrácená funkcí imap_fetchstructure(), změní se na strukturu nalezené části zprávy * @param int požadovaný typ vrácené části (viz imap_fetchstructure(), např. 0 = text) * @param string požadovaný podtyp vrácené části (např. "PLAIN"), při předání hodnoty null na něm nezáleží * @return string identifikátor požadované části použitelný v imap_fetchbody() nebo false v případě nenalezení požadované části * @copyright Jakub Vrána, https://php.vrana.cz/ */ function imap_first_part(&$structure, $type = 0, $subtype = "PLAIN") { if ($structure->type == $type && (!isset($subtype) || $structure->subtype == $subtype)) { return 1; } elseif ($structure->parts) { foreach ($structure->parts as $key => $val) { $return = imap_first_part($val, $type, $subtype); if ($return) { $structure = $val; return ($key + 1) . ($return !== 1 ? ".$return" : ""); } } } return false; } // načtení struktury $row = mysql_fetch_assoc(mysql_query("SELECT * FROM imap WHERE id = " . intval($_GET["select"]))); $imap = imap_open("$root$row[mailbox]", $login, $password); $structure = imap_fetchstructure($imap, $row["uid"], FT_UID); $part_number = imap_first_part($structure); // vypsání textové části zprávy if (!$part_number) { echo "Zpráva nemá textovou část.\n"; } else { $message = imap_fetchbody($imap, $row["uid"], $part_number, FT_UID | FT_PEEK); switch ($structure->encoding) { case ENCBASE64: $message = base64_decode($message); break; // imap_base64 case ENCQUOTEDPRINTABLE: $message = quoted_printable_decode($message); break; // imap_qprint } foreach ($structure->parameters as $parameter) { if (strtolower($parameter->attribute) == 'charset') { $message = iconv($parameter->value, 'utf-8', $message); break; } } echo nl2br(htmlspecialchars($message)); } ?>
Tento skript zobrazí první textovou část zprávy. Bylo by možné zobrazovat i zprávy v HTML formátu, v tom případě je ale tělo zprávy nutné důkladně ošetřit, aby nemohlo dojít ke Cross Site Scriptingu.
Diskuse je zrušena z důvodu spamu.