Piszemy klienta poczty e-mail w PHP

Autor: Tomasz Jędrzejewski
Data publikacji: 18.12.2003, 17:52 | Ostatnia modyfikacja: 02.11.2006, 16:57

Artykuł ten demonstruje, jak napisać klienta poczty elektronicznej korzystającego z protokołu IMAP w PHP. Zawiera także wprowadzenie do modułu "imap".



Zapewne wiele razy patrzyÅ‚eÅ› z zazdroÅ›ciÄ… na interfejsy WWW do zarzÄ…dzania kontem pocztowym. Ba - być może nawet marzyÅ‚eÅ› o stworzeniu wÅ‚asnego? Tylko jak siÄ™ do tego zabrać, co jest potrzebne? Otóż do stworzenia takiego czegoÅ› potrzeba PHP, odrobiny inwencji, oraz rozszerzenia IMAP. Tak oto witam w kolejnym artykule z serii "Piszemy coÅ›tam", tym razem biorÄ…cym pod ostrzaÅ‚ klienty poczty e-mail...

Jak to będzie wyglądać?

Na poczÄ…tku musimy sobie zadać ważne pytanie: jak to ma wyglÄ…dać? ZdecydowaÅ‚em siÄ™ na stworzenie klienta, który:

Wiemy już, że do tego zadania wykorzystamy protokóÅ‚ pocztowy IMAP, oraz moduÅ‚ PHP pozwalajÄ…cy siÄ™ poprzez niego komunikować. Mocno zalecam także pobranie jakiegoÅ› serwera poczty e-mail, by móc spokojnie testować omawiany tu kod.

Do góry

Instalacja modułu

W systemach Windows jest ona (jak zwykle :)) bajecznie prosta - wystarczy otworzyć plik php.ini i usunąć średnik sprzed linii

;extension = php_imap.dll

pod Unixem należy zrekompilować PHP z opcjami

--with-imap=sciezka_do_biblioteki_klienckiej

Jak zwykle, w Unixie musimy mieć gdzieś odpowiednią bibliotekę. Tym razem odeślę do manuala PHP, gdzie opisany jest proces kompilacji jednej z nich.

Należy pamiÄ™tać, że moduÅ‚u IMAP w żadnym wypadku nie wolno nam używać razem z moduÅ‚ami Recode i YAZ, gdyż używajÄ… one tej samej sygnatury i może to powodować wiele problemów.

Do góry

Instalacja serwera IMAP

Aby nie mieć problemów z prezentowanym w dalszej części artykuÅ‚u kodem PHP, zalecam pobranie jakiegokolwiek serwera IMAP. JeÅ›li pracujesz na systemie UNIX/Linux, możesz sprawdzić ten adres: http://www.inter7.com/courierimap.html. Ja jednak piszÄ™ to wszystko w oparciu o system Windows, wiÄ™c znajdzie siÄ™ tu opis instalacji Windowsowego klienta. Jest nim MercuryMail, którego można pobrać ze strony http://www.pmail.com/. Instalacja jest prosta i wÅ‚aÅ›nie dlatego jÄ… opiszÄ™ :).

