Formuláře v PHP - ošetření odesílaných dat: díl II

 |  PHP: Základy  |  53 075x
_

Další z článků, který po letech potřebuje oprášit jsou Formuláře v PHP - ošetření odesílaných dat. Byť samotné kontroly zůstávají pořád stejné, způsob jejich realizace se po letech vyvinul. Dříve jsme zobrazovali chybová hlášení všechna po kupě někde nad formulářem, pomocí JavaScriptového alertu nebo v případě začátečnické realizace samostatně na externí stránce. Dnes uživatelé vyžadují větší pohodlí a intuitivnější chování webových aplikací, a tak zobrazujeme chybová hlášení většinou hned vedle daného políčka. Jak jsem už ale zmínil, princip kontrol není třeba měnit. Článek "Je čas udělat Wordpressu pápá" se datuje na 1. 10. 2007, tehdy jsem přešel na vlastní redakční systém, kde používám úplně stejné kontroly v komentářích. A od té doby jsem nezaznamenal jediný spam - tedy spam od neživého návštěvníka. Základní prvky ochrany tedy fungují stále dobře a je na čase si je připomenout.

Ukázkový formulář

<? if(!empty($_GET['sent'])){ ?>
	<p class="success">Formulář byl úspěšně odeslán</p>
<? } ?>

<form action="<?=$_SERVER['REQUEST_URI'];?>" method="post">
	<fieldset>
		<legend>Přidej komentář</legend>
		
		<label>Jméno <b>*</b></label><br />
		<input type="text" name="jmeno" value="<?=(isset($_jmeno) ? $_jmeno : '');?>" /><br />
		<?=(!empty($error_messages['jmeno']) ? '<div class="error">'.$error_messages['jmeno'].'</div>' : ''); ?>
		
		<label>E-mail <b>*</b></label><br />
		<input type="text" name="web" value="<?=(isset($_email) ? $_email : '');?>" /><br />
		<?=(!empty($error_messages['email']) ? '<div class="error">'.$error_messages['email'].'</div>' : ''); ?>
		
		<label>Web</label><br />
		<input type="text" name="email" value="<?=(isset($_web) ? $_web : '');?>" /><br />
		<?=(!empty($error_messages['web']) ? '<div class="error">'.$error_messages['web'].'</div>' : ''); ?>
		
		<label>Vaše zpráva <b>*</b></label><br />
		<textarea name="text" rows="4" cols="10"><?=(isset($_text) ? $_text : '');?></textarea><br />
		<?=(!empty($error_messages['text']) ? '<div class="error">'.$error_messages['text'].'</div>' : ''); ?>
		
		<span class="schovany">
			<label>Tohle políčko ponechte prázdné</label><br />
			<input type="text" name="city" /><br />
			<?=(!empty($error_messages['check1']) ? '<div class="error">'.$error_messages['check1'].'</div>' : ''); ?>
		</span>
		
		<label>Kontrola: Opište číslici pět <b>*</b></label><br />
		<input type="text" name="skype" /><br />
		<?=(!empty($error_messages['check2']) ? '<div class="error">'.$error_messages['check2'].'</div>' : ''); ?>
		
		<button name="submit" type="submit">Odeslat</button><br />
	</fieldset>
</form>

Takhle nějak by mohla vypadat základní kostra formuláře. Doporučuji samozřejmě i atributy FOR a ID pro labely a inputy (popřípadě obalit input labelem). Třídu "schovany" nastylujte na display:none. Pokud se na kód podíváte blíže, můžete si všimnout hned tří úrovní ochrany proti spamu, které za chvíli rozeberu. Další dvě úrovně ochrany pak budou schované už v obsluze odesílacího skriptu.

Zpracování dat po odeslání, krok 1: Základní ošetření

Formulář budeme posílat na stejnou url, na které je, takže zpracování odeslaných dat musí proběhnout ještě před zobrazením jakéhokoli HTML kódu. V případě jednoduchého formuláře, na kterém kontrolu předvádím můžeme začít například takto:

<?php
if(isset($_POST['submit'])){
	$_jmeno = htmlspecialchars(trim($_POST['jmeno']));
	$_email = htmlspecialchars(trim($_POST['web']));
	$_web   = htmlspecialchars(trim($_POST['email']));
	$_text  = htmlspecialchars(trim($_POST['text']));
	
	$_check1 = $_POST['city'];
	$_check2 = htmlspecialchars(trim($_POST['skype']));

htmlspecialchars() a trim() je vše, co k ošetření vstupu potřebujete. Při ukládání do databáze je samozřejmě potřeba myslet na escape uvozovek, ale o databázi se v dnešním článku bavit nebudeme. U větších formulářů pak tato kontrola bude napsaná úplně jinak, ale pro začátek je tato ukázka víc než dostatečná. Ukončující závorku záměrně neuvádím, protože v bloku kódu budeme pokračovat.

Zpracování dat po odeslání, krok 2: Antispam

Na pořadí kroku 2 a 3 samozřejmě nezáleží, já zvolil jako první antispam. Jak nahoře ve formuláři, tak zde v prvních pár řádcích PHP jste si mohli všimnout, že jména pole e-mail a web jsou prohozená. To je první past na roboty. Pokud v poli email bude zavináč, vypíšeme chybu a formulář nezpracujeme. Dále zkontrolujeme odpověď na kontrolní otázku a hodnotu skrytého pole: pokud je vyplněno, opět se jedná o robota. Roboti totiž zpravidla vyplní každé pole formuláře nějakými bláboly - a my si proto řekneme, že právě jedno z polí musí být prázdné. Robot ho vyplní a skončil.

Skrytému poli je také dobré dát nějaký výstižný název, na který se roboti nachytají. V mém případě "city", můžete ale uvést i názvy "email_here" nebo cokoli jiného. I kontrolní pole samo o sobě má název, který láká na vyplnění všeho jiného jen ne jednomístného čísla.

Prohození názvů polí samozřejmě můžeme vynechat. V případě, kdy generujeme celý formulář z databáze a atribut name má hodnoty ve tvaru "input_25", "input_26" a podobně, budou nám bohatě stačit dvě antispamová pole pojmenovaná "email", "skype" či v podobném duchu.

Dále provedeme kontrolu zakázaných slov a počet hypertextových odkazů v těle zprávy. Zakázaná slova můžeme mít uložená v databázi nebo jenom staticky v proměnné, na tom nezáleží. Na čem ale záleží je uvedení těch správných frází, které se často vyskytují ve spamu. Pokud takové slovo vloží sám uživatel, dostane chybové hlášení, slovo smaže a příspěvek může odeslat. Robot ale bude ztracen.

	// antispam
	$evil_words = array('[url', 'viagra', 'website');
	foreach($evil_words as $word){
		if(strpos($_text, $word) !== false){
			$error_messages['text'] = 'Zpráva obsahuje některé ze zakázaných slov: '.implode(', ', $evil_words);
		}
	}
	
	if(substr_count($_text, 'http://') > 5 ){
  		$error_messages['text'] = 'Více než pět hypertextových odkazů není povoleno.';
  	}
  	
  	if(strpos($_web, '@') !== false){
		$error_messages['web'] = 'Jste spam, prosím nebuďte spam.';
	}
	
	if(!empty($_check1)){
		$error_messages['check1'] = 'První kontrolní pole ponechte prázdné.';
	}
	
	if($_check2 != 5){
		$error_messages['check2'] = 'Chybně opsaný kontrolní kód.';
	}

Zpracování dat po odeslání, krok 2: Kontrola uživatelských dat

Po prvním bloku kontrol následuje druhý, kde už jenom zkontrolujeme, zda-li jsou vyplněna všechna pole a jestli mají správný formát. Abychom ošetřili samotné tělo zprávy proti dlouhým řetězcům bez mezer, přidáme poslední blok kódu, které rozdělí slova delší než 50 znaků. Pokud je vše v pořádku (proměnná $error_messages nebyla ani jednou naplněna), můžeme provést uložení formuláře a redirect.

	// kontrola uzivatelskych dat
	if(empty($_jmeno)){
		$error_messages['jmeno'] = 'Pole Jméno je povinné.';
	}
	
	if(!preg_match('/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/', $_email)){
		$error_messages['email'] = 'Pole E-mail má chybný formát.';
	}

	if(empty($_email)){
		$error_messages['email'] = 'Pole E-mail je povinné.';
	}
	
	if(empty($_text)){
		$error_messages['text'] = 'Pole Vaše zpráva je povinné.';
	}else{
		$text_array  = explode(' ', $_text);
		$text_count  = count($text_array);
		$text_return = NULL;
		
		for($i = 0; $i <= $text_count-1; $i++){
		    $text_array[$i] = wordwrap($text_array[$i], 50, ' ', 1); 
		    $text_return .= $text_array[$i].' '; 
		}
		
		$_text = $text_return;
	}
	
	// uložení dat do DB, odeslání na email + přesměrování
	if(empty($error_messages)){
		$current_url = $_SERVER['REQUEST_URI'];
		$current_url.= (strpos($current_url, '?') === false ? '?sent=1' : '&sent=1');
		
		header('Location: '.$current_url);
		echo '<meta http-equiv="refresh" content="1;url='.$current_url.'" />';
		exit;
	}
} // konec if(isset($_POST['submit']))

