JustPaste.it

IMAP w stylu PHP

Wszyscy korzystamy z poczty elektronicznej. Ci z nas, którzy bardziej zaangażowani są w działalność w sieci posiadają zazwyczaj wiele kont, z których pocztę pobierają poprzez protokół POP za pomocą programów, często z rozbudowanymi funkcjami filtrującymi i porządkującymi pocztę. Wiadomości kopiowane są z serwera na nasz komputer, tym samym ich odczyt odbywa się całkowicie lokalnie. Co zrobić jednak, kiedy dostęp do tej samej poczty chcemy mieć w wielu miejscach? Być może na komputerze w domu, w pracy i na laptopie? Nie kasować listów na serwerze i ściągnąć na każdym z nich osobno?

Istnieje inne rozwiązanie – korzystanie z poczty bezpośrednio na serwerze dzięki możliwościom jakie daje nam protokół IMAP. Rozwinięcie tego skrótu - Internet Message Access Protocol – daje też dobry obraz jego faktycznych możliwości. Nie jest on jedynie protokołem umożliwiającym ściąganie poczty. Kluczowe w tym przypadku słowo 'Access' oznacza możliwość korzystania z poczty znajdującej się na zdalnym serwerze tak jakby była ona przechowywana lokalnie.

W tym artykule pokażę jak napisać w PHP pewne podstawowe moduły interfejsu do zarządzania pocztą poprzez protokół IMAP. Zanim zaczniemy warto jednak sprawdzić, czy dostęp do twojego konta jest w ogóle możliwy za pomocą IMAP – w Polsce nie jest to często oferowana funkcja. Głównym czynnikiem ograniczającym dostęp do IMAP jest wymóg posiadania dobrego połączenia z Internetem, naturalnie za pomocą stałego łącza, co jeszcze do niedawna nie było u nas łatwe do osiągnięcia. Na szczęście teraz DIAL-UP przechodzi do historii.

PHP w akcji

PHP posiada bardzo szeroką gamę funkcji służących korzystaniu z protokołu IMAP. Ich przydatność nie jest jednak ograniczona wyłącznie do samego IMAP. Obsługują one równocześnie protokoły POP3 oraz NNTP a także dają możliwość korzystania z lokalnych skrzynek pocztowych.

Wszystkie funkcje IMAP są powiązane z odpowiednim rozszerzeniem PHP, które nie jest domyślnie instalowane. W systemie Windows konieczne będzie znalezienie w pliku php.ini linijki:

;extension=php_imap.dll

Należy usunąć na jej początku średnik oraz uruchomić ponownie oprogramowanie serwera, pod którym PHP u nas pracuje.

Linux z kolei będzie wymagał od nas rekompilacji PHP z opcją:

--with-imap=/sciezka/do/imap

Połączenie

Aby w jakikolwiek sposób korzystać z zasobów konta IMAP musimy rozpocząć od połączenia z serwerem, które otwiera nam funkcja imap_open(). Jej składnia wygląda następująco:

$mbox = imap_open("{mail.example.com:143}INBOX", "user", "haslo");

Gdzie mail.example.org jest przykładowym adresem serwera pocztowego, port 143 jest domyślny dla protokołu IMAP, INBOX domyślną nazwą skrzynki pocztowej a user i haslo danymi użytkownika konta, koniecznymi do zalogowania.

Ze względu na dużą wagę bezpieczeństwa komunikacji w ostatnich czasach możemy chcieć kontaktować się z serwerem za pomocą szyfrowanego połączenia. W tym przypadku otwarcie połączenia będzie wyglądało następująco:

$mbox = imap_open("{mail.example.com:993/ssl}INBOX", "user", "haslo");

Zmienił się port pod jakim będziemy się łączyć z serwerem, ale również zaznaczyliśmy potrzebę wywołania funkcji SSL poprzez dodanie po nazwie serwera opcji /ssl. Takich możliwych do wykorzystania opcji jest więcej, pełną ich listę można znaleźć w dokumentacji php. Na nasze potrzeby w obecnej chwili nie musimy znać ich reszty.