Uruchom pobrany plik i kliknij przycisk "Setup". Po rozpakowaniu archiwum i uruchomieniu programu instalacyjnego wskaż "New Installation". Zostaniesz zapytany o to, czy chcesz integracji z systemami Novell NetWare. Jak ktoÅ› wie, o co chodzi, to intuicja sama mu podpowie, co wybrać, natomiast reszta powinna kliknąć przycisk "No NetWare support". Kolejnym krokiem jest wskazanie katalogu, do którego ma zostać zainstalowany program. Po klikniÄ™ciu na "OK" program zapyta siÄ™ o... nastÄ™pnÄ… Å›cieżkÄ™ instalacyjnÄ… :). Jednak podejrzewam, że nie posiadasz programu "Pegasus Mail", a wiÄ™c nie musisz integrować z nim Mercurego. Kliknij wiÄ™c na "No Pegasus Mail integration", by powiedzieć o tym instalatorowi. NastÄ™pnie kolejna Å›cieżka, na nasze potrzeby wystarczy zaakceptować domyÅ›lnÄ…. Teraz zostaniesz zaprowadzony do bardzo ważnego okna, a mianowicie do wyboru protokoÅ‚ów i możliwoÅ›ci, które chcesz mieć aktywne. Nam wystarczÄ… "SMTP server module" i "IMAP4rev1 module" (na dole). BÄ™dzie trzeba jeszcze wybrać jakieÅ› duperszmity zwiÄ…zane z optymalizacjÄ… korzystania z SMTP, ale nam to niepotrzebne, wiÄ™c wybieramy "Install no SMTP client". Potem bÄ™dziesz musiaÅ‚ wypeÅ‚nić dane odnoÅ›nie sieciowej konfiguracji. Wpisz domenÄ™, w jakiej pracujesz (najprawdopodobniej wystarczy "localhost"). NastÄ™pnie program poprosi CiÄ™ o wskazanie, co ma robić z e-mailami przeznaczonymi dla innych serwerów. Jako, że my raczej nie bÄ™dziemy ich wysyÅ‚ać tak daleko, możesz wskazać "None". Potwierdź jeszcze jednÄ… Å›cieżkÄ™ dostÄ™pu i wreszcie bÄ™dzie Ci dane ujrzeć przycisk "Install Mercury/32" :). Chyba wiesz, co należy już zrobić?

Do góry

Konfiguracja serwera Mercury

Jako że chcemy porzÄ…dnie "pobawić" siÄ™ protokoÅ‚em IMAP, dodamy sobie parÄ™ skrzynek pocztowych. Uruchom Mercurego (plik mercury.exe), po czym wybierz z menu pozycjÄ™ "Configuration". Na dole jest opcja "Manage local users". Po klikniÄ™ciu na niÄ… ukaże siÄ™ okienko, które pozwoli Ci na utworzenie i zarzÄ…dzanie skrzynkami pocztowymi userów. ResztÄ™ opcji możesz zostawić w spokoju, gdyż powinna automatycznie dopasować siÄ™ do ustawieÅ„ twego komputera.

Do góry

Zaczynamy programować

A wiÄ™c przystÄ…pmy do programowania. Z serwerem IMAP (który musi być włączony w trakcie testów) łączymy siÄ™ podobnie, jak z bazÄ…. Musimy najpierw otworzyć połączenie, a na koÅ„cu je zamknąć. NapisaÅ‚em wyżej, że nasz skrypt umożliwi logowanie siÄ™ do rozmaitych skrzynek pocztowych na serwerze, wiÄ™c podobna praktyka, jak przy łączeniu siÄ™ ze zwykłą bazÄ… danych (że nazwÄ™ użytkownika i hasÅ‚o wbudowujemy w skrypt) odpada. Musimy napisać stosowny mechanizm. BÄ™dzie on umieszczony w katalogu "includes" w pliku "polacz.php":

<?php
   define('CZAS_SESJI', 3600);
 
   function polacz($user, $haslo){
      $r = @imap_open('{eniac.blok22}', $user, $haslo) or $r = imap_last_error();
      return $r;
   }
 
   if($_GET['co']   == 'wyloguj'){
      unset($_COOKIE['user']);
      unset($_COOKIE['pass']);
      setcookie('user', '', 0);
      setcookie('pass', '', 0);
   }
 
   if(!isset($_COOKIE['user']) || !isset($_COOKIE['pass'])){
      if(isset($_POST['user']) && isset($_POST['pass'])){
         $imap = polacz($_POST['user'], $_POST['pass']);
         if(is_string($imap)){
            // Blad
            blad($imap);
            loguj();
            stopka();
            die();
         }elseif(is_resource($imap)){
            setcookie('user', $_POST['user'], time()+CZAS_SESJI);
            setcookie('pass', $_POST['pass'], time()+CZAS_SESJI);
         }
      }else{
         loguj();
         stopka();
         die();
      }
   }else{
      if(isset($_COOKIE['user']) && isset($_COOKIE['pass'])){
         $imap = polacz($_COOKIE['user'], $_COOKIE['pass']);
         if(is_string($imap)){
            // Blad
            blad($imap);
            loguj();
            stopka();
            die();
         }elseif(is_resource($imap)){
            setcookie('user', $_COOKIE['user'], time()+CZAS_SESJI);
            setcookie('pass', $_COOKIE['pass'], time()+CZAS_SESJI);
         }
      }
   }