Vypsání chyb zpět do formuláře

Jak jste si mohli všimnout v ukázce výchozího HTML, formulář už je na chystaný na zpětný výpis chyb. Pokud bude naplněn některý z klíčů proměnné k tomu určené, na daném místě se vypíše. Pokud bude u některého pole zaznamenáno více chyb, vypíše se vždy jen poslední zaznamenaná: není nutné vypisovat všechny, uživatel formulář odešle a dostane nové chybová hlášení. Když například zapomenu vyplnit e-mail, stačí napsat, že e-mail není vyplněn. Nemusím ještě číst další řádek o tom, že pole neodpovídá regulárnímu výrazu.

Bonus: GEO IP ochrana

Ochrana před přístupy z některých vyjmenovaných zemí je sice věc, která by měla být řešená na úrovni PHP (abychom mohli návštěvníkovi vypsat alespoň hlášení, že je ve špatné lokalitě a celé to mohlo být administrovatelné), ovšem pro malé weby cílené na menší region je tohle vcelku zbytečné. IP adresy některé exotických zemí bývají často využíváné spammery, takže zákazem přístupu z dané země můžeme velice jednoduše přidat další úroveň ochrany. Stačí do souboru .htaccess vložit následující řádky, popřípadě je rozšířit o další domény.

# soubor .htaccess
Deny from .ru # rusko
Deny from .ua # ukrajina
Deny from .cn # cina
Deny from .in # indie
Deny from .sa # saudska arabie

Ukázka

Formulář
Zdrojový kód

Facebook Twitter Google+

Komentáře k článku "Formuláře v PHP - ošetření odesílaných dat: díl II"

Gravatar
Dan 8. 10 2007, 00:06
1/25 Pondělí 8. Října 2007, 00:06  |  Firefox, Windows XP

Perfektní článek! Hned zítra to vyzkouším %2. Jen mám jednu připomínku...když má slovo 20 a víc znaků, hodí to tam pomlčku...jenže pokud člověk z masa a kostí vkládá nějaký www odkaz, čili www.jehostranka.cz/i-ndex.php?stranka=zaj-imavost_na_webu tak ten odkaz vložená pomlčka "rozbije"-; a odkaz je pak nefunkční....což nám tvůrcům webů dojde, ale běžnej uživatel si nevšimne, že mu nejde odkaz kvůli pomlčce..

Gravatar
Mike 8. 10 2007, 02:26
2/25 Pondělí 8. Října 2007, 02:26  |  Opera, Windows XP

Dan: díky %0 s rozbitím odkazu máš pravdu, tady mi to taky dělá...

napadaj-í mě dvě řešení: první možná složitější, a to další testování každého slova regulárním výrazem + následné rozdělení / nerozdělení pokud se nejedná / jedná o odkaz.

druhá varianta je jednodušší - zde jsem uvedl 20 znaků jako příklad. nějak takto: vezmeš nejdelší znak u bezpatkového písma, takže asi M, a prostě si spočítáš, kolik jich může být vedle sebe, aniž by to rozhodilo lay-out. jenom tady se mi jich vleze přes 40, respektive přes 60 různě velkých znaků průměrně (0.7em Lucida Grande), a tak dlouhé odkazy snad už nikdo posílat nebude %0 ale první způsob je samozřejmně lepší...

