Prosty formularz kontaktowy

Pokazałam już jak zrobić stronę za pomocą frameworka Kohana. Teraz dołożę na jednej z podstron prosty formularz kontaktowy i oprogramuje jego obsługę. Żeby bez problemu przejść przez ten tutorial może być konieczne zapoznanie się z poprzednim.
Przy okazji pokażę jak można wykorzystać następujące komponenty Kohana:

Wstęp

Struktura plików
Struktura plików
Żeby formularz kontaktowy mógł działać prawidłowo potrzebny będzie dodatkowy widok zawierający kod HTML formularza. Będzie trzeba stworzyć również model obsługujący dane z formularza. Ja pójdę o krok dalej i przygotuję sobie jeszcze jeden model, który będzie odpowiedzialny za generowanie listy adresów e-mail spośród których będzie można wybierać adresata wiadomości. W przyszłości ten kontroler będzie można rozbudować dodając komunikację z bazą danych. Poza trzeba będzie również dokonać zmian w głównym widoku i w kontrolerze. Po całej operacji struktura plików naszej aplikacji powinna wyglądać tak jak na rysunku obok.

Widok formularza

Do stworzenia widoku formularza, który zostanie umieszczony w pliku /application/views/t_form_contact.php wykorzystam sympatycznego helpera o sugestywnej nazwie form. Oczywiście cały kod formularza można było napisać ‘z palca’ tym bardziej, że nie jest on bardzo skomplikowany, ale jak się bawić to na całego. Później będę się zastanawiać co jest właściwie bardziej wydajne. Jedyne co jest nieco irytujące to fakt, że helper form (podobnie zresztą jak helper html) tworzą kod w formacie XHTML czyli domykają wszystkie znaczniki. Jeśli ktoś się uprze, żeby pisać w HTML 4 to będzie miał z tym trochę dodatkowej pracy.

Cały formularz postanowiłam zorganizować jako listę definicji. To dość wygodne rozwiązanie jeśli chce się potem taki formularz ładnie ostylować. W dodatku w przeciwieństwie do tabeli, której również można by było użyć, lista definicji pozostawia nieco więcej swobody jeśli chodzi o sposób umieszczenia względem siebie kontrolek i ich etykiet. Nie będę szczegółowo opisywać poszczególnych metod helpera form, których użyłam, gdyż ich opis z łatwością można znaleźć w dokumentacji a ich użycie jest proste i intuicyjne. Na poniższym listingu widać cały kod formularza.

<?php echo form::open('home/send',array('class'=>'search_form')) ?>
<dl>
	<dt><?php echo form::label('to', 'Wybierz adresata: ');?></dt>
	<dd><?php echo form::dropdown('to',$a_Email); ?></dd>
	<dt><?php echo form::label('title', 'Temat wiadomosci: '); ?></dt>
	<dd><?php echo form::input('title'); ?></dd>
	<dt><?php echo form::label('from','Twoj e-mail: '); ?></dt>
	<dd><?php echo form::input('from'); ?></dd>
	<dt><?php echo form::label('message', 'Tresc wiadomosci: '); ?></dt>
	<dd><?php echo form::textarea(array('name' => 'message', 'rows'=>'15', 'cols'=>'50'));?></dd>
	<dt><?php echo form::label('copy', 'Send copy to me: '); ?></dt>
	<dd><?php echo form::checkbox('copy', '1'); ?></dd>
	<dt><?php echo form::label('submit', '&nbsp;'); ?></dt>
	<dd><?php echo form::submit('send','Send').'
	'.form::submit('clear','Clear'); ?></dd>
</dl>
<?php echo form::close();?>

Warto podkreślić, że do metody form::open(), która generuje znacznik otwierający formularz, przekazuję jako pierwszy parametr ścieżkę w postaci home/send co oznacza, że w kontrolerze home będę musiała stworzyć metodę send(), która będzie odpowiadała za obsługę danych z tego formularza.

Zwrócę jeszcze tylko tylko uwagę na fakt, że w metodzie form::dropdown(), która generuje rozwijana listę, pojawiła się zmienna $a_Email. Jest to tablica e-maili, którą trzeba będzie do widoku przekazać z poziomu kontrolera. I to jest właściwie minimum jakie musi się znaleźć w pliku /application/views/t_form_contact.php.