?>

Za połączenie siÄ™ odpowiada funkcja "polacz" zawierajÄ…ca odwoÅ‚anie do pierwszej poznanej przez nas funkcji obsÅ‚ugi IMAP'a - imap_open(). Jako parametry musimy podać nazwÄ™ hosta, pod jakim dostÄ™pny jest serwer IMAP (koniecznie w nawiasach klamrowych). NastÄ™pnie wskazujesz użytkownika, który posiada na serwerze skrzynkÄ™, oraz jego hasÅ‚o. Funkcja imap_last_error() pozwala zwrócić ewentualny komunikat błędu.

Dalszy kod sprawdza, czy ktoś jest zalogowany, czy też nie i w zależności od tego podejmuje stosowną akcję. Nazwa użytkownika i hasło przesyłane są poprzez ciastko. Jeśli nie jesteś wybredny/nie masz jeszcze zbyt dużych zdolności, możesz kodu tego używać jako logowania do twego serwisu (aczkolwiek bezpieczny to on nie jest...).

W powyższym przykładzie znajdują się odwołania do funkcji typu "loguj", "stopka", "blad". Są to funkcje generujące kod HTML dla naszego klienta i wysyłające go do przeglądarki. Umieszczone są one w pliku "layout.php" w katalogu "includes", a plik ten prezentuje się następująco (listing jest baaaaaaaardzo długi, ale jakoś musimy przez niego przebrnąć...):

Aby zaoszczedzić miejsce: Pobierz kod listingu.

Zauważ, że połączenie z serwerem IMAP zamyka funkcja stopka(). Używa do tego celu funkcji "imap_close()" pobierajÄ…cej identyfikator połączenia. Teraz możemy już przystÄ…pić do napisania wÅ‚aÅ›ciwych i dziaÅ‚ajÄ…cych już plików. Najpierw stworzymy plik "index.php" (wszystkie nastÄ™pne pliki umieszczaj w katalogu gÅ‚ównym skryptu) z takÄ… zawartoÅ›ciÄ…:

<?php
 
   require('./includes/layout.php');
   naglowek('Statystyki skrzynki');
 
   require('./includes/polacz.php');
 
   pasek_narzedzi();
 
   $skrzynka = imap_mailboxmsginfo($imap);
   if(is_object($skrzynka)){
      statystyki(
         $skrzynka->Date,
         $skrzynka->Driver,
         preg_replace('/\{(.*?):([0-9]*)\/imap\/user="(.*?)"}INBOX/', '\\3@\\1', $skrzynka->Mailbox),
         $skrzynka->Nmsgs,
         $skrzynka->Recent,
         $skrzynka->Unread,
         $skrzynka->Deleted,
         $skrzynka->Size
      );
   }else{
      blad('Nie można pobrać statystyk skrzynki!');
   }
 
   stopka();
 
?>

Plik ten bÄ™dzie wyÅ›wietlać statystyki naszej skrzynki. Możemy pobrać je funkcjÄ… "imap_mailboxmsginfo()". Za parametr pobiera tylko identyfikator połączenia, a w zamian zwraca obiekt z interesujÄ…cymi nas danymi. Mamy wiÄ™c datÄ™, ilość nowych wiadomoÅ›ci, ilość usuniÄ™tych, ilość nieczytanych itp. Zwróć uwagÄ™, że pole "Mailbox" potraktowaÅ‚em dodatkowo funkcjÄ… preg_replace. Po co? Ponieważ normalnie dostajemy różne gÅ‚upoty w stylu "{localhost:port/imap/user=zyx}INBOX", lecz sÄ… one lekko niezrozumiaÅ‚e. Dlaczegóż wiÄ™c nie przerobić ich na czytelniejszÄ… postać "zyx@localhost"?