Gravatar
Mazlík 8. 10 2007, 17:34
3/25 Pondělí 8. Října 2007, 17:34  |  Opera, Windows 2000

Já když odesílám data z formuláře, tak to posílám na stejnou uri (action="&quo-t;) a na začátku scriptu udělám kontrolu dat + sql dotaz a pakud je vše ok, tak udělám ještě refresh pomocí header(). V opačném případě si do proměnné $error uložím chybovou hlášku a pak jí u formuláře vypíšu.

A má to dost jednu velkou výhodu - uživatel třeba zapomene zadat nějaký povinný údaj či udělal jinou chybu a může formulář vyplňovat znova. U mě se zobrazí formulář znova i se zadanými údaji :-) U formuláře teda musím udělat třeba echo '<input value="'.$_PO-ST['nazev_inputu']-'">'

M-ísto header("Locati-on: zpatky na clanek") píše použí

Gravatar
Mike 8. 10 2007, 19:19
4/25 Pondělí 8. Října 2007, 19:19  |  Opera, Windows XP

[3] Mazlík: Přesně tak to dělám také %0 (i když zde zrovna ne...) Jenže jako úvod do problematiky se mi zdálo jednodušší vysvětlit běh externího skriptu, než sekvenci X mnoha ifů / elseifů...

O zachování vyplněného formuláře se postará návrat v historii přes odkaz v javascriptu self.history.back();

Gravatar
Petr "Pe3ček" Loukota 10. 10 2007, 18:25
5/25 Středa 10. Října 2007, 18:25  |  Firefox, Windows Vista

Ahoj Miku,

užitečný článek. %5 Také jsem přemýšlel o nějaké té kontrolní otázce proti robotům, ale používám nějaký antispamový plug-in (já jsem stále na WP) a spamy mi vůbec nechodí, takže jsem do toho zatím nešel.
Jinak pokud bys měl chuť, hodila by se mi nějaká PHP šifra (já totiž PHP neumím psát, pouze částečně číst), která by na jedné stránce dovolila např. pouze 50 komentářů. Při překročení by se vytvořila nová strana, na které by zůstal článek a jen komentáře pod ním by byly další. Primárně by se samozřejmě zobrazovala strana číslo jedna, dále by se vybralo kliknutím na číslo určité stránky.
Docela by se mi to hodilo, ale vůbec to nespěchá. Vím, že to není otázka dvou minut, jen kdybys prostě měl náladu, víš jak. %1

Gravatar
Mike 10. 10 2007, 19:24
6/25 Středa 10. Října 2007, 19:24  |  Opera, Windows XP

zdarec, thnks %0

asi takhle... nic těžkého to není. jedná se o klasické stránkování, stejné, jako je na úvodní stránce u článků. ve vlastním systému bych to za ty dvě minuty aj hotový měl %4 ale u WP by to znamenalo poněkud složitější zásah do systému. (ale ne nereálný)

plánuji napsat krátkej článek "Upgrade a zrychlení Wordpressu" a ty si mi dal právě další námět %0 něco vymyslím, i když říkám: nezávislý skript jednoduchý, implementace do WP složitější.

ale zkusím, uvidím %1

Gravatar
Onecar 11. 10 2007, 19:05
7/25 Čtvrtek 11. Října 2007, 19:05  |  Opera, Windows XP

Opět a zas script, který se ani nepozastavuje nad možností, že spamovat bude člověk z masa a kostí se špetkou znalosti PHP a CURL :-). Pověz, co kdybych napsal script, který bude takto vyplňovat pole daty, která projdou? Nezastaví mě nic, odešlu statisíce dat a zahltím celou databázi :-).

Doporučuji zkouknout toto:

http://jednoauto.com/blog/?text-=93-bezpecnost-blogu-je-takrka-nulova

Gravatar
Mike 11. 10 2007, 22:50
8/25 Čtvrtek 11. Října 2007, 22:50  |  Opera, Windows XP

[7] Onecar: ten článek jsem četl %0

máš pravdu, proti tomu, co uvádíš jedině captcha. tohle proti robotům ale funguje bezproblémově, navíc skript je psaný spíše pro začátečníky, takže tomu také odpovídá náročnost.

Gravatar
vydělej si 23. 10 2007, 16:52
9/25 Úterý 23. Října 2007, 16:52  |  Opera, Windows XP