Wyświetlenie listy wiadomości

Zanim przystąpimy do pobierania i wyświetlania listy wiadomości na koncie musimy sprawdzić czy skrzynka w ogóle jakieś wiadomości zawiera.

if (imap_num_msg($mbox) == 0)
{
echo 'Brak wiadomosci';
}
else
{
echo 'Ilość wiadomości: ' . imap_num_msq($mbox);
}

Jak łatwo się domyślić funkcja imap_num_msg() zwraca nam ilość wiadomości znajdujących się w otwartej skrzynce. Zakładając, że jakieś wiadomości są na naszym koncie możemy je teraz wyświetlić:

echo '<table>
<tr>
<th>Lp</th>
<th>Data nadania</th>
<th>Nadawca</th>
<th>Temat</th>
</tr>';

for ($i = 1; $i <= imap_num_msg($mbox); $i++)
{
$naglowek = imap_headerinfo($mbox, $i, 80, 80);

echo '<tr>
<td>' . $i . '</td>
<td>' . gmdate('Y-m-d H:i:s', $naglowek->udate) . '</td>
<td><a href='' . $naglowek->from[0]->mailbox . '@' . $naglowek->from[0]->host . '">' . $naglowek->from[0]->mailbox . '@' . $naglowek->from[0]->host . '</a></td>
<td>' . $naglowek->fetchsubject . '</a></td>
</tr>';
}

echo '</table>';

Kod stał się trochę niewyraźny poprzez dodanie znaczników HTML, ale taki jest naturalny sposób wyświetlania listy wiadomości – w formie tabeli. Tworzymy pętlę o ilości powtórzeń równej liczbie wiadomości zwróconych funkcją imap_num_msg().

Dla każdej wiadomości wywołujemy funkcję imap_headerinfo(), która zwraca obiekt zawierający informacje z nagłówka wiadomości. Pierwszy jej parametrem jest zmienna przechowująca otwarte połączenie z serwerem, drugim numer wiadomości, której nagłówki chcemy pobrać. Dwa kolejne parametry są opcjonalne – ustalają one ograniczenia długości pól "od" oraz "temat".

Cały obiekt jest dosyć bogatą strukturą, której pełną zawartość wyjaśnia dokumentacja PHP. Dla naszych potrzeb wykorzystaliśmy jedynie kilka wartości:

udatezwraca datę i czas otrzymania wiadomości w formacie Unix, stąd potrzeba wykorzystania funkcji gmdate(), aby dane te przedstawić w czytelniejszej formie
from[]ta wartość przechowuje tablicę informacji o nadawcy wiadomości, osobno przechowując nazwę skrzynki (mailbox), a więc to co znajduje się przed znakiem @, oraz osobno adres serwera, a więc część adresu po @.
fetchsubjectzwraca temat wiadomości, opcjonalnie skrócony do ilości znaków podanej jako parametr funkcji imap_headerinfo()

Istnieje również druga, bardzo słabo opisana w dokumentacji PHP, jednak mimo wszystko łatwiejsza w użyciu funkcja do pobierania nagłówków wiadomości. Jest to mianowicie imap_headers(), która jako parametr przyjmuje jedynie identyfikator połączenia, zwraca natomiast tablicę z danymi z nagłówków wszystkich wiadomości w skrzynce.

O każdej wiadomości w tablicy znajdują się następujące informacje:

FlagiNumer)DataNadawcaTemat(Rozmiar)

Dla pewnej przykładowej wiadomości może to wyglądać tak:

AA oznacza "Answered", czyli na tą wiadomość wysłano już odpowiedź.
123)numer wiadomości w skrzynce to 123
14-Oct-2004odebrana została 14 października 2004 roku
noone@example.comadres nadawcy wiadomości
Example subjecttemat wiadomości
(8415 chars)długość wiadomości to 8415 znaków