Tu dam jeszcze małą poradÄ™: jeÅ›li zależy Ci na wydajnoÅ›ci i nie masz nic przeciwko lekkiemu uszczupleniu iloÅ›ci danych prezentowanych w statystykach, użyj do pobrania danych funkcji imap_status(). W manualu znaduje siÄ™ komentarz dodany przez jednego z programistów PHP, który pokazuje różnicÄ™ wydajnoÅ›ci. Pobranie danych ze skrzynki z 3987 wiadomoÅ›ciami przy użyciu imap_mailboxmsginfo() - 6 minut i 5 sekund; przy użyciu imap_status() - 11,05 sekundy. Wynik przemawia sam za siebie.

Do góry

Wysyłanie wiadomości

Mimo iż funkcja wysyłająca wiadomości e-mail ma w nazwie "imap", to używa do tego celu protokołu SMTP i właściwie można uznać ją za synonim funkcji mail(). Korzystają one z tych samych ustawień w php.ini, dlatego wejdź tam i odszukaj blok (uwaga: wyciąłem komentarze, by nie marnować miejsca):

[mail function]
SMTP = localhost

sendmail_from = zyx@localhost

;sendmail_path =

;mail.force_extra_paramaters =

Wszyscy powinni w dyrektywie SMTP wpisać nazwÄ™ serwera SMTP. Jako że my zainstalowaliÅ›my Mercurego z obsÅ‚ugÄ… tego protokoÅ‚u, możesz tam Å›miaÅ‚o wpisać "localhost". DyrektywÄ™ "sendmail_from" wypeÅ‚niajÄ… użytkownicy Windowsa, natomiast dwie nastÄ™pne użytkownicy systemów unixowych.

A oto kod pliku "wiadomosc.php" wyświetlającego odpowiedni formularz, a następnie "wyslij.php" wysyłającego e-maila:

<?php
 
   require('./includes/layout.php');   
   naglowek('Edytor wiadomosci');
 
   require('./includes/polacz.php');
 
   pasek_narzedzi();
 
   if(isset($_GET['odp'])){
      $naglowki = imap_header($imap, $_GET['odp']);
 
      if($naglowki -> Subject == ''){
         $temat = 'Brak tematu';
      }else{
         $temat = $naglowki -> Subject;
      }
 
      form_wiad($temat, $naglowki -> fromaddress);
   }else{
      form_wiad();
   }
   
   stopka();
 
?>

Zapewne dostrzegÅ‚eÅ› tu nieznanÄ… funkcjÄ™ "imap_header()". Jej znaczenie poznamy dalej. Tu jest ona potrzebna, by móc prawidÅ‚owo obsÅ‚użyć odpowiadanie na wiadomoÅ›ci. Oto kod pliku "wyslij.php":

<?php
 
   require('./includes/layout.php');
   naglowek('Wysylanie');
 
   require('./includes/polacz.php');
 
   pasek_narzedzi();
 
   if(imap_mail($_POST['to'], $_POST['temat'], $_POST['tresc'], 'Content-type: text/plain')){
      komunikat('Wiadomość została wysłana.');
   }else{
      blad(imap_last_error());
   }
 
   stopka();
 
?>

Tak więc naszą funkcją wysyłającą wiadomości był "imap_mail()". W swej podstawowej formie używa jej się tak samo, jak "mail()" i możliwe, że składnia wydaje się znajoma.

Do góry

Wyświetlanie listy wiadomości

Lista wiadomości będzie wyświetlana poprzez plik "skrzynka.php". Oto jego kod:

<?php
 
   require('./includes/layout.php');
   naglowek('Twoja skrzynka');
 
   require('./includes/polacz.php');
 
   pasek_narzedzi();
 
   if(isset($_POST['usun'])){
 
      if(count($_POST['dmsg']) > 0){
         foreach($_POST['dmsg'] as $wiad_num){
            imap_delete($imap, $wiad_num);
         }
         imap_expunge($imap);
      }
   }
 
   $ilosc = imap_num_msg($imap);
 
   if($ilosc > 0){
      list_wiadomosci();
 
      for($i = $ilosc; $i > 0; $i--){
         $naglowki = imap_header($imap, $i);
         $struktura = imap_fetchstructure($imap, $i);
 
         if($naglowki -> Subject == ''){
            $temat = 'Brak tematu';
         }else{
            $temat = $naglowki -> Subject;
         }
 
         wiadomosc($i, substr($naglowki->Date, 0, 22), htmlspecialchars($naglowki->fromaddress), $temat, ceil(($structure->bytes/1024)).' KB');
      }
 
      list_wiadomosci_koniec();
 
   }else{
      komunikat('Brak wiadomości w skrzynce');
   }
 
   stopka();
 