Model generujący listę adresów

Aby wygenerować listę kontaktów prezentowanych w formularzu stworzymy model Contact_Model w pliku /application/models/contact.php. Na razie ten model będzie miał tylko jedną funkcję publiczną odpowiedzialną za zwracanie statycznej listy kontaktów. W przyszłości będzie można dodać tu komunikację z bazą danych. Na poniższym listingu widać całą definicję modelu.

defined('SYSPATH') OR die('No direct access allowed.');
class Contact_Model extends Model{
	private $a_EmailList = array();
	public function __construct(){
		parent::__construct();		
	}
	public function get_email_list(){
		$this->a_EmailList = array('email1@bexlab.pl' =>'Pozycja1 &nbsp;', 
				'email2@bexlab.pl' => 'Pozycja2 &nbsp;', 
				'email3@bexlab.pl' => 'Pozycja3 &nbsp;');
		return $this->a_EmailList;
	}
}

Interesująca jest tu tylko funkcja get_email_list(), która tworzy i zwraca tablicę w formacie odpowiednim dla uzytej w widoku formularza funkcji form::dropdown().

Model przetwarzający dane z formularza

Bardziej rozbudowany jest model Form_Contact_Model, zdefiniowany w pliku /application/models/form_contact.php. Zadaniem tego modelu będzie odbieranie i przetwarzanie danych z formularza. Poniżej szkielet modelu z wszystkimi zmiennymi i metodami niezbędnymi do obróbki danych z formularza.

defined('SYSPATH') OR die('No direct access allowed.');
class Form_Contact_Model extends Model{
	private $o_Post;
	private $a_FormContent;
	public $a_VerifySection;
	public function __construct(){
		parent::__construct();		
	}
	private function parse_data(){ }
	public function receive_data(){ }
	private function set_verify_section(){ }
	private function send_email(){ }
}

Najpierw omówię prywatną metodę parse_data(), która będzie potrzebna do sprawdzenia poprawności danych przesłanych z formularza. W tym celu wykorzystałam bardzo fajną bibliotekę Validation. W metodzie parse_data() tworzę obiekt klasy Validation i przypisuję do prywatnej zmiennej $o_Post. Następnie za pomocą metody pre_filter() mogę zdefiniować filtry jakie będą zastosowane podczas sprawdzania danych. W naszym przypadku jest to tylko jeden filtr usuwający białe znaki z początku i końca stringu. Następnie posługując się metodą add_rules() dodaję szereg warunków jakie muszą spełniać dane wprowadzone do formularza. Wszystkie wprowadzone przeze mnie reguły dotyczą obowiązkowych pól formularza. Poza tym niektóre będą sprawdzane pod względem poprawności formatu. Każda z tych reguł wygeneruje komunikat błędu jeśli dane z konkretnego pola nie będa spełniały warunków. Na koniec wywoływana jest metoda validate(), która dokonuje sprawdzenia danych zgodnie z zadanymi regułami a jej wynik jest zwracany przez metodę parse_data().

private function parse_data(){
	$this->o_Post = new Validation($_POST);
	$this->o_Post->pre_filter('trim', TRUE);
	$this->o_Post->add_rules('formID', 'required', array('valid','alpha_numeric'));
	$this->o_Post->add_rules('from', 'required', array('valid','email'));
	$this->o_Post->add_rules('message', 'required');
	$this->o_Post->add_rules('send', 'required');
	return $this->o_Post->validate();
}

Publiczna metoda receive_data(), która będzie wywoływana w kontrolerze, kopiuje dane z superglobalnej tablicy $_POST do prywatnej tablicy $a_FormContent. Następnie wywołując wcześniej opisaną metodę parse_data() sprawdza, czy dane są pełne i mają prawidłowy format. Jeśli parsowanie przebiegnie pomyślnie funkcja wywołuje prywatną metodę send_email() a zaraz potem prywatną metodę set_verify_section() (obie omówię później). W przeciwnym razie funkcja pobiera tablicę błędów $a_Errors wygenerowanych podczas walidacji danych. Jest to tablica asocjacyjna zawierająca pola o kluczach odpowiadających nazwom pól formularza, które nie przeszły pomyślnie walidacji.
W pierwszej kolejności sprawdzam czy pojawił się błąd związany z polem send. Pojawienie się takiego błędu oznacza, że użytkownik nie wcisnął przycisku ‘Send’ lecz ‘Clear’. W takim wypadku do tablicy $a_FormContent zostanie dodane pole z komunikatem informującym o wyczyszczeniu formularza. Jeśli użytkownik wcisnął klawisz ‘Send’ do tablicy $a_FormContent zostaną dodane wszystkie komunikaty o błędach parsowania danych. Dodatkowo zmieniam wartość pola send zmiennej $a_FormContent na zero, co można później sprytnie wykorzystać w kontrolerze.