PRO VLASTNÍKA STRÁNEK - Máš vlastnní webové stránky, které mají dobrou náštevnost a chtěl(a) bys aby ti tvoje stránky vydělali nějakou korunu ? Pokud Ano ,doporuču se podívat na http://*** kde můžete využít provizní system , kde budete získávat penize aniž by jste museli něco dělat ,stačí na svoje stránky umistít banner(několik na výběr) a čekat až si lidi budou dělat IQ test a zakaždého člověka tak získaš určitou provizi ,
viz na http://*** ;) p.s. doporučuju vám test si udělat, at víte o čem to je ;) (www.***)

Gravatar
Mike 23. 10 2007, 17:13
10/25 Úterý 23. Října 2007, 17:13  |  Opera, Windows XP

vydělej si: "Máš vlastnní webové stránky" : nemám, tyhle mi pučila babička na dobu neurčitou.

"které mají dobrou náštevnost" : vypni si styly, uvidíš toplist %4

"chtěl(a)" : viz patička stránek: Copyright 2007 by Michal Sobola - zjevně jméno mužské.

asi sem dám těžší kontrolní otázku...

Gravatar
Pavel 3. 3 2008, 17:14
11/25 Pondělí 3. Března 2008, 17:14  |  Firefox, Windows XP

Co takhle udělat mailform s touto ochranou, mohl bys udělat článek, jak na to ???
PS: Taky by se hodilo vyhledávání na vlastním webu, všude to je jen přes google, ale z vlastních stránek nikde nic.

Gravatar
Mike 3. 3 2008, 17:53
12/25 Pondělí 3. Března 2008, 17:53  |  Opera, Windows XP

