Pokud chceme zkontrolovat platnost odkazů na stránkách případně navštívit všechny stránky z důvodu kontroly chyb zapisovaných do logu, máme k dispozici několik nástrojů. Například on-line od W3C nebo desktopovou aplikaci Xenu's Link Sleuth. Tyto nástroje ale ignorují formuláře, protože jejich odeslání často způsobí provedení nějaké akce – zápis do databáze, odeslání e-mailu a podobně. Také pochopitelně neví, co do formuláře mají vyplnit.
Někdy se ale může hodit formuláře odeslat alespoň s výchozími hodnotami – pro kontrolu v testovacím prostředí, kde provedení akcí nevadí, nebo třeba pro automatické zpracování cizích formulářů. Ke zjištění názvů a předvyplněných hodnot polí ve formuláři se dá použít následující funkce:
<?php /** Vrácení všech dat odeslaných z formuláře * @param string vnitřek značky <form> * @param string které tlačítko se má použít pro odeslání * @return array ("name=value", ...) * @copyright Jakub Vrána, https://php.vrana.cz/ */ function form_fields($form, $submit = "") { static $token = '(?:"([^"]*)"|\'([^\']*)\'|([^\\s>]*))'; $return = array(); preg_match_all('~<input([^>]*\\sname=' . $token . '[^>]*)~i', $form, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE); foreach ($matches as $val) { $name = $val[2][0] . $val[3][0] . $val[4][0]; if (!preg_match('~\\stype=["\']?(reset|file|button)~i', $val[1][0]) && (!preg_match('~\\stype=["\']?submit~i', $val[1][0]) || $name == $submit) && (!preg_match('~\\stype=["\']?(checkbox|radio)~i', $val[1][0]) || preg_match('~\\schecked~', $val[1][0])) ) { $value = (preg_match("~\\svalue=$token~i", $val[1][0], $val2) ? urlencode(html_entity_decode("$val2[1]$val2[2]$val2[3]")) : ""); $return[$val[0][1]] = urlencode(html_entity_decode($name)) . "=$value"; if (preg_match('~\\stype=["\']?image~i', $val[1][0])) { $return[$val[0][1]+1] = urlencode(html_entity_decode("$name.x")) . "=1"; $return[$val[0][1]+2] = urlencode(html_entity_decode("$name.y")) . "=1"; } } } preg_match_all('~<textarea[^>]*\\sname=' . $token . '[^>]*>(.*?)</textarea>~is', $form, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE); foreach ($matches as $val) { $name = $val[1][0] . $val[2][0] . $val[3][0]; $return[$val[0][1]] = urlencode(html_entity_decode($name)) . "=" . urlencode(html_entity_decode($val[4][0])); } preg_match_all('~<select[^>]*\\sname=' . $token . '[^>]*>(.+?)</select>~is', $form, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE); foreach ($matches as $val) { $name = $val[1][0] . $val[2][0] . $val[3][0]; if (preg_match_all('~<option([^>]*\\sselected[^>]*)>([^<]*)~', $val[4][0], $options, PREG_SET_ORDER)) { foreach ($options as $key => $option) { $value = (preg_match("~\\svalue=$token~i", $option[1], $val2) ? "$val2[1]$val2[2]$val2[3]" : $option[2]); $return[$val[0][1]+$key] = urlencode(html_entity_decode($name)) . "=" . urlencode(html_entity_decode($value)); } } elseif (preg_match('~<option([^>]*)>([^<]*)~', $val[4][0], $option, PREG_SET_ORDER)) { // v souladu s chováním prohlížečů se vezme první možnost $value = (preg_match("~\\svalue=$token~i", $option[1], $val2) ? "$val2[1]$val2[2]$val2[3]" : $option[2]); $return[$val[0][1]] = urlencode(html_entity_decode($name)) . "=" . urlencode(html_entity_decode($value)); } } ksort($return); return $return; } // použití $content = implode("&", form_fields(preg_replace('~.*<form[^>]*>|</form>.*~', '', $file))); ?>
Ve formulářích mohou být tři druhy polí: <input>
, <select>
a <textarea>
. Špatně napsané aplikace mohou očekávat předání těchto polí ve stejném pořadí, v jakém jsou ve formuláři, což lze zajistit dvěma způsoby: Buď se všechny značky budou hledat najednou (což povede k nepřehlednému regulárnímu výrazu ale zároveň o něco rychlejšímu skriptu) nebo postupně s tím, že se k nim uloží i pozice v dokumentu (skript k tomu používá modifikátor PREG_OFFSET_CAPTURE), podle které se výsledek nakonec setřídí.
Nesmíme zapomenout na správné escapování – ošetření dat z dokumentu odstraníme funkcí html_entity_decode a pro přenos v URL je naopak ošetříme funkcí urlencode. Některá pole přenášet nechceme vůbec – soubory, gumovací tlačítka a ani odesílací tlačítka, protože z těch může uživatel vybrat maximálně jedno (funkce ho přijímá jako volitelný parametr).
Funkce je poměrně nepřehledná a plná hacků, navíc ji zmate např. HTML kód value="a checked"
. K přesnějšímu chování a ke zpřehlednění je nutné formulář rozebrat na vyšší úrovni, v PHP 5 např. knihovnou DOM:
<?php /** Vrácení všech dat odeslaných z formuláře * @param DOMNode element <form> * @param string které tlačítko se má použít pro odeslání * @return array ("name=value", ...) * @copyright Jakub Vrána, https://php.vrana.cz/ */ function form_fields_dom($form, $submit = false) { $return = array(); foreach ($form->getElementsByTagName('*') as $child) { if ($child->hasAttribute('name')) { $name = urlencode($child->getAttribute('name')); switch ($child->tagName) { case 'input': switch (strtolower($child->getAttribute('type'))) { case 'reset': case 'file': case 'button': break; case 'checkbox': case 'radio': if ($child->hasAttribute('checked')) { $return[] = "$name=" . $child->getAttribute('value'); } break; case 'image': if ($submit && $child->getAttribute('name') === $submit) { $return[] = "$name.x=1"; $return[] = "$name.y=1"; } if (!$child->getAttribute('value')) { break; } case 'submit': if ($child->getAttribute('name') !== $submit) { break; } default: $return[] = "$name=" . $child->getAttribute('value'); } break; case 'select': $options = $child->getElementsByTagName('option'); $selected = array(); foreach ($options as $option) { if ($option->hasAttribute('selected')) { $selected[] = $option; } } if ($options->length && !$selected && (!$child->getAttribute('size') || $child->getAttribute('size') == 1)) { // v souladu s chováním prohlížečů se vezme první možnost $selected[] = $options->item(0); } foreach ($selected as $option) { $return[] = "$name=" . urlencode($option->hasAttribute('value') ? $option->getAttribute('value') : $option->textContent); } break; case 'textarea': $return[] = "$name=" . urlencode($child->textContent); break; } } if ($child->tagName == 'input' && strtolower($child->getAttribute('type')) == 'image' && !$child->getAttribute('name') && $submit === "") { $return[] = "x=1"; $return[] = "y=1"; } } return $return; } // použití $dom = DOMDocument::loadHTMLFile($filename); $content = implode("&", form_fields_dom($dom->getElementsByTagName('form')->item(0))); ?>
S takovouto funkcí je možné snadno postavit naznačené aplikace. Kromě toho se dá použít třeba i pro zpřístupnění formuláře pro hledání dopravního spojení pro rychlé hledání.
Diskuse je zrušena z důvodu spamu.