Inne flagi jakie mogą posiadać wiadomości to:

  • N – New - nowa
  • R – Recent - niedawna
  • U – Unread - nieprzeczytana
  • F – Flagged - oflagowana
  • D – Deleted - usunięta
  • X – Draft – kopia robocza

Jak najłatwiej wyświetlić tak sformatowaną listę wiadomości? Może tak:
$naglowki = imap_headers($mbox);

if (!$naglowki)
{
print "Blad podczas pobierania naglowkow";
}
else
{
foreach($naglowki as $naglowek)
{
print "$naglowek";
}
}

Ponieważ funkcja imap_headers() pobiera całą listę wiadomości jej wykonanie może potrwać długo i znacznie większa staje się szansa niepowodzenia. W przypadku błędu zwraca ona wartość false, stąd nasz warunek sprawdzający czy pobieranie się udało.

Po wyświetleniu listy wiadomości pozostaje tylko zakończyć połączenie:

imap_close($mbox);

Funkcja zamykająca połączenie oprócz identyfikatora połączenia przyjmuje jeszcze jeden opcjonalny parametr – flagę CL_EXPUNGE. Jeśli ten parametr zostanie ustawiony wszystkie wiadomości oflagowane do usunięcia zostaną usunięte. Wiadomości oflagować można funkcją imap_delete() (nie usuwa ona fizycznie wiadomości a jedynie oznacza jako usunięte). Już po zastosowaniu tej funkcji, a jeszcze przed fizycznym usunięciem wiadomości możemy je przywrócić dzięki funkcji imap_undelete().

Fizycznie usunąć wiadomości do tego przeznaczone można również w każdej chwili wywołując funkcję imap_expunge(). Flaga CL_EXPUNGE jest także opcjonalnym parametrem funkcji imap_open(), którego ustawienie spowoduje skasowanie 'usuniętych' wiadomości w momencie zamykania połączenia.

Wyświetlenie konkretnej wiadomości

Tworząc stronę wyświetlającą cały list zaczniemy pewnie od wypisania nagłówków, takich jak nadawca, adresat czy tytuł, co w praktyce opisane zostało przed chwilą przy okazji wyświetlania listy wszystkich wiadomości. Przejdźmy więc do treści listu. Przyjmijmy, że identyfikator wiadomości którą chcemy wyświetlić mamy zapisany w zmiennej $msg_id.

nl2br(imap_fetchbody($mbox, $msg_id, "1"));

Funkcja imap_fetchbody() zwraca żądaną część wiadomości. W naszym przypadku będzie to część pierwsza, a więc czysty tekst, który dla poprawnego wyświetlenia musimy jeszcze poddać działaniu funkcji nl2br() zamieniającej nowe linie w tekście na znaczniki <br />.

Można by się to tego sposobu wyświetlania ograniczyć, jednakże nie uwzględnia on wszystkich możliwych wiadomości e-mail, które być może będziemy musieli wyświetlić. Tutaj niestety zaczynają się schody. Jak wszyscy wiemy list elektroniczny może zostać napisany czystym tekstem, co jednak w obecnych czasach coraz częściej ustępuje wersjom tzw. "wzbogaconym" czyli po prostu HTML. Zwłaszcza, że duża część programów domyślnie ustawiona jest na wysyłanie listów w tym formacie, co zresztą typowym użytkownikom zdaje się podobać. Do tego dochodzą wszelkie załączniki.

Wracając jednak do naszego wyświetlania wiadomości, jeżeli chcielibyśmy w systemie obsługi poczty uwzględnić również przypadki innych maili niż czysto tekstowe, będziemy musieli głębiej zainteresować się nie tylko możliwościami funkcji imap_fetchbody() ale również towarzyszącej jej imap_fetchstructure().

Ta nowa funkcja, jak wskazuje jej nazwa, służy analizie struktury wiadomości email. Zwraca bardzo bogato wyposażony obiekt z informacjami, z których najistotniejsze dotyczą rodzaju danych, które zawiera wiadomość oraz kodowania załączników.