public function receive_data(){
	$this->a_FormContent = $_POST;
	if ($this->parse_data()){
		$b_Send = $this->send_email();
		$this->set_verify_section($b_Send);
	}else{
		$a_Errors = $this->o_Post->errors();
		if(isset($a_Errors['send'])){
			$this->a_FormContent['info'][] = 'Wyczyszczono formularz';
		}else{
			$this->a_FormContent['info'] = $a_Errors;
		}
	}		
	return $this->a_FormContent;
}

Postanowiłam nie opisywać tu tego szczegółowo, gdyż takie informacje można znaleźć w dokumentacji, ale wszystkie komunikaty można sobie samemu zdefiniować.

Obiecałam, ze opiszę również prywatną metodę send_email(). Wysyłanie e-maili w Kohana jest proste, jeśli do pakietu instalacyjnego dołączy się narzędzie SwiftMailer i wykorzysta się helper Email. Wystarczy więc sprawdzić, czy w formularzu została wybrana opcja wysyłki kopi na adres użytkownika i w zależności od tego przygotować tabelę adresatów, lub pojedynczy adres odbiorcy. Potem wystarczy już tylko przekazać odpowiednie parametry metodzie send() helpera email i zwrócić otrzymaną wartość.

private function send_email(){
	if(isset($this->a_FormContent['copy'])) 
		$recipients = array('to'=>$this->a_FormContent['to'],
				'Cc'=>$this->a_FormContent['from']);
	else $recipients = $this->a_FormContent['to'];
	return email::send($recipients, $this->a_FormContent['from'], $this->a_FormContent['title'], $this->a_FormContent['message'], TRUE);
}

Żeby jednak ta funkcja miała prawo zadziałać należy jeszcze wpisać prawidłowe parametry do pliku konfiguracyjnego /config/email.php

No i na koniec omawiania tego modelu została prywatna metoda set_verify_section(). Przyjmuje ona jeden parametr tybu BOOL (ściślej mówiąc jest to wartość zwracana przez metodę send_email()) i w zależności od niego przygotowuje treść komunikatu. Następnie do tablicy $a_VerifySection zapisuje rekord w formacie takim jaki jest wymagany w widoku do wyświetlenia sekcji (patrz poprzedni tutorial).

private function set_verify_section($b_Sended){
	if($b_Sended){
		$text = 'To: '.$this->a_FormContent['to'].'
		Title: '.$this->a_FormContent['title'].'
		From: '.$this->a_FormContent['from'].'
		Message: '.$this->a_FormContent['message'];
		if(isset($this->a_FormContent['copy']))
		$text .='Kopia (Cc): '.$this->a_FormContent['from'];
	}else{
		$text = 'Message cannot be sended';
	}
	$this->a_VerifySection[] = array(
			'title'=>'Mailed information',
			'content'=>$text);
}

Zmiany w widoku głównym

W widoku głównym opisanym w poprzednim tutorialu należy w jednym miejscu dokonać drobnej modyfikacji. Poniżej kodu odpowiedzialnego za wyświetlanie sekcji danej podstrony dodajemy następujący fragment:

<?php if(isset($form_Contact))	echo $form_Contact;?>

Linijka ta odpowiada za wyświetlenie zawartości widoku formularza jeśli zostanie on przez kontroler dołączony, czyli przypisany do zmiennej $form_Contact opisanym w poprzednim tutorialu.

Zmiany w kontrolerze

Na koniec należy jeszcze przeprowadzić niezbędne zmiany w kontrolerze Home_Controller zdefiniowanym w pliku /application/controllers/home.php. Po pierwsze metoda set_content() została rozbita na dwie metody get_content() i set_content():