?>

Każda wiadomość w protokole IMAP ma przypisany specjalny identyfikator, czyli numer wiadomoÅ›ci. DziaÅ‚a on jednak trochÄ™ inaczej od identyfikatorów w bazach danych, co pozwala nam na pobranie wiadomoÅ›ci zwykłą pÄ™tlÄ… for. Na poczÄ…tku ustalamy ilość e-maili funkcjÄ… imap_num_msg(), a później uruchamiamy pÄ™tlÄ™. Za każdym przebiegiem dane o pojedynczym liÅ›cie otrzymujemy wywoÅ‚ujÄ…c funkcje imap_header() i imap_fetchstructure(). Pierwsza z nich pozwala nam pobrać takie duperszmity, jak temat, adresat, nadawca, flaga. Z kolei druga daje nam dostÄ™p do danych technicznych e-maila: typu (np. text/plain), rozmiaru, kodowania. Nas interesuje tylko rozmiar, który potem odpowiednio przetwarzamy. Wszystko wprowadzamy do funkcji wiadomosc(), która generuje kod HTML dla pojedynczego wiersza z danymi wiadomoÅ›ci. Dodaje także tam pole checkbox pozwalajÄ…ce nam na wybranie wiadomoÅ›ci do usuniÄ™cia. Możesz teraz skoczyć do pliku "layout.php", by podpatrzeć jego nazwÄ™. Pole to zwie siÄ™ "dmsg[]". Widzisz te nawiasy klamrowe? DziÄ™ki temu do PHP (po klikniÄ™ciu na "UsuÅ„ zaznaczone") przejdzie tablica z numerami wszystkich e-maili do skasowania, a nie numer ostatniej zaznaczonej pozycji. Kod obsÅ‚ugujÄ…cy usuwanie znajduje siÄ™ tuż nad kodem wyÅ›wietlajÄ…cym. KażdÄ… wiadomość najpierw oznaczam flagÄ… "UsuniÄ™ty" używajÄ…c do tego funkcji "imap_delete()". Jednak nie oznacza to fizycznego usuniÄ™cia listu z serwera. Do tego potrzebne jest jeszcze wywoÅ‚anie funkcji imap_expunge(). Dlaczego jest to tak udziwnione? Zobacz: dziÄ™ki temu możesz stworzyć coÅ› w stylu "Elementów usuniÄ™tych". WspomniaÅ‚em wczeÅ›niej, że funkcja imap_header() zwraca m.in flagÄ™ listu. JednÄ… z możliwych flag jest "UsuniÄ™ty". Oznacza to, że w skrzynce odbiorczej możesz wyÅ›wietlać wszystkie listy, które tej flagi ustawionej nie majÄ… (można do tego użyć zwykÅ‚ej instrukcji IF). W "Elementach usuniÄ™tych" przeciwnie - flaga ta musi być ustawiona, by listy tam siÄ™ znalazÅ‚y. Dalsza przeróbka: w skrzynce odbiorczej przy usuwaniu nie wywoÅ‚ujesz imap_expunge(); jej wywoÅ‚anie przenosisz do pliku odpowiedzialnego za elementy usuniÄ™te - tam jest ona aktywowana po klikniÄ™ciu na "Opróżnij elementy usuniÄ™te".

Do góry

Wyświetlanie pojedynczej wiadomości

Samo pokazanie listy wiadomości nie wystarczy, jeśli użytkownik nie może ich przeczytać. Dlatego też stworzymy teraz plik "zobacz.php" pokazujący treść wiadomości. Wykorzystałem w nim kod zaprezentowany w komentarzach do dokumentacji PHP. Daje on nam pewność, że treść zostanie porządnie zdekodowana i złożona do kupy. "Suche" wywołanie funkcji do pobierania tekstu z listu jest MOCNO niezalecane.