Obsługa złożonych wiadomości to temat na zupełnie nowy artykuł, ograniczmy się tutaj więc do podstawowego przykładu.

Wysyłanie poczty

Wysyłaniem poczty zajmuje się funkcja imap_mail(), której parametry przypominają znaną już zapewne mail():

imap_mail('adresat@example.com', 'temat', 'tresc wiadomosci');

Trzy powyższe parametry są wymagane, możemy jednak wykorzystać również kolejne opcjonalne, a są to:

  • dodatkowe nagłówki, podawane jako String
  • adresy cc – "carbon copy", co w polskiej wersji znaczy "do wiadomości"
  • adresy bcc – "blind karbon copy", po polsku "ukryte do wiadomości"
  • ścieżka zwrotna, czyli adres pod jaki domyślnie ma być wysyłana odpowiedź na list

Możemy więc już wysłać czysto tekstowego maila. Rozszerzenie IMAP daje nam jednak znacznie bardziej zaawansowane możliwości. Korzystając z funkcji imap_mail_compose() możemy dodać do naszego maila zarówno załączniki jak i stworzyć list w formacie HTML.

W tym miejscu, aby niepotrzebnie nie wydłużać tekstu zakładam, że masz pewne pojęcie o sposobie, w jaki załączniki przesyłane są razem z listami.

Funkcja imap_mail_compose() wygeneruje nam pełną treść listu razem z nagłówkami i załącznikami na podstawie dwóch parametrów:

envelopedosłownie "koperta", spełnia podobną funkcję jak swój papierowy odpowiednik. Jest tablicą zawierającą pola adresowe.
body"ciało" czyli treść wiadomości razem z zakodowanymi załącznikami.

Przykładowy kod może wyglądać następująco:

// koperta zawiera adres nadawcy listu
$envelope["from"]= "joe@example.com";

// pierwsza część mówi o tym że wiadomość zawiera różnego typu dane
$part1["type"] = TYPEMULTIPART;
$part1["subtype"] = "mixed";

// druga część to nasz załącznik
$part2["type"] = TYPEAPPLICATION;
$part2["encoding"] = ENCBINARY;
$part2["subtype"] = "octet-stream";
$part2["description"] = $plik;
$part2["contents.data"] = file_get_contents('test.txt');

// trzecia część to obrazek tła
$part3["type"] = TYPEIMAGE;
$part3["subtype"] = "jpeg";
$part3["encoding"] = ENCBINARY;
$part3["id"] = "abc";
$part3["description"] = "tlo";
$part3["contents.data"] = file_get_contents('tlo.jpg');

// czwarta część zawiera tekst jako HTML
$part4["type"] = TYPETEXT;
$part4["subtype"] = "html";
$part4["description"] = "czesc 3";
$part4["contents.data"] = "<html><body background=CID:abc>test1</body></html>\n\n\n\t";

// składamy treść w jedną tablicę
$body[1] = $part1;
$body[2] = $part2;
$body[3] = $part3;
$body[4] = $part4;

// generujemy wiadomość
$msg = imap_mail_compose($envelope, $body);

// oddzielamy nagłówki od ciała
$msg_head = substr($a, 0, strpos($msg, "\r\n\r\n"));
$msg_body = substr($a, strpos($msg, "\r\n\r\n"));

// wysyłamy wiadomość
imap_mail('bob@example.com', 'Temat', $msg_body, $msg_head);

Jest to prosty przykład, który można udoskonalić. Rzecz, na którą warto zwrócić uwagę to, w jaki sposób zostało zapisane w kodzie HTML odwołanie do obrazka, który ma być tłem. Każda część może mieć swój identyfikator (Content-ID). Obrazek (część 3) ma ustawiony identyfikator jako abc i aby się odwołać do tego obrazka w HTMLu użyłem zapisu CID:abc. Aby zobaczyć jak stworzyć bardziej złożoną wiadomość najlepiej utworzyć ją jakimś klientem poczty i podejrzeć źródło.