private function get_content($active,$i_Subactive){
	$m_Content = new Content_Model;
	$this->a_Content = $m_Content->get_content($active,$i_Subactive);
}
private function set_content($i_Subactive){
	$this->template->title = $this->a_Content['title'];
	$this->template->main_menu = $this->a_Content['main_menu'];
	$this->template->sub_menu = $this->a_Content['submenu'];
	$this->template->sub_title = $this->a_Content['submenu'][$i_Subactive]['name'];
	$this->template->a_Sections = $this->a_Content['sections'];
}

Wobec tego we wszystkich metodach gdzie wcześniej wywoływano metodę set_content() trzeba dla kompletu wywołać najpierw get_content(). Na przykład w metodzie index():

public function index($i_Subactive=0){
	$this->get_content(0,$i_Subactive);
	$this->set_content($i_Subactive);
}

Większym modyfikacjom należy poddać metodę contact(), gdyż chcemy aby formularz kontaktowy był wyświetlony na drugiej podstronie kategorii ‘Contact’. Dlatego też w funkcji tej musimy dac warunek na zmienną $i_Subactive w momencie, gdy będzie miała ona wartoć 1 zostanie wywołana prywatna metoda set_form_contact() odpowiedzialna za dołączanie widoku formularza do widoku głównego (o tej metodzie za chwilę). Ponadto dla zmodyfikowałam zawartość jednego pola tablicy $a_Content, która wcześniej została wygenerowana za pomocą metody get_content()

public function contact($i_Subactive=0){
	$this->get_content(4,$i_Subactive);
	if($i_Subactive == 1) $this->set_form_contact();
	$this->a_Content['submenu'][1]['name'] = 'Contact Form';
	$this->set_content($i_Subactive);
}

Metoda set_form_contact() korzysta z opisanego wcześniej modelu Contact_Model by pobrać za jego pomozą listę kontaktową. Następnie tworzy widok formularza i przypisuje go do zmiennej $form_Contact widoku głównego. Na koniec przekazuje do widoku formularza listę kontaktową pobraną z modelu.

private function set_form_contact(){
	$m_EmailList = new Contact_Model;
	$this->template->form_Contact = new View('t_form_contact');
	$this->template->form_Contact->a_Email = $m_EmailList->get_email_list();
}

Ostatnia zmiana jakiej trzeba dokonać w kontrolerze jest stworzenie metody send() na którą powołuje się link w formularzu kontaktowym. Metoda ta korzysta z omówionego wcześniej modelu Form_Contact_Model aby przetworzyć dane z formularza. Pobiera z modelu dane do tablicy $a_FormContactContent a następnie działa w zależności od zawartości tej tablicy. Jeśli tablica jest pusta, to oznacza, że z formularza nie przekazano absolutnie żadnych danych. Może się to wydarzyć jeśli ktoś ręcznie napisze w przeglądarce adres index.php/send/. W takich sytuacjach metoda send() zadziała dokładnie tak jak metoda contact(), czyli wyświetli pusty formularz kontaktowy. Jeśli zaś z formularza kontaktowego przekazano kompletne dane to zostanie wyświetlony komunikat (skonstruowany w modelu Form_Contact_Model) zawierający informację o tym, czy wysłanie e-maila się powiodło, czy też nie. Jeśli zaś odebrane dane były niekompletne lub też formularz został zresetowany następuje powrót do widoku formularza.

public function send(){
	$m_FormContact = new Form_Contact_Model;
	$a_FormContactContent = $m_FormContact->receive_data();
	if(!$a_FormContactContent){//nie odebrano danych z formularza
		$this->contact(1);
	}else{//odebrano dane z formularza
		if(isset($a_FormContactContent['send']) and $a_FormContactContent['send']){//dane kompletne
			$this->get_content(4,2);
			$this->a_Content['submenu'][1]['name'] = 'Contact Form';
			$this->a_Content['submenu'][2]['name'] = 'Verification';
			$this->a_Content['sections'] = $m_FormContact->a_VerifySection;
			$this->set_content(2);
		}else{//powrót do formularza
			$this->contact(1);
			$this->template->form_Contact->a_Post = $a_FormContactContent; 
		}
	}			
}