<?php
 
   function pobierz_mime(&$structure){
      $primary_mime_type = array('TEXT', 'MULTIPART', 'MESSAGE', 'APPLICATION', 'AUDIO', 'IMAGE', 'VIDEO', 'OTHER');
      if($structure->subtype){
         return $primary_mime_type[(int) $structure->type] . '/' . $structure->subtype;
      }
      return 'TEXT/PLAIN';
   }
 
   function pobierz_czesc($stream, $msg_number, $mime_type, $structure = 0, $part_number = 0){
      if(!$structure){
         $structure = imap_fetchstructure($stream, $msg_number);
      }
 
      if($mime_type == pobierz_mime($structure)){
         if(!$part_number){
            $part_number = '1';
         }
         $text = imap_fetchbody($stream, $msg_number, $part_number);
         if($structure->encoding == 3){
            return imap_base64($text);
         }elseif($structure->encoding == 4){
            return imap_qprint($text);
         }else{
            return $text;
         }
      }
      if($structure->type == 1){
         while(list($index, $sub_structure) = each($structure->parts)){
            if($part_number){
               $prefix = $part_number . '.';
            }
            $data = pobierz_czesc($stream, $msg_number, $mime_type, $sub_structure, $prefix . ($index + 1));
            if($data){
               return $data;
            }
         }
      }
 
      return 0;
   }
 
   require('./includes/layout.php');
   naglowek('Edytor wiadomosci');
 
   require('./includes/polacz.php');
 
   pasek_narzedzi();
 
   if($_GET['num'] > 0){
      $naglowki = imap_header($imap, $_GET['num']);
 
      pokaz($_GET['num'], $naglowki->fromaddress, $naglowki -> Subject, nl2br(pobierz_czesc($imap, $_GET['num'], 'TEXT/PLAIN')));
 
   }
 
   stopka();
 
?>

Treść listu pobierana jest funkcjÄ… imap_fetchbody(). Podajemy do niej: identyfikator połączenia, numer wiadomoÅ›ci, oraz numer części do pobrania. PosÅ‚ugujÄ…c siÄ™ funkcjÄ… imap_fetchstructure() pobieramy dane techniczne wiadomoÅ›ci, które pozwolÄ… nam jÄ… rozkodować. JeÅ›li zostaÅ‚a ona podzielona na kilka części, używany jest mechanizm rekurencji do pobrania caÅ‚oÅ›ci.

Generowany kod HTML zawiera dwa odnośniki: "Odpowiedz" kierujący nas do pliku "wiadomosc.php"; "Usuń" dający nam możliwość skasowania wiadomości. Do tego potrzebny jest jednak plik "usun.php", a oto jego kod:

<?php
 
   require('./includes/layout.php');
   naglowek('Wysylanie');
 
   require('./includes/polacz.php');
 
   pasek_narzedzi();
 
   if(isset($_GET['num'])){
      imap_delete($imap, $_GET['num']);
      imap_expunge($imap);
      komunikat('Wiadomosc zostala usunięta');
   }else{
      blad('Nie wybrano wiadomości!');
   }
 
   stopka();
 
?>

Krótko i przejrzyÅ›cie :). Możesz teraz przetestować caÅ‚y skrypt.

Do góry

Zakończenie

Tak oto artykuł ten dobiega powoli końca. Pozostaje mi tylko wyrażać nadzieję, że zainteresowałem Cię tym tematem i że będziesz dalej eksperymentował z przedstawionym tu kodem. Oto, co mógłbyś wykonać dla przećwiczenia praktycznego poznanych informacji:

A oto parę przydatnych adresów:

Autor: Tomasz "Zyx" Jędrzejewski, www.zyxist.com

Do góry

Waszym zdaniem:

Nikt jeszcze nie dodał swojego komentarza. Możesz być pierwszy!


Twoim zdaniem:

Reklama

banner

Partnerzy

CityDesign.pl
phpSolutions