psát návod na mailform v plánu nemám, abych řekl pravdu. jestli chceš odesílat e-maily, tak sačí na konec odesílacího skriptu přidat funkci mail ( http://cz.php.net/mail ) s patřičnými parametry, umazat políčko web a máš mailform %1

a co se týče vyhledávání, návod na nějaké jednoduché bych samozřejmě napsat mohl, možná i někdy napíšu... (i když po netu je návodů spousta) ovšem řekl bych, že třeba pro tebe by bylo vyhledávání asi nepoužitelné. je nutné mít veškerý obsah webu v databázi, ne v souborech. a hledání v inkludovaných souborech? podle mého názoru nesmysl.

Gravatar
Pavel 3. 3 2008, 18:20
13/25 Pondělí 3. Března 2008, 18:20  |  Firefox, Windows XP

Aha, díky za tip a rady, tak nějak jsem si to myslel (s tim vyhledáváním). Abych pravdu řekl, tak jsem moc nehledal ...

Gravatar
Péťa 27. 3 2008, 13:24
14/25 Čtvrtek 27. Března 2008, 13:24  |  Firefox, Ubuntu

Ta validace e-mailu je ovšem poněkud zanedbaná :-D

Gravatar
Mike 27. 3 2008, 13:34
15/25 Čtvrtek 27. Března 2008, 13:34  |  Opera, Windows XP

[14] Péťa: decánko %5 ale já to beru takhle... pokud někdo ten mejl zadat nechce, tak ho prostě nezadá. a ani sebemenší kontrolou mu nezabráníš napsat mejl ve stylu nesmysl@nesmysl.cz

navíc se nejedná o žádnou důležitou akci, jako třeba registraci : http://mike.treba.cz/prihlasovani-a-registrace-uzivatelu-pomoci-sessions/ tam to mám už ošeštřený trochu líp %1

Gravatar
Majk-X 2. 4 2008, 10:51
16/25 Středa 2. Dubna 2008, 10:51  |  Firefox, Windows XP

[10] Mike: On to myslel asi tak, že proti TĚMHLE spamům tě asi nic neuchrání. %4

Gravatar
johnny 18. 11 2008, 18:20
17/25 Úterý 18. Listopadu 2008, 18:20  |  Opera, Windows XP

udělal jsem vše jak jsem měl, ale ulozeni informaci do souboru a nasledny vypis souboru na stranku pod navstevni knihu nechapu..:( pomoc prosim

Gravatar
Mike 19. 11 2008, 09:33
18/25 Středa 19. Listopadu 2008, 09:33  |  Opera, Windows XP

[17] johnny: ahoj, v prvé řadě by mi pomohla ukázka tvého kódu, abych viděl, jakým způsobem to děláš : při ukládání do textového souboru je potřeba zvolit nějaký způsob, jakým se budou příspěvky oddělovat - zpravidla to bývá nový řádek. záleží také na tom, v jaké formě data ukládáš - pouze textově, s html, jako xml...

ve všech případech je ale základ stejný: načíst soubor (funkce file_get_contents / simplexml_load_file) - rozřezání na jednotlivé příspěvky (explode, unserialize...) spočítání příspěvků (count) a následný výpis.

moc toho ale o tvé návštěvní knize nevím, takže zatím mohu jen hádat...

Gravatar
David 2. 2 2011, 17:46
19/25 Středa 2. Února 2011, 17:46  |  Opera, Windows XP

Tak jsem to trošku zkrátil a vylepšil o tu kontrolu odkazů - to zalamování textů.

$slova = explode(' ', $text);
$text = null;
foreach ($slova as $slovo)
{
if (!preg_match('#^(http|ftp|https)?://[a-z0-9-_.]+\.[a-z]{2,4}#i',$slovo)) {
$text.=wordwrap($slovo, 20, "-", 1).' ';
} else {
$text.='<a href="'.$slovo.'">'.wordwrap($slovo, 20, "-", 1).'</a> ';
}
};
echo $text;

- popřípadě je to tu: http://pastebin.com/R6ZKr1HW

Gravatar
Minor 8. 7 2011, 18:47
20/25 Pátek 8. Července 2011, 18:47  |  Chrome, Windows 7

ahoj, já osobně to řeším tak, že mi nikdo nemůže pracovat s funkcema, který postujou data, pokud ten někdo nemá povolené cookies +....(princip tady rozepisovat nebudu, jistě chápete proč :)), což se dá lehce a 100% ověřit, standardní uživatel je má povolené vždy... moje metoda je celkem velmi účinná a nepotřebuju dávat ochranné prvky typu captcha což akorát hodně zdržuje a štve poctivé uživatele, leckdy ten kód vyluštit dá hodně zabrat(ano i lidem) a nepovede se to hned na poprvé...

Gravatar
Mike 25. 7 2011, 14:32
21/25 Pondělí 25. Července 2011, 14:32  |  Opera, Windows Vista

@: Souhlasím s captchou, je to hrůza, sám ji nemám rád. Ale roboti bohužel s cookies pracovat umí. Vždy je proto lepší použít více vrstev ochrany než jen jednu.

Gravatar
Minor 1. 8 2011, 00:27
22/25 Pondělí 1. Srpna 2011, 00:27  |  Chrome, Windows XP

Pokud robot bude pracovat s cookies, tím líp, stačí nastavit cookies po odeslání vzkazu, registraci..., která bude platná třeba 5 minut a dát podmínku do skriptu - pokud cookies existuje, odepřít odeslání vzkazu...

Gravatar
hans2 10. 2 2015, 11:22
23/25 Úterý 10. Února 2015, 11:22  |  Firefox, Windows 7

Oprav si tld u Ukrajiny...správně je .ua
A pokud jde o ten .htaccess...myslím, že pokud nejde o hosting, pak je lepší to řešit na úrovni iptables...takové pakety nemusí pak zpracovávat až Apache.

Gravatar
Mike 10. 2 2015, 12:01
24/25 Úterý 10. Února 2015, 12:01  |  Opera, Windows 8.1

díky za komentář, ukrajinu jsem si opravil. je pravda, že deny from .doména není úplně ideální, zbytečně to zatěžuje apache. no měl bych to tam někam dopsat jako poznámku. někdy ale zoufalé situace vyžadují zoufalá řešení, že ano.

Gravatar
PCH 17. 8 2015, 22:01
25/25 Pondělí 17. Srpna 2015, 22:01  |  Chrome, Windows 8.1

To s lákadlem na roboty a poli nazvanými -email- a pod funguje hezky. Používám to cca rok na www.c64.cz a ani jeden robotí záznam :)

Přidat komentář







Nevím, kolik to je
Parak simati, Muballit mitte, Nergal allatu mellamu mesaru, La tapallah Annuaki, Kettu Puluthu qillatua