Po odebraniu wiadomości z powyższego przykładu MS Outlook interpretował obrazek jako załącznik. Ukrycie załącznika wymagałoby znacznie więcej starań i komplikacji, które na dobrą sprawę nie przynoszą żadnego efektu.

Dla funkcji imap_mail_compose() w dokumentacji PHP nie istnieje praktycznie żaden opis, warto jednak przyglądnąć się licznym komentarzom napisanym przez czytelników.
NNTP, POP3

Jak wspomnieliśmy wcześniej, funkcje rozszerzenia IMAP obsługują poza tytułowym protokołem również POP3 oraz NNTP.

POP3 jest powszechnie stosowanym protokołem do odbierania poczty, działającym na porcie 110. Korzystając z niego możemy jedynie odebrać wiadomość i ją skasować. Nie działają żadne flagi, nie można nic wysyłać oraz nie ma on wbudowanej obsługi katalogów.

Połączenie z POP3 nawiązujemy w identyczny sposób jak w przypadku IMAP, zmieniając jednak port i dodając do imap_open() stosowną opcję:

$mbox = imap_open('{mail.example.com:110/pop3}INBOX', 'user', 'pass')

Protokół NNTP z kolei służy do obsługi list dyskusyjnych. Połączenie wywołuje się w jego przypadku nieco inaczej.

$nntp = imap_open('{news.example.com:119/nntp}pl.comp.lang.php', "", "")

Jak widać zamiast wywołania domyślnej skrzynki INBOX podajemy nazwę grupy dyskusyjnej, nazwą użytkownika i hasło pozostawiamy puste. Teraz pobieramy nagłówki wiadomości:

$overview = imap_fetch_overview($nntp, "1:20", 0);

W tym momencie pobierzemy 20 wiadomości, zaczynając od wiadomości 1. Jeśli łączymy się nie z lokalnym serwerem NNTP to nie polecam pobierania wszystkich nagłówków na raz, ponieważ spowoduje to najprawdopodobniej przekroczenie limitu wykonywania skryptu.

Funkcja imap_fetch_overview() zwraca obiekt, który zawiera wszystkie interesujące nas informacje (m.in. temat – "subject", datę – "date", nadawcę – "from", identyfikator wiadomości – "message_id").

Wyświetlamy teraz listę, którą pobraliśmy przed chwilą:

echo '<table>';

while (list($key, $val) = each($overview))
{
$temat = (empty($val->subject)) ? 'brak tematu' : $val->subject;

echo '<tr><td>' . $val->msgno . '</td>
<td>' . $temat . '</a>' . '</td>
<td>' . $val->from . '</td>
<td>' . $val->date . '</td></tr>';
}

echo '</table>';

Wyświetlanie zawartości danej wiadomości odbywa się w ten sam sposób jak podczas połączenia z IMAP.

echo nl2br(imap_fetchbody($nntp, $msgid, '1'));

Osoby korzystające z bardziej zaawansowanych programów do obsługi grup dyskusyjnych przywiązane będą zapewne do widoku listy wiadomości w formie drzewa wątków, tzn. kiedy kolejne wiadomości będące odpowiedziami na inne są pod nie wizualnie "podczepiane". Możemy również taki efekt osiągnąć stosując funkcję imap_thread(). Szczegóły jej wykorzystania najlepiej również poznać studiując dokumentację PHP.

Podsumowanie

Artykuł ten miał za zadanie wprowadzić jedynie w bardzo bogaty świat możliwości związanych z rozszerzeniami IMAP. Pełna lista dostępnych funkcji zawiera kilkadziesiąt pozycji, z których spora część przydatna jest głównie przy próbach wyświetlania wiadomości o bardziej skomplikowanej zawartości.

Aby w pełni wykorzystać zarówno możliwości protokołu IMAP jak i jego obsługi w PHP warto zacząć od zapoznania się z odpowiednim dokumentem RFC opisującym omawiany protokół. Być może to właśnie on jest przyszłością poczty elektronicznej.

 

Źródło: Piotr Lewandowski, Michał Paluchowski