Na tym właściwie kończy się modyfikacja kodu naszej aplikacji związana z dodaniem prostego formularza kontaktowego. Nie omówiłam tu takich wodotrysków jak obsługa błędów walidacji, czy też automatyczne wypełnianie pól formularza po częściowej walidacji danych. Te zagadnienia zostawiam dociekliwym a może kiedyś napisze uzupełnienie. Działanie aplikacji po opisanych modyfikacjach można zobaczyć uruchamiając skrypt

13 komentarzy do wpisu „Prosty formularz kontaktowy”

  1. dobry temat, choć zawiera kilka błędów

    <?=form::label(‘message’, ‘Tresc wiadomosci: ‘))
    proszę poprawić na
    <?=form::label(‘message’, ‘Tresc wiadomosci: ‘);

    ‘message’, ‘rows’=>’15’, ‘cols’=>’50’);?>
    (syntax error, unexpected T_DOUBLE_ARROW) proszę poprawić na
    ‘message’, ‘rows’=>’15’, ‘cols’=>’50’)); ?>

    Pozdrawiam :)

  2. To nawet większy błąd niż się wydaje (mam na myśli ten drugi). Brakuje tam array(); Dzięki za uwagi. Pisać dla tak uważnego “studenta” to czysta przyjemność.

  3. no właśnie wpisałem array ale część kodu została usunięta jak sama widzisz z poprzedniej wiadomości. Zauważyłem także błąd odnośnie twojego rozdzielenia set_content i get_content zamiast poprzedniego _set_content. Otrzymywałem informacje z debuggera o pustych wartościach dla tablicy. Po zmodyfikowaniu metody kontrollera

    private function _set_content($i_Active,$i_Subactive){
    $m_Content = new Content_Model;
    $m_Content->get_content($i_Active,$i_Subactive);

    if($i_Active == 4 && $i_Subactive == 1) {
    $this->set_form_contact();
    $this->a_Content[‘submenu’][1][‘name’] = ‘Contact Form’;
    }

    $this->template->title = $m_Content->a_Content[‘title’];
    $this->template->main_menu = $m_Content->a_Content[‘main_menu’];
    $this->template->sub_menu = $m_Content->a_Content[‘submenu’];
    $this->template->sub_title = $m_Content->a_Content[‘submenu’][$i_Subactive][‘name’];
    $this->template->sections = $m_Content->a_Content[‘sections’];
    }

    wszystko działało poprawnie. Nie wiem dlaczego umieszczony przez panią fragment kodu nie zadziałał.

    Poza tym wszyściutko OK, parę mniejszych swoich modyfikacji wprowadziłem ale myśle że na oryginalną postać kodu nie miało to wpływu. Bardzo dobra jakość tutoriala, oczekuję na kolejne i pozdrawiam

  4. Warunek, który dodałeś jest tak naprawdę załatwiony w metodzie contact(). Rozumiem, że Ty chciałeś wywołać formularz kontaktowy wprost z metody index() no to wtedy faktycznie nie zadziałało. Ten tutorial został napisany w oparciu o wcześniejszy w którym dla każdej podstrony była osobna metoda. Jeśli chcesz, tak jak w kolejnym tutorialu zrobić jedną uniwersalną metodę index() i wczytywać zawartość w zależności od wartości dwóch parametrów (wskazujących na stronę i na podstronę), to faktycznie albo w get_contact() albo w index() musisz zrobić warunek na dodanie tego formularza :)

  5. Witam,
    na wstępie dziękuję za niezwykle przydatny tutorial!

    Miałem ten sam problem, który opisywał poprzednik i nie wywołałem formularza z metody index(), lecz tak jak Ty proponowałaś. Rozwiązałem to jeszcze inaczej, pozostawiłem metodę _set_content(), tak jak była, natomiast wywołanie formularza dodałem do metody contact(). Nie wiem na ile to profesjonalne, ale działa wyśmienicie.

    Mam również inny problem – z wysyłaniem kopii maila. Za pomocą helepra Email nie można ustawić adresu zwrotnego i podejrzewam, że tu jest przyczyna. Wysyłam przez SMTP i adres wpisany w formularzu, jest trkatowany jako adres, z którego faktycznie wysyłany jest mail (podczas gdy na prawdę idzie on z mojego konta e-mail). Na niektórych serwerach wywołuje to błąd SPF (np. o2.pl, wp.pl) i zamiast otrzymać kopię maila, wysyłający otrzymuję informację MAILER_DAEMON. Czy spotkałaś się z tym problemem? Pozdrawiam!

  6. Nie bardzo rozumiem Twój problem. Chodzi ci o prawidłowy adres zwrotny w nagłówku maila, czy o wysyłanie kopii e-maila na jakiś dodatkowy adres?

  7. Tak, chodzi o adres zwrotny w nagłówku maila. I w zasadzie nie tylko zwrotny ale generalnie prawidłowy adres FROM. Dzieje się coś takiego, że gdy na skrzynkę w o2.pl trafia mail wysłany niby z tego adresu, ale tak naprawdę pochodzi on z innego serwera (np home.pl), serwer o2 uznaje to za nadużycie i informuje o tym użytkownika. Gdyby przekazać adres z formularza jako zwrotny, a nie podszywać się, to problemu by prawdopodobnie nie było.

  8. Powiem szczerze, że zbaraniałam. W formularzu który tu omawiam jest pole do wpisania adresy zwrotnego, które to pole jest wykorzystane w funkcji send helpera email, jako drugi parametr ustawiający właśnie watrość ‘from’ i ‘odpowiedz do’ w nagłówku wiadomości

    email::send($recipients, $this->a_FormContent[‘from’], $this->a_FormContent[‘title’], $this->a_FormContent[‘message’], TRUE);

    Jeśli nie przewidujesz tego pola w formularzu to ustawiaj wartość tego parametru jakoś inaczej.

    A może ja nie rozumiem w czym tkwi Twój problem?

  9. Chyba nie rozumiesz ;) Nie będę już mieszał, bo może to jakiś zupełnie odosobniony przypadek. Tak jak mówisz, helper ustawia nagłówki ‘from’ oraz ‘reply-to’. W momencie kiedy SwiftMailer wysyła kopię maila do osoby, która wypełniła formularz, w nagłówku ‘from’ jest jej adres, ale wiadomość pochodzi z innego serwera, co jest traktowane jako nadużycie w postaci podszywania się innego nadawcy pod dany adres. Tu opisany jest cały problem: http://www.openspf.org/Introduction

    Mi chodzi o to, żeby adres podany w formularzu ustawić jedynie jako ‘reply-to’. Zdążyłem już dowiedzieć się, że za pomocą helpera jest to niemożliwe, zatem użyję bezpośrednio SwiftMailera.

    Przepraszam za wprowadzenie zamętu i pozdrawiam!

  10. Nie nie. To nie żaden zamęt. To cenna informacja. Nie wiedziałam, że taki problem występuje. Teraz to wszystko jasne :) No i dzięki za linka :)

  11. Dzikie za przystępne przedstawienie tematu jednak miałem problem z jedną linijką w funkcji parse_data():
    $this->o_Post->add_rules(‘formID’, ‘required’, array(‘valid’,’alpha_numeric’));
    dopiero po odkomentowaniu jej walidacja formularza przeszła mi pomyślnie. Nie znalazłem też w formularzu zdefiniowanego elementu o nazwie formID więc jaki sens miało jej stosowanie?

    Podobna sprawa z tym warunkiem?
    if(!$a_FormContactContent)

    …i już ostatni problem gdzie wykorzystywana jest zawartość tablicy $this->a_FormContent[‘info’] tworzonej w metodzie receive_data()?

    Przepraszam za bałagan, bardzo proszę o scalenie komentarzy gdyż niestety nie udostępniono funkcji edycji.

  12. z tym formID to słuszna uwaga, muszę to poprawić w treści. Po prostu brakuje tego elementu w kodzie formularza więc walidacja, nie mogła się udać :)

    formID wykorzystuję jako unikalny identyfikator formularza (hidden), który pozwala mi zapobiegać wielokrotnemu przetwarzaniu.

    Nie bardzo rozumiem co jest nie tak z warunkiem: if(!$a_FormContactContent)

    Co do $this->a_FormContent[‘info’] to jest on wykorzystany przy po przeładowaniu formularza. Zawarty tam string można wyświetlić, żeby poinformowac usera czy wysyłanie formularza siępowiodło czy nie.

Leave a Reply

%d bloggers like this: