Kirjautuminen

Haku

Tehtävät

Keskustelu: Nettisivujen teko: Lomakkeen validointi

Sivun loppuun

latenleffahylly [19.12.2011 14:37:32]

#

Hei,

Minulla olisi kysymys, kiitos jos ehditte auttaa.

- Miten kannattaa tarkistaa, että lomakkeen <textarea> kohtaan kirjoitetaan korkeintaan 256 merkkiä?

Olen toistaiseksi saanut aikaan seuraavan koodin pätkän:

SCRIPTI 1:

<script type="text/javascript">

function validateForm()
{
var a=document.forms["myForm"]["asianumero"].value
var v=document.forms["myForm"]["viesti"].value;

                                if (a==null || a=="")
                                    {
                                    alert("Asianumero on pakollinen tieto!");
                                    return false;
                                    }
                                if (v==null || v=="")
                                    {
                                    alert("Viesti on pakollinen tieto!");
                                    return false;
                                    }
                            }
</script>

SCRIPTI 2:

<script type="text/javascript">
                            function maxlength(element, maxvalue)
                             {
                             var q = eval("document.myForm."+element+".value.length");
                             var r = q - maxvalue;
                             var msg = "Sorry, you have input "+q+" characters into the "+
                               "text area box you just completed. It can return no more than "+
                               maxvalue+" characters to be processed. Please abbreviate "+
                               "your text by at least "+r+" characters";
                             if (q > maxvalue) alert(msg);
                             }

</script>

LOMAKE:

<form method='post' action='' name="myForm" onsubmit="return validateForm()">
<table>

<tr><td>Asianumero (*)</td><td><input type='text' name='asianumero'></td></tr>

<tr><td>Viesti (*)</td><td><p>viestin pituus max. 256 merkkiä</td></tr>
<tr><td></td><td><textarea name='viesti' onchange="maxlength('viesti', 256)"></textarea></td></tr>

<tr><td></td><td><input type="submit" name="submit" value="Lähetä"</td></tr>

</table>
</form>

MUUTA:

- lomake on WordPressin "sisällä"

- Onko tarkistus riittävä? (tietoturva)

- kiitos ylläpidolle hienoista sivuista (huom! tämä on eka viestini, olen uusi kävijä)

Mod. lisäsi kooditagit! Ole hyvä ja lue keskustelun ohjeet!

Macro [19.12.2011 14:47:53]

#

Onko se linkki keskustelun ohjeisiin kirjoituskentän yläpuolella niin hankalassa paikassa, ettei sitä näe?

Javascriptillä maksimipituuden tarkistuksen voisi luoda näin.

function maxPituus(elementti, pituus) {
    if(elementti.value.length > pituus)
        elementti.value = elementti.value.substring(0, pituus);
}

Mitä sen riittävyyteen tulee, niin sanon, että se ei riitä. Voin ottaa selaimestani Javascriptin pois päältä, ja kenttään saakin kirjoitettua vaikka kuinka paljon.

PHP:llä tarkistusta voisi jatkaa seuraavasti.

<?php
if(strlen($_POST["kentta"]) > $maxpituus)
    die("Liian pitkä viesti!");
?>

Sitten kun vielä käsittelet lomaketta, niin muista käyttää mysql_real_escape_string-funktiota, jollet käytä sitten PDO:ta.

latenleffahylly [19.12.2011 14:54:14]

#

Kiitos, luen ohjeet seuraavaksi.

- Olen surkea PHP:ssä ja scriptien teossa, mutta kiitos esimerkeistä. Yritän saada koodin aikaan..

latenleffahylly [19.12.2011 15:46:34]

#

Hei, onko mahdollista saada hieman apua koodin kirjoittamiseen, että pääsen alkuun? En osaa kunnolla yhdistää koodia.


(huom! haluan itse oppia koodaamaan, en ilmaista koodia, mutta olen sen verran aloittelija eli jos voisitte antaa jonkun rungon näiden koodien pohjalta, kiitos!)

- miten koodi yhdistetään toisiinsa?

-----------------------------------------------------------------------------

<?php
                            if($_POST['submit']) {
                                $con = mysql_connect("xxx", "xxx", "xxx");
                                if (!$con)
                                    {
                                        die('Could not connect: ' . mysql_error());
                                    }
                                        mysql_select_db("xxx", $con);
                                        $sql="INSERT INTO Persons (asianumero, etunimi, sukunimi, puhelin, sahkoposti, viesti, tekstiviesti, pvm)
                                        VALUES
                                        ('$_POST[asianumero]','$_POST[etunimi]','$_POST[sukunimi]','$_POST[puhelin]','$_POST[sahkoposti]','$_POST[viesti]','$_POST[tekstiviesti]','$_POST[pvm]')";
                                        if (!mysql_query($sql,$con))
                                            {
                                                die('Error: ' . mysql_error());
                                            }
                                        echo "Kiitos!";
                                mysql_close($con);
                            }
                        ?>
<?php
                            if(strlen($_POST["viesti"]) > $maxpituus)
                                die("Viesti on liian pitkä!");
                        ?>

En oikein ymmärrä miten tämä liittyy muuhun koodiin.. ("...muista käyttää mysql_real_escape_string-funktiota,..")

<?php
                        // Query
                        $query = sprintf("SELECT * FROM users WHERE user='%s' AND password='%s'",
                        mysql_real_escape_string($user),
                        mysql_real_escape_string($password));
                        ?>

qeijo [19.12.2011 16:05:48]

#

Sit luetaan taas iltasanomista injektioista ja tietovuodoista.

Macro [19.12.2011 16:17:16]

#

Luitko funktion kuvauksen php.netistä? Ilmeisesti et.

Nyt annat käyttäjän lähettää ihan mitä tahansa tietoa lomakkeelta tietokantaan. Mitä jos tekisit jotenkin näin...?

<?php
$yhteys = mysql_connect("localhost", "käyttäjä", "salasana") or die(mysql_error());
mysql_select_db("tietokanta");

$arvot = array();
foreach($_POST as $posti => $arvo) {
	$arvot[] = "'" . mysql_real_escape_string($arvo) . "'";

	if(empty($arvo))
		die("Täytä {$posti}!");
}

if(!mysql_query("INSERT INTO Persons(asianumero, etunimi, sukunimi, puhelin, sahkoposti, viesti, tekstiviesti, pvm) VALUES " . implode(", ", $arvot)))
	die("Henkilön lisäys epäonnistui!");

mysql_close($yhteys);
?>

Tämä tietenkin edellyttää, että $_POST-taulukossa olevat tiedot ovat samassa järjestyksessä, kuin ne lisätään tietokantaan.

lainaus:

SELECT * FROM users WHERE user='%s' AND password='%s'

Mitä jos annankin kyselylle käyttäjäksi vaikka admin' --? Nyt haetaankin vain kaikki käyttäjät nimellä admin, eikä salasanaa vaadita. Kirjautuminen onnistuu siis ilman salasanaa.

Täältä löytyy pari opasta PHP:n ja MySQL:n käyttöön, kannattaisi vilkaista.

https://www.ohjelmointiputka.net/oppaat/opas.php?tunnus=php_01
https://www.ohjelmointiputka.net/oppaat/opas.php?tunnus=mysqlphp01

latenleffahylly [19.12.2011 16:24:58]

#

Kiitos paljon Macro. Ongelmani on, että ymmärrän hieman PHP:tä mutta koodin tekeminen itse on lähes mahdotonta. Tutustun linkkeihin ja yritän päästä eteenpäin. (pahoittelut aihauttamastani vaivasta)

Metabolix [19.12.2011 16:33:54]

#

Macron koodissa on parikin ongelmaa: arvojen ympärille ei laiteta sulkuja eikä '-merkkejä ja järjestys ja määräkin voi olla (ja todennäköisesti myös on) taulukossa väärä. Lisäksi esimerkikstä puuttui se kaikkein olennaisin asia eli mysql_real_escape_string.

Jos on mitenkään mahdollista, kannattaa vaihtaa PDO:hon ja tehdä, kuten Macron mainitsemissa oppaissa näytetään.

Älä missään tapauksessa yritä "yhdistää koodeja", jos et ymmärrä, mitä niissä tapahtuu. Ei ole harvinaista, että huonon koodarin tekemä "korjaus" aiheuttaa koodiin jonkin kriittisen vian tai tietoturva-aukon. Turvallisinta on käyttää laadukkaita, kokonaisia koodeja (ei mistään satunnaisilta foorumeilta kopioituja!) tai opetella asiat niin hyvin, että pystyy itse tekemään laadukkaita koodeja.

Kunnollisessa opettelussa kestää tyypillisesti vuosia, mutta perusasiat tietoturvasta pitäisi minusta pystyä opettelemaan auttavasti aivan muutamassa minuutissa, jos ymmärtää jo PHP:n alkeet. Macron mainitsemassa PHP-opassarjassa on yksi osa juuri tästä aiheesta.

latenleffahylly [19.12.2011 16:42:00]

#

Ongelmani on kokemattomuuteni..

Eli teen lomaketta, jossa on mm. tekstikentät (textarea) ja (input).

VIESTI
- pakollinen tieto (*)
- max. 256 merkkiä


ASIANUMERO
- pakollinen tieto (*)


Ennen kuin voi painaa lähetä pitää tarkistaa, että molemmat kentät täytetty eikä yli 256 merkkiä. Toivoisin, että voisin saada tähän, jonkun perusrakenteen miten kannattaisi tehdä.. en tiedä onko sellaista.

En kuitenkaan tarkoita, että toisten pitää koodata puolestani, mutta tarvitsisin apua, että pääsen alkuun.. (esim. PHP + JavaScript-validointi??)

Metabolix [19.12.2011 16:58:06]

#

JavaScriptilla voit estää lähetyksen vain niiltä käyttäjiltä, joilla JavaScript on käytössä. Kuka tahansa voi laittaa JavaScriptin pois päältä.

Tässä on kuitenkin JavaScriptilla tehty tarkistus. Bonuksena on vielä esimerkki asiakasnumeron muodon tarkistuksesta.

<form onsubmit="return tarkista(this);">
	<dl>
		<dt><label for="asianumero">Asianumero</label></dt>
		<dd><input type="number" name="asianumero" id="asianumero" /></dd>
		<dt><label for="viesti">Viesti</label></dt>
		<dd><textarea name="viesti" id="viesti" rows="5" cols="60"></textarea></dd>
	</dl>
</form>
function virhe(viesti) {
	alert(viesti);
	return false;
}
function tarkista(form) {
	if (form.viesti.value.length > 256)
		return virhe("Liian pitkä viesti!");
	if (form.asianumero.value.length > 256)
		return virhe("Liian pitkä asianumero!");
	if (!form.asianumero.value.match(/^[0-9]{1,10}$/))
		return virhe("Asianumeron pitää sisältää 1–10 numeroa!");
	return true;
}

Vastaavan PHP-koodin Macro jo näyttikin, tosin die on aika ruma tapa näyttää viestejä käyttäjälle.

latenleffahylly [19.12.2011 17:10:09]

#

Hei Metabolix,

- onko koodisi tuota PDO:ta?


Yritän hieman tutkia sitä, kiitos suuresti..

------------------------------------------------------------------------------

Otin mallia W3SCHOOLS -oppaasta:

- Edelleen vaikea liittää omaa koodia esimerkkeihin..

<?php
function check_input($value)
{
// Stripslashes
if (get_magic_quotes_gpc())
  {
  $value = stripslashes($value);
  }
// Quote if not a number
if (!is_numeric($value))
  {
  $value = "'" . mysql_real_escape_string($value) . "'";
  }
return $value;
}

$con = mysql_connect("localhost", "peter", "abc123");
if (!$con)
  {
  die('Could not connect: ' . mysql_error());
  }

// Make a safe SQL
$user = check_input($_POST['user']);
$pwd = check_input($_POST['pwd']);

$sqlYKSI = "SELECT * FROM users WHERE
user=$user AND password=$pwd";
mysql_query($sqlYKSI);


if(strlen($_POST["kentta"]) > $maxpituus)
{
    die("Liian pitkä viesti!");
}


$sqlKAKSI="INSERT INTO Persons (asianumero, etunimi, sukunimi, puhelin, sahkoposti, viesti, tekstiviesti, pvm)
VALUES
('$_POST[asianumero]','$_POST[etunimi]','$_POST[sukunimi]','$_POST[puhelin]','$_POST[sahkoposti]','$_POST[viesti]','$_POST[tekstiviesti]','$_POST[pvm]')";
if (!mysql_query($sqlKAKSI))
{
die('Error: ' . mysql_error());
}
echo "Kiitos!";


mysql_close($con);
?>

LOMAKE:

<form method='post' action='' name="myForm" onsubmit="return validateForm()">
<table>

<tr><td>Asianumero (*)</td><td><input type='text' name='asianumero'></td></tr>

<tr><td>Viesti (*)</td><td><p>viestin pituus max. 256 merkkiä</td></tr>
<tr><td></td><td><textarea name='viesti' onchange="maxlength('viesti', 256)"></textarea></td></tr>

<tr><td></td><td><input type="submit" name="submit" value="Lähetä"</td></tr>

</table>
</form>

Yucca [19.12.2011 17:12:58]

#

Aloittelijan kannattaa ehkä alusta alkaen opetella tekemään tämä asia HTML5-tyylisesti: käytetään required-, pattern- ja maxlength-määritettä (jotka toimivat riittävän moderneissa selaimissa vaikka JavaScript ei olisi käytössä) ja myöhemmässä vaiheessa täydennetään JavaScript-koodeilla, joka olennaisesti toteuttaa ne vanhemmissakin selaimissa. Tämä ei ole ihan triviaalia mutta ei hirveän vaikeaakaan. Pitäisiköhän suositella jotain suomenkielistä HTML5-kirjaa, jossa aihetta käsitellään... :-)

Esim.

<input name=asianumero id=asianumero required size=10 maxlength=10 pattern=[0-9]{1,10}>

Tietoturvan kannalta lähinnä vain palvelimessa tapahtuvilla tarkistuksilla on merkitystä. Nuo selainpuolen tarkistukset ovat lähinnä käyttömukavuuden takia, niin että käyttäjä saa mahdollisimman varhaisessa vaiheessa ilmoituksen virheistä. Palvelinpuolen tarkistusten tarve taas olennaisesti riippuu siitä, mitä datalla on tarkoitus tehdä.

latenleffahylly [19.12.2011 17:16:41]

#

HTML-5 on hieno kieli ja haluan oppia sen tulevaisuudessa. NYT yritän ensin tehdä tuon Matabolixsin koodin mukaisesti.. ja W3SCHOOLS (esimerkki)--------thänks! kaikille olette auttaneet todella paljon ja kärsivällisesti, vaikka muitakin kiireitä teilläkin..

Macro [19.12.2011 17:26:27]

#

Nähtävästi koodistani jäi juuri ne, mitkä olivat aloittajallakin väärin. Korjasin ne koodiin.

latenleffahylly [19.12.2011 17:34:26]

#

Macro, kiitos tästä.. aloitan rakentamisen tämän pohjalta.

Metabolix, kiitos näistä.. koodi toimi täydellisesti.

(Mod. huom: Turhaan kopioit viestiisi muiden koodeja.)

Yucca [21.12.2011 01:55:17]

#

latenleffahylly kirjoitti:

Otin mallia W3SCHOOLS -oppaasta:

Ei kannattaisi ottaa. Asiantuntijat ovat kyllä hiukan erimielisiä siitä, onko se kartettava, vältettävä vai kammottava, mutta viisainta pysyä poissa. Ks.
http://w3fools.com/
(En allekirjoita kaikkea, koska mukana on liian tiukkapipoistakin kritiikkiä, mutta kokonaisuudessaan sivusto osoittaa aika murskaavasti w3schoolsin kelvottomuuden.)

latenleffahylly [22.12.2011 18:09:06]

#

Yucca kirjoitti:

Aloittelijan kannattaa ehkä alusta alkaen opetella tekemään tämä asia HTML5-tyylisesti:

- HTML5 - uudet ominaisuudet (2011), tollanen kirja menee ostoon ensi torstaina.

Yucca kirjoitti:

Ei kannattaisi ottaa...

(W3Schools!) ..Kiitos tästä tiedosta, tuli suurena yllätyksenä, sillä koulussa tätä sivua suositeltiin ja käytettiin paljon.

Onko ehdotuksia mistä saisi "parhaat" ohjeet? - kun nyt lomaketta rakennan: (php, html, css, javascrpit)

-------------------------------------------

Eli teen lomaketta, jossa on mm. tekstikentät (textarea) ja (input).

VIESTI
- pakollinen tieto (*)
- max. 256 merkkiä


ASIANUMERO
- pakollinen tieto (*)


kiitos

Metabolix [22.12.2011 22:27:02]

#

Mikä siinä vieläkin mättää? Olet saanut kaiken tarvittavan tiedon jo pariin kertaan eri muodoissa, joten jos ei tällä selviä, ei auta kuin opetella oikeasti koodaamaan itse (mainituista PHP-oppaista ynnä muista, w3fools-sivulla oli monta hyvää linkkiä myös) tai palkata joku tekemään homma alusta loppuun puolestasi.

latenleffahylly [23.12.2011 00:29:30]

#

Terve Metabolix,

..olen itseasiassa hylännyt w3schoolsin ja seuraavaksi hankin HTML5-kirjan. Tuota PHP-opasta käyn juuri läpi ja perusversio lomakkeesta on jo valmis.

tarkistan lomakkeen ensin javascriptillä ja sitten php:llä.

--------------------------------------------------------------------------------

kohta... mysql_real_escape_string

täytyy vielä käydä läpi, eli miten upotan omaan koodiin. Huom! olen täysi aloittelija PHP:n suhteen, mutta oppaista on ollut apua, ymmärrän muutenkin php-koodia, mutta sen kirjoittaminen itse on vaikeaa jostain syystä.

Macro [23.12.2011 08:50:05]

#

Metabolix kirjoitti:

w3fools-sivulla

latenleffahylly kirjoitti:

olen itseasiassa hylännyt w3schoolsin

W3Fools onkin oma sivunsa.

latenleffahylly [25.12.2011 18:53:10]

#

Onko kellään aikaa tarkistaa tämä koodi?

(tietokantaan tallentaminen ei toimi, antaa virheilmoituksen. Kaikki tarkistukset toimivat oikein)

----------------------------------------------------------------------

JAVASCRIPT:

<script type="text/javascript">

                           function virhe(viesti) {
                           alert(viesti);
                           return false;
                           }
                           function tarkista(form) {

                           if (form.asianumero.value.length > 8)
                                   return virhe("Asianumero 7 numeroa!");

                           if (!form.asianumero.value.match(/^[0-9]{7,7}$/))
                                   return virhe("Tarkista asianumero!");



                           if (form.viesti.value.length > 5)
                                   return virhe("Viesti 1-5 kirjainta!");

                           if (!form.viesti.value.match(/^[a-zA-z0-9]{1,5}$/))
                                   return virhe("Kirjoita viesti!");

                           return true;
                           }

                       </script>

PHP:

<?php $pvm = mktime(0,0,0,date("m"),date("d"),date("Y")); ?>
                           <?php
                           if($_POST['submit']) {

                               $asno = $_POST["asianumero"];
                               if ($asno < 5000000)
                                   {
                                       echo "virhe1!";
                                   }
                               elseif ($asno > 5999999)
                                   {
                                       echo "virhe2!";
                                   }
                               else
                                   {

                                       $yhteys = mysql_connect("localhost", "käyttäjä", "salasana") or die(mysql_error());
                                       mysql_select_db("tietokanta", $yhteys);


                                       $arvot = array();
                                       foreach($_POST as $posti => $arvo) {
                                       $arvot[] = "'" . mysql_real_escape_string($arvo) . "'";

                                       if(empty($arvo))
                                       die("Täytä {$posti}!");
                                       }

                                       if(!mysql_query("INSERT INTO Persons(asianumero, etunimi, sukunimi, puhelin, sahkoposti, viesti, tekstiviesti, pvm) VALUES " . implode(", ", $arvot)))
                                       die("Henkilön lisäys epäonnistui!");


                                       mysql_close($yhteys);
                                   }
                           }
                           ?>

HTML (LOMAKE)

<form method='post' action='TÄMÄ SAMA LOMAKE' onsubmit="return tarkista(this);">
                                <dl>
                                    <dt><label for="asianumero">Asianumero (*)</label></dt>
                                    <dd><input type="number" name='asianumero' id="asianumero" maxlength="7" /></dd>

                                    <dt><label for="etunimi">Etunimi</label></dt>
                                    <dd><input type="text" name='etunimi' size='50' maxlength="32" /></dd>

                                    <dt><label for="sukunimi">Sukunimi</label></dt>
                                    <dd><input type="text" name='sukunimi' size='50' maxlength="32" /></dd>

                                    <dt><label for="puhelin">Puhelin</label></dt>
                                    <dd><input type="text" name='puhelin' size='50' maxlength="32" /></dd>

                                    <dt><label for="sahkoposti">Sahkoposti</label></dt>
                                    <dd><input type="text" name='sahkoposti' size='50' maxlength="32" /></dd>

                                    <dt><label for="viesti">Viesti (*) 1-5 merkki&auml;..</label></dt>
                                    <dd><textarea name='viesti' id="viesti" rows="5" cols="60"></textarea></dd>

                                    <dt><label for="tekstiviesti">Haluan saada maksutietoja tekstiviestin&auml;</label></dt>
                                    <dd><input type="radio" name='tekstiviesti' value="kylla" /> kyll&auml; <input type="radio" name="tekstiviesti" value="ei" checked="checked" /> ei</dd>

                                    <input type='hidden' name='pvm' value='<?php echo "".date("d/m/Y", $pvm); ?>' />
                                    <input type="submit" name="submit" value="L&auml;het&auml; tiedot &raquo;" style="cursor:pointer; float:right;">
                                </dl>
                            </form>

Grez [25.12.2011 18:58:45]

#

Mikähän se virheilmoitus on?

Sulla on muuten PHP:tä tuolla "HTML-lomakkeella"

Ja sitten sanoisin vielä että toi tallennusskripti luottaa mielestäni turhan vahvasti siihen, että syöte tulee oikeassa järjestyksessä ja oikealla määrällä kenttiä.

latenleffahylly [25.12.2011 19:17:15]

#

Okei, kiitos Grez..

Niin virheilmoituksella tarkoitin vain tätä: die("Henkilön lisäys epäonnistui!");

Olen hieman jumissa lomakkeen kanssa, mutta yritetään. Tuo php-koodi html:n seassa liittyy päivämäärään, jonka tallennan tietokantaan "hidden" kätkettynä.

qeijo [25.12.2011 19:47:31]

#

Tulosta kysely niin näet..

Metabolix [25.12.2011 19:52:07]

#

Kannattaisi oikeasti lukea nämä viestit. Kerroin jo, miksei tuo lähestymistapa toimi:

Metabolix kirjoitti:

Macron koodissa – – arvojen järjestys ja määräkin voi olla (ja todennäköisesti myös on) taulukossa väärä.

Mutta kai sinun oli pakko selvittää asia oikein kantapään kautta. ^^

Älä yritä lyhentää koodia, vaan luettele arvot yksitellen. Tuo sprintf-funktiota käyttävä koodisi on oikein hyvä alku.

latenleffahylly [25.12.2011 20:10:39]

#

Metabolixille tiedoksi, että todellakin yritän tarkkaan lukea joka kirjoituksen alusta loppuun.

nyt yritän löytää tuon -> "Tuo sprintf-funktiota käyttävä koodisi on oikein hyvä alku."

latenleffahylly [25.12.2011 21:07:05]

#

Hei, sellaista vielä, että ehkä olen selittänyt asian jotenkin typerästi, mutta ASIANUMERO-kenttään saa syöttää minkä tahansa numerosarjan väliltä (5 000 000 - 5 999 999)

- Eli tietokannan taulusta ei tarkisteta täsmääkö luku.

Olen täysin hukassa tämän komennon kanssa: mysql_real_escape_string($jokunimi)

----------------------------------------------


JAKSAAKO JOKU SELITTÄÄ MITÄ TÄSSÄ KOODISSA TEHDÄÄN/TAPAHTUU?

$query = sprintf("SELECT * FROM Users WHERE user='TESTI' AND password='123'",

                                    mysql_real_escape_string($user),
                                    mysql_real_escape_string($password));

                                    $tarkistus1=user("TESTI");
                                    $tarkistus2=password("123");

                                    if ($tarkistus1!="TESTI" || $tarkistus2!="123")
                                    echo "Virhe!!!";
                                    else {

Lebe80 [25.12.2011 21:51:35]

#

Pitäisikö $tarkistus2-muuttujaa verrata "123"-merkkijonon sijaan password("123")-merkkijonoon (tai mitä funktio ikinä palauttaakaan)...

latenleffahylly [25.12.2011 23:23:13]

#

..hei Lebe80, en osaa sanoa. Tarvitsisin tiedon mitä mysql_real_escape_string tekee ylipäätään? Ja sitten se, että kun käyttäjä kirjoittaa ASIANUMERO-kenttään numerosarjan, niin mihin tarvitaan USERNAME/PASSWORD -tarkistuksia?

..no joo kiitoksia jälleen, yritän päästä koko ajan PHP:hen paremmin sisään, mutta en vieläkään ymmärrä kokonaisuutta.

latenleffahylly [26.12.2011 00:41:33]

#

Huomaako joku tarkistetaanko (kentät mysql_real_escape_string) avulla?

-----------------------------------------------------------------------

PHP-koodi on tällä hetkellä tällainen...

<?php $pvm = mktime(0,0,0,date("m"),date("d"),date("Y")); ?>
                            <?php
                            if($_POST['submit']) {

                                $asno = $_POST["asianumero"];
                                if ($asno < 5000000)
                                    {
                                        echo "virhe1!";
                                    }
                                elseif ($asno > 5999999)
                                    {
                                        echo "virhe2!";
                                    }
                                else
                                    {


                                    function check_input($value)
                                    {
                                    // Stripslashes
                                    if (get_magic_quotes_gpc())
                                      {
                                      $value = stripslashes($value);
                                      }
                                    // Quote if not a number
                                    if (!is_numeric($value))
                                      {
                                      $value = "'" . mysql_real_escape_string($value) . "'";
                                      }
                                    return $value;
                                    }

                                    $con = mysql_connect("localhost", "XXX", "XXX");
                                        if (!$con)
                                            {
                                                die('Could not connect: ' . mysql_error());
                                            }

                                                mysql_select_db("XXX", $con);

                                    // Make a safe SQL
                                    $asianumero = check_input($_POST['asianumero']);
                                    $etunimi = check_input($_POST['etunimi']);
                                    $sukunimi = check_input($_POST['sukunimi']);
                                    $puhelin = check_input($_POST['puhelin']);
                                    $viesti = check_input($_POST['viesti']);
                                    $tekstiviesti = check_input($_POST['tekstiviesti']);
                                    $pvm = check_input($_POST['pvm']);

                                    $sql="INSERT INTO Persons (asianumero, etunimi, sukunimi, puhelin,
                                    sahkoposti, viesti, tekstiviesti, pvm)
                                                VALUES
                                                ('$_POST[asianumero]','$_POST[etunimi]','$_POST[sukunimi]','$_POST[puhelin]','$_POST[sahkoposti]','$_POST[viesti]','$_POST[tekstiviesti]','$_POST[pvm]')";



                                    if (!mysql_query($sql,$con))
                                            {
                                                die('Error: ' . mysql_error());
                                            }

                                                echo "kiitos!";

                                    mysql_close($con);
                                    }
                            }
                            ?>

Tiedot tallentuvat tietokantaan, mutta tarkistetaanko kentät nyt oikein mahdollisten huijauksien varalta?

Grez [26.12.2011 00:45:47]

#

Eihän tuosta mysql_real_escapesta ole mitään hyötyä, kun tunget SQL-kyselyyn kuitenkin ne alkuperäisest käsittelemättömät arvot $POST-lomakkeelta.

latenleffahylly kirjoitti:

Tarvitsisin tiedon mitä mysql_real_escape_string tekee ylipäätään?

Paljon yksinkertaistettuna se muuttaa merkkijonon Don't muotoon Don\'t

Tarkemmin sanottuna se muuttaa minkä tahansa merkkijonon sellaiseksi, että '-merkkien sisään laitettuna se ei kyselyssä mahdollista tietoa syöttävää käyttäjää katkaisemaan eikä näin ollen SQL-injektiota. Se huomioi kulloinkin avoinna olevan kantayhteyden asetukset ko. operaation tehdessään.

latenleffahylly [26.12.2011 00:50:17]

#

Hei Grez,

Pitäisikö tuo minun SQL-lause muuttaa esim. vähän tähän tapaan: asianumero='" . $asianumero . "'

// Make a safe SQL
$user = check_input($_POST['user']);
$pwd = check_input($_POST['pwd']);

$sql = "SELECT * FROM users WHERE
user=$user AND password=$pwd";

Yritän selvittää asiaa tämän mallin pohjalta...

Grez [26.12.2011 01:01:16]

#

No tuohan vaikuttaa ihan hyvältä. Tosin nimeäisin kyllä tuon "check_input" funktion toisella tavalla, koska se ei tarkista mitään vaan käsittelee sen merkkijonon. Vaikkapa escape_input tai process_input olisi kuvaavampi.

Tosin yleensä selväkielisten salasanojen säilyttäminen tietokannassa ei ole suositeltavaa, mutta ainakin tuossa tuo syötteen käsittely toimii.

latenleffahylly [26.12.2011 01:09:59]

#

Olet oikeassa, mutta ongelmani on miten saada tämä lause esimerkin mukaiseen muotoon:

VALUES
                                          ('$_POST[asianumero]','$_POST[etunimi]','$_POST[sukunimi]','$_POST[puhelin]','$_POST[sahkoposti]','$_POST[viesti]','$_POST[tekstiviesti]','$_POST[pvm]')";

Esimerkki:

$sql = "SELECT * FROM users WHERE
user=$user AND password=$pwd";

Kirjoitin näin:

$sql="INSERT INTO Persons (asianumero, etunimi, sukunimi, puhelin, sahkoposti, viesti, tekstiviesti, pvm)
                                                VALUES
                                                ('asianumero=$asianumero','etunimi=$etunimi','sukunimi=$sukunimi','puhelin=$puhelin','sahkoposti=$sahkoposti',
                                                 'viesti=$viesti','tekstiviesti=$tekstiviesti','pvm=$pvm')";

Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'aaa'','tekstiviesti='ei'','pvm='25/12/2011'')' at line 4

Grez [26.12.2011 01:28:53]

#

Pitäisi varmaankin olla näin

$sql="INSERT INTO Persons (asianumero, etunimi, sukunimi, puhelin, sahkoposti, viesti, tekstiviesti, pvm)
                                                VALUES
                                                ($asianumero,$etunimi,$sukunimi,$puhelin,$sahkoposti,$viesti,$tekstiviesti,$pvm)";

latenleffahylly [26.12.2011 02:07:59]

#

Harmillista,

kun tekstikenttään kirjoittaa esim. etunimen kohdalle <h1>TESTI1</h1> niin ylläpidossa näkyy etunimi isolla.

tietokantannassa sen sijaan tageilla: <h1>TESTI1</h1>


Hmm.. jos joku ehtii tällä viikolla hieman kertomaan tallentuuko tieto nyt kuitenkin oikein?

KOODI:

function check_input($value)
                                    {
                                    // Stripslashes
                                    if (get_magic_quotes_gpc())
                                      {
                                      $value = stripslashes($value);
                                      }
                                    // Quote if not a number
                                    if (!is_numeric($value))
                                      {
                                      $value = "'" . mysql_real_escape_string($value) . "'";
                                      }
                                    return $value;
                                    }

                                    $con = mysql_connect("localhost", "xxx", "xxx");
                                        if (!$con)
                                            {
                                                die('Could not connect: ' . mysql_error());
                                            }

                                                mysql_select_db("xxx", $con);

                                    // Make a safe SQL
                                    $asianumero = check_input($_POST['asianumero']);
                                    $etunimi = check_input($_POST['etunimi']);
                                    $sukunimi = check_input($_POST['sukunimi']);
                                    $puhelin = check_input($_POST['puhelin']);
                                    $sahkoposti = check_input($_POST['sahkoposti']);
                                    $viesti = check_input($_POST['viesti']);
                                    $tekstiviesti = check_input($_POST['tekstiviesti']);
                                    $pvm = check_input($_POST['pvm']);


                                    $sql="INSERT INTO Persons (asianumero, etunimi, sukunimi, puhelin, sahkoposti, viesti, tekstiviesti, pvm)
                                                VALUES
                                                ($asianumero,$etunimi,$sukunimi,$puhelin,$sahkoposti,$viesti,$tekstiviesti,$pvm)";

TIETOKANTA:

P_Id 	asianumero 	etunimi 	        sukunimi 	        puhelin 	        sahkoposti 	        viesti 	tekstiviesti 	pvm

92 	    5999999 	<h1>TESTI1</h1> 	<h1>TESTI2</h1> 	<h1>TESTI3</h1> 	<h1>TESTI4</h1> 	testi 	kylla 	        26/12/2011

Grez [26.12.2011 02:43:00]

#

Tee tulostuspäässä $arvo = htmlspecialchars($arvo) ennen tulostamista.

qeijo [26.12.2011 10:06:39]

#

Grez kirjoitti:

Tee tulostuspäässä $arvo = htmlspecialchars($arvo) ennen tulostamista.

Tai/ja

$viesti = strip_tags($viesti);

- funktio ennen tietojen tallentamista.

ps.

Ennen kuin kysyt, lue: https://www.php.net/manual/en/function.strip-tags.php ja https://www.php.net/manual/en/function.htmlspecialchars.php

latenleffahylly [26.12.2011 15:36:15]

#

No nyt on linkkejä luettu... tuota mielestäni olisi paras jos tietokantaan ei voisi tallentaa esim. <H1>TESTI</H1>

Vaan tämä estettäisiin koodissa, mutta oman funktion kirjoittaminen ei onnistu, vaikka ohjeita pilvin pimein. Kiitos kuitenkin kaikesta avusta.

- Onko nyt mahdollista kuitenkin välttyä hyökkäyksiltä? (ilman funktiota, jota en osaa tehdä)

-tossu- [26.12.2011 18:45:20]

#

latenleffahylly kirjoitti:

No nyt on linkkejä luettu... tuota mielestäni olisi paras jos tietokantaan ei voisi tallentaa esim. <H1>TESTI</H1>

Vaan tämä estettäisiin koodissa, mutta oman funktion kirjoittaminen ei onnistu, vaikka ohjeita pilvin pimein.

Suosittelen tallentamaan tekstit tietokantaan käsittelemättöminä ja tulostusvaiheessa ajamaan ne htmlspecialchars-funktion läpi. Silloin sinun ei tarvitse kirjoittaa itse yhtään funktiota ja hyökkäysvaaraa ei tältä osin ole.

latenleffahylly [26.12.2011 19:06:48]

#

Kiitos tiedosta tossu,

..mutta mihin tämä tieto perustuu? ("Suosittelen tallentamaan tekstit tietokantaan käsittelemättöminä")

- Haluaisin tehdä asian oikeaoppisesti eli annetaanko silloin käyttäjän tallentaa tekstikenttää juuri sen mitä hän haluaa? Esimerkiksi jonkun hyökkäys koodin..

No mutta tuo tulostun pään juttu kuulostaa ihan hyvältä, sen osaankin tehdä.

Metabolix [26.12.2011 19:22:52]

#

Kyllä, käyttäjän annetaan kirjoittaa ihan mitä tahansa. Järjestelmän pitää toimia niin, että mikään teksti ei aiheuta vaaraa – sitä varten ovat funktiot mysql_real_escape_string (turvallinen tallennus tietokantaan) ja htmlspecialchars (turvallinen tulostus sivulle).

Tiedon sisällön tarkistaminen on aivan toinen asia, ja sitä ei pidä sekoittaa tietoturvaan. Turvallisuuden kannalta ei yhtään haittaa, vaikka käyttäjä väittäisi osoitteensa olevan "; DROP TABLE kayttajat --". Ainoa harmi on, että kukaan ei voi lähettää tähän osoitteeseen postia. Siksi on käyttäjän etu, jos tarkistetaan myös, että syötetty tieto on järkevää.

latenleffahylly kirjoitti:

mihin tämä tieto perustuu?

Tieto kannattaa tallentaa ilman ylimääräistä käsittelyä, koska käsittelyn voi aina tehdä myöhemminkin mutta käsiteltyä tietoa ei ehkä mitenkään saa takaisin alkuperäiseen muotoon.

latenleffahylly [26.12.2011 19:38:39]

#

No täytyy sanoa, että olen vaikuttunut tekstistäsi Metabolix. Laitan nämä asiat muistiin - hyvä tietää filosofia tiedon tallentamisen takana ja tiedon tulostaminen (tietokannat).

Huoh.. no nyt sitten viimeistelen koodin ja saatan sitten loppuviikosta laittaa sen tänne jos joku ehtisi sen nopeasti tarkistaa läpi. En haluaisi, että koodiin jää tietoturva aukko tai jokin muu todella typerä virhe. Jälleen kerran erittäin nöyrät suurkiitokseni kaikille auttaneille. (varsinkin suorapuheinen Metabolix) kiitos:)

latenleffahylly [27.12.2011 11:02:59]

#

Ylläpito voitteko tarkistaa lomakkeen kun ehditte?

- Lomake on nyt lähes valmis, mutta siinä on ongelmakohtia.
- 1. Lomakkeen kaikki tekstikentät tyhjenevät jos antaa väärän ASIANUMERON ja klikkaa lähetä. (onko ok?)
- 2. Haluaisin tarkistaa JavaScriptillä, että VIESTI-kentässä on 1-5 merkkiä, ei muita tarkistuksia.
- 3. Muut ongelmat, havaitseeko kukaan ongelmia/virheitä? (KIITOKSET TEILLE, JOTKA JAKSATTE/EHDITTE AUTTAA!)

----------------------------------------------------------------------------------

JAVASCRIPT -tarkistus:

<script type="text/javascript">

                            function virhe(viesti) {
                            alert(viesti);
                            return false;
                            }
                            function tarkista(form) {

                            if (form.asianumero.value.length > 8)
                                    return virhe("Asianumero 7 numeroa!");

                            if (!form.asianumero.value.match(/^[0-9]{7,7}$/))
                                    return virhe("Tarkista asianumero!");

                            if (form.viesti.value.length > 5)
                                    return virhe("Viesti 1-5 kirjainta!");

                            if (!form.viesti.value.match(/^[a-zA-z0-9]{1,5}$/))
                                    return virhe("Kirjoita viesti!");

                            return true;
                            }

                        </script>

PHP -koodi:

<?php $pvm = mktime(0,0,0,date("m"),date("d"),date("Y")); ?>
                            <?php

                            $maxpituus = 5;

                            if($_POST['submit']) {

                                $asno = $_POST["asianumero"];
                                if ($asno < 5000000 || $asno > 5999999)
                                    {
                                        echo "virhe1!";
                                    }
                                else if (strlen($_POST["viesti"]) > $maxpituus)
                                    {
                                        echo "virhe2!";
                                    }
                                else
                                    {

                                    function check_input($value)
                                    {
                                    // Stripslashes
                                    if (get_magic_quotes_gpc())
                                      {
                                      $value = stripslashes($value);
                                      }
                                    // Quote if not a number
                                    if (!is_numeric($value))
                                      {
                                      $value = "'" . mysql_real_escape_string($value) . "'";
                                      }
                                    return $value;
                                    }

                                    $con = mysql_connect("localhost", "XXX", "XXX");
                                        if (!$con)
                                            {
                                                die('Could not connect: ' . mysql_error());
                                            }

                                                mysql_select_db("XXX", $con);

                                    // Make a safe SQL
                                    $asianumero = check_input($_POST['asianumero']);
                                    $etunimi = check_input($_POST['etunimi']);
                                    $sukunimi = check_input($_POST['sukunimi']);
                                    $puhelin = check_input($_POST['puhelin']);
                                    $sahkoposti = check_input($_POST['sahkoposti']);
                                    $viesti = check_input($_POST['viesti']);
                                    $tekstiviesti = check_input($_POST['tekstiviesti']);
                                    $pvm = check_input($_POST['pvm']);

                                    $sql="INSERT INTO Persons (asianumero, etunimi, sukunimi, puhelin, sahkoposti, viesti, tekstiviesti, pvm)
                                                VALUES
                                                ($asianumero,$etunimi,$sukunimi,$puhelin,$sahkoposti,$viesti,$tekstiviesti,$pvm)";

                                    if (!mysql_query($sql,$con))
                                            {
                                                die('Error: ' . mysql_error());
                                            }

                                                echo "<p>Kiitos!</p>";

                                    mysql_close($con);
                                    }
                                }
                            ?>

HTML -lomake

<form method='post' action='TÄMÄ SAMA LOMAKE' onsubmit="return tarkista(this);">
                                <dl>
                                    <dt><label for="asianumero">Asianumero (*)</label></dt>
                                    <dd><input type="number" name="asianumero" id="asianumero" maxlength="7" /></dd>

                                    <dt><label for="etunimi">Etunimi</label></dt>
                                    <dd><input type="text" name="etunimi" size='50' maxlength="32" /></dd>

                                    <dt><label for="sukunimi">Sukunimi</label></dt>
                                    <dd><input type="text" name="sukunimi" size='50' maxlength="32" /></dd>

                                    <dt><label for="puhelin">Puhelin</label></dt>
                                    <dd><input type="text" name="puhelin" size='50' maxlength="32" /></dd>

                                    <dt><label for="sahkoposti">Sahkoposti</label></dt>
                                    <dd><input type="text" name="sahkoposti" size='50' maxlength="32" /></dd>

                                    <dt><label for="viesti">Viesti (*) 1-5 merkki&auml;..</label></dt>
                                    <dd><textarea name="viesti" id="viesti" rows="5" cols="60"></textarea></dd>

                                    <dt><label for="tekstiviesti">Haluan saada maksutietoja tekstiviestin&auml;</label></dt>
                                    <dd><input type="radio" name="tekstiviesti" value="kylla" /> kyll&auml; <input type="radio" name="tekstiviesti" value="ei" checked="checked" /> ei</dd>

                                    <input type='hidden' name="pvm" value='<?php echo "".date("d/m/Y", $pvm); ?>' />
                                    <input type="submit" name="submit" value="LÄHETÄ">
                                </dl>
                            </form>

MUUTA: Tiedot tallentuvat oikein tietokantaan. (esim. <H1>testi</H1>)


HUOM! Toivon, että tämä on viimeinen viesti, jolla häiritsen tämän asian tiimoilta. Tulevaisuudessa tarkoitus opetella enemmän itse ja siksi olenkin hankkimassa HTML5-kirjaa. Jos joku tietää uuden, hyvän PHP-kirjan noviisille niin olisi mukava! Pahoittelut aiheuttamastani häiriöstä ja postejen suuresta määrästä. Kiitän!

latenleffahylly [27.12.2011 18:40:30]

#

..no ehtisikö joku vilkaista hieman lomakettani?

AkeMake [28.12.2011 02:27:14]

#

En nyt kovin tarkkaan jaksanut koodia katsella, mutta jos jotain siitä sanon..
Tuossa PHP-puolella kannattaa tarkistaa, että submit on asetettu. Lisäksi vaikka lähetetty asianumero JavaScriptillä tarkistetaankin, kannattaa se tarkistaa myös php:ssä. Paras tapa kai on muuttaa $_POST:sta tuleva merkkijono kokonaisluvuksi, jottet vahingossakaan tutki mitään muuta kuin lukua.

if($_POST['submit'] && isset($_POST['submit'])) {
      $asno = intval($_POST["asianumero"]);

Toki jos asianumero saa olla murtoluku, niin sitten floatvalilla.

floatval($_POST["asianumero"]);

latenleffahylly [28.12.2011 05:29:38]

#

Eli tuossa ylemmässä php-koodissa tarkistetaan ensin, että lähetä-painiketta on painettu. Sitten että se on asetettu? (mitä tämä tarkoittaa)

..sen jälkeen "intval" tutkii asianumeron "jotenkin" php:llä?? Kiva kuitenkin, että jaksoit vilkaista koodia, mutta voitko sinä/joku avata tuota ensimmäistä php-koodia.

The Alchemist [28.12.2011 07:42:22]

#

latenleffahylly kirjoitti:

Eli tuossa ylemmässä php-koodissa tarkistetaan ensin, että lähetä-painiketta on painettu. Sitten että se on asetettu? (mitä tämä tarkoittaa)

Ei tarkoita yhtään mitään. Isset() tarkistaa, onko nappia painettu. "$_POST['submit']" on ainoastaan väärin. Mikäli vaihdat napin nimeä tai jopa käytät samaa koodia eri lomakkeen kanssa, niin tuo ehtolausekkeen ensimmäinen osa aiheuttaa virheilmoituksen (notice level).

lainaus:

..sen jälkeen "intval" tutkii asianumeron "jotenkin" php:llä?? Kiva kuitenkin, että jaksoit vilkaista koodia, mutta voitko sinä/joku avata tuota ensimmäistä php-koodia.

Ei tutki yhtään mitään. Se vain parsii annetusta merkkijonosta kokonaisluvun.

Arvailun ja jossittelun sijaan voisit lukea manuaalista: intval()

latenleffahylly [28.12.2011 10:49:48]

#

...hetkinen, tuo "$_POST['submit']" on ainakin ennen toiminut omilla sivuillani ja nyt suosittelette mielummin "isset" -käyttöä post/submit sijaan?

"intval" sen sijaan tekee kokonaisluvun.. hmm.. eli tälläkö atribuutilla Asianumero kannattaa tarkistaa. Mielestäni sen voisi tarkistaa niin että onko luku 5000000-5999999 ja 7 numeroa, ei kirjainta (sama idea kuin javascript-koodissa)

makumaku [28.12.2011 11:17:47]

#

latenleffahylly kirjoitti:

"intval" sen sijaan tekee kokonaisluvun.. hmm.. eli tälläkö atribuutilla Asianumero kannattaa tarkistaa. Mielestäni sen voisi tarkistaa niin että onko luku 5000000-5999999 ja 7 numeroa, ei kirjainta (sama idea kuin javascript-koodissa)

Tuota "atribuuttia" kutsutaan myös nimellä funktio. :)
Mutta kuten sanottu niin tuo ei tarkista mitään. Luitko edellistä viestiä?
Jos haluat tarkistaa että asianumero on välillä 5000000-5999999 ja 7 numeroa, niin sitten tietenkin teet tälläiset tarkistukset. Yksinkertaista.
Jos käyttäjä syöttäisi asianumerokenttään vaikka "0477S25U3AB", niin intval leipoo tuosta luvun 477, vaikka oikealla tarkistuksella voisi huomata että käyttäjä ei joko tajunnut mitä kenttään piti kirjoittaa tai on ymmärtänyt oman asianumeronsa väärin, eikä tuota intvallin antamaa lukua pitäisi käyttää.

latenleffahylly [28.12.2011 11:38:01]

#

noniin taas lisää infoa.. uskokaa tai älkää niin minä tosiaan luen kaikki viestit, mutta olen yllättynyt kuinka monimutkaiseksi suht yksinkertaisen lomakkeen tarkistaminen on muodostunut käsissäni.

hmm.. eli nyt tarkistan että lomakkeen lähetä painiketta on klikattu issetin avulla ja asianumeron tarkistuksen teen samaan tapaan kuin javascript koodissa. Kiitos makumaku. Mielestäni funktio intval ei sovi tähän lomakkeeseen, mutta luultavasti se on hyödyllinen tulevaisuudessa. (kiitos myös AkeMake & The Alchemist).

Seuraavaksi varmaan käy niin että joku kertoo uuden tavan tehdä tämä kaikki ja kertoo että esimerkit vääriä.. yritän toki koko ajan päästä paremmin php:hen sisään- vahvuuteni kuitenkin muualla.

The Alchemist [28.12.2011 20:37:29]

#

Voihan sen tehdä juuri sillä tavoin kuin huvittaa. Netistä varmasti löytyy vaikka millaisia viritelmiä, jos vain haluaa kokeilla googlettamista. Itsekin olen vääntänyt yhden systeemin - nähtävissä alla, joka tosin on ollut muutoksen alla ja siksikin hieman vajaavainen. Tein sen enemmän kokeilun vuoksi kuin tuotantokäyttöön, joten hyödyllisyys on vähän niin ja näin.

(FormView-luokan koodi on paskaa mutta voi voi.)

*** FormFactory.php
<?php

class FormFactory {
	static private $default_method = 'post';
	static private $default_action = '';

	static function form($id, $form_class=null, $handler_class=null) {
		if (empty($form_class))
			$form_class = 'Form';

		$attrs = array(
			'action' => self::$default_action,
			'method' => self::$default_method,
		);

		if (($form = Form::restoreSession($id)) == false)
			$form = new $form_class($id, $attrs);

		if (!empty($handler_class))
			$form->setHandler(new $handler_class());

		return $form;
	}

	static function setDefaultMethod($method) {
		self::$default_method = $method;
	}

	static function defaultMethod() {
		return self::$default_method;
	}

	static function setDefaultAction($action) {
		self::$default_action = $action;
	}

	static function defaultAction() {
		return self::$default_action;
	}
}

?>
*** Form.php
<?php

/**
 * Printable form for user input.
 */
class Form {
	/**
	 * Contains all elements in a form.
	 **/
	private $nodes;

	/**
	 * References of node values.
	 **/
	private $values;

	/**
	 * Identifier of a form.
	 **/
	private $id;

	/**
	 * URL to which the form data will be sent.
	 **/
	private $action;

	/**
	 * Method used to submit the data (get/post).
	 **/
	private $method;

	/**
	 * Vector of references to input fields in the node tree.
	 */
	private $fields;

	/**
	 * Handler provides validating and processing for the form.
	 **/
	private $handler;

	/**
	 * @param $id id attribute for this form
	 * @param $action URL to which the form data will be sent
	 * @param $method method used to submit the data
	 **/
	public function __construct($id, $attrs=array()) {
		$this->id = $id;
		$this->method = $attrs['method'];

		// Initializes $nodes and $values
		$this->reset(true);

		if (isset($attrs['action']))
			$this->action = $attrs['action'];
		else {

			if (isset($_SERVER['REDIRECT_URL']))
				$this->action = $_SERVER['REDIRECT_URL'];
			else
				$this->action = $_SERVER['SCRIPT_FILENAME'];
		}

		$this->init();

		$this->setHandler(new DefaultHandler());
	}

	/**
	 * Handle trivial get/set methods here.
	 *
	 * @param $name name of the method that was called
	 * @param $args array of arguments supplied for the method
	 **/
	public function __call($name, $params) {
		switch ($name) {
			case 'action':
			case 'handler':
			case 'id':
			case 'method':
			case 'nodes':
			case 'values':
				return $this->$name;

			case 'setHandler':
				$this->handler = $params[0];
				$this->handler->setForm($this);
				break;

			case 'setValue':
				if (array_key_exists($params[0], $this->values))
					$this->values[$params[0]] = $params[1];

				break;

			default:
				trigger_error('Form: No method ' . $name, E_USER_WARNING);
				return null;
		}
	}

	final public function validate() {
		if (!$this->handler)
			return false;

		return $this->handler->validate($this);
	}

	final public function process() {
		if (!$this->handler)
			return false;

		return $this->handler->process($this);
	}

	public function value($field) {
		if (empty($this->values[$field]))
			return false;

		return $this->values[$field];
	}

	public function fields() {
		return array_keys($this->values);
	}

	public function clearSession() {
		$id = 'FORM_' . $this->id;

		if (isset($_SESSION[$id]))
			unset($_SESSION[$id]);
	}

	/**
	 * Matches a value against another value or an array of values.
	 * If an array is supplied, a match means the first value is contained in it.
	 *
	 * @param $value scalar value being tested
	 * @param $options scalar or an array to be matched with
	 * @return boolean
	 **/
	static public function valueMatches($value, $options) {
		return is_array($options) ? in_array($value, $options) : $value == $options;
	}

	/**
	 * Restores a form from user's session data.
	 *
	 * @param $id identifier of the form
	 * @param $clear whether or not the form should be removed from cache. Defaults to TRUE
	 * @return form object or FALSE if no form found
	 **/
	static public function restoreSession($id, $clear=false) {
		$id = 'FORM_' . $id;

		if (!isset($_SESSION[$id]))
			return false;

		$form = $_SESSION[$id];

		if ($clear)
			unset($_SESSION[$id]);

		$form->saveSession();

		if (!is_a($form, 'Form')) {
			trigger_error(
				'Form::restoreSession(): corrupted form! ' .
				'Remember to load the class file before session_start()!'
			);

			return false;
		}

		return $form;
	}

	/**
	 * Appends a new group into the form. Might be rendered e.g. as a fieldset.
	 *
	 * @param $label Name and label for the group
	 * @return boolean
	 **/
	public function addGroup($label) {
		if (isset($this->nodes[$label]))
			return false;

		$this->nodes[$label] = array();

		return true;
	}

	/**
	 * Inserts a form element into the form. The element will be appended to its
	 * group.
	 *
	 * @param $type HTML type of the field (checkbox, text, button, ...)
	 * @param $label label for the element
	 * @param $id id attribute for the element. By default lower-case $label.
	 * @param $required Set to true if this value is required to accept the input
	 * @param $group the group this element belongs to. (See {@link addGroup})
	 * @param $value pre-defined value or HTML for a non-input element
	 * @param $options allowed values or additional definions for a non-input element.
	 **/
	public function addNode(array $node) {

		if (empty($node['id']) && empty($node['name'])) {
			trigger_error('Form::addNode(): Invalid node!');
			return;
		}

		if (empty($node['name']))
			$node['name'] = $node['id'];

		// Name has to be unique
		if (array_key_exists($node['name'], $this->values))
			return false;

		$defaults = array(
			'type' => 'text',
			'required' => false,
			'options' => null,
			'group' => '_TOP_',
			'label' => null,
			'value' => null,
		);

		foreach ($defaults as $key => $value) {
			if (!array_key_exists($key, $node))
				$node[$key] = $value;
		}

		extract($node, EXTR_PREFIX_ALL, '');

		if (!isset($this->nodes[$_group]))
			return false;

		$i = count($this->nodes[$_group]);

		$this->nodes[$_group][$i] = array(
			'type' => $_type,
			'label' => $_label,
			'required' => $_required,
			'name' => $_name,
		);

		if (isset($_id))
			$this->nodes[$_group][$i]['id'] = $_id;

		if (is_array($_options)) {
			$i = count($this->nodes[$_group]) - 1;
			$this->nodes[$_group][$i]['options'] = $_options;

			if (is_array($_value)) {
				foreach ($_value as $j => $val) {
					if (!isset($_options[$val])) {
						unset($_value[$j]);
					}
				}

				if (empty($_value))
					$_value = null;

			} elseif (!isset($_options[$value]))
				$_value = null;
		}

		$this->nodes[$_group][$i]['value'] = $_value;
		$this->fields[$_name] = &$this->nodes[$_group][$i];
		$this->values[$_name] = &$this->nodes[$_group][$i]['value'];
	}

	/**
	 * Remove all fields and their values.
	 *
	 * @param $setDefaults should the default set of fields be restored
	 **/
	public function reset($setDefaults=true) {
		$this->values = array();
		$this->nodes = array('_TOP_' => array());
		$this->fields = array();

		if ($setDefaults) {
			$this->addHiddenField('action', 'submit_form');
			$this->addHiddenField('form_id', $this->id);
			$this->addHiddenField('form_url', $_SERVER['REQUEST_URI']);
			$this->init();
		}
	}

	/**
	 * Clear all values.
	 **/
	public function clear() {
		foreach ($this->values as $key => $value)
			$this->values[$key] = null;
	}

	/**
	 * Reads user input into the form.
	 *
	 * @param $variable input variable to read from. Defaults to $_REQUEST
	 **/
	public function readUserInput(&$variable=null) {
		if (is_null($variable))
			$variable = &$_REQUEST;

		foreach ($variable as $key => $val) {
			if (!array_key_exists($key, $this->values))
				continue;

			$this->values[$key] = htmlspecialchars_decode($val);
		}
	}

	/**
	 * Saves form into the current session.
	 **/
	public function saveSession() {
		$_SESSION['FORM_' . $this->id] = $this;
	}

	/**
	 * Convenience function for adding a hidden field.
	 **/
	public function addHiddenField($name, $value) {
		$this->addNode(array(
			'type' => 'hidden',
			'name' => $name,
			'value' => $value,
		));
	}

	/**
	 * Convenience function for adding a hidden field that indicates to which
	 * URL redirect after a succesfull form processing.
	 **/
	public function setRedirect($url) {
		$key = 'form_redirect';

		if (isset($this->values[$key]))
			$this->values[$key] = $url;
		else
			$this->addHiddenField($key, $url);
	}

	protected function init() {

	}
}

?>
*** FormHandler.php
<?php

const NO_ERROR = 0;

abstract class FormHandler {

	abstract function validate();
	abstract function process();

	private $invalid_fields;
	private $validated;

	function __construct() {
		$this->setForm(null);
	}

	public function setForm($form) {
		$this->form = $form;
		$this->validated = false;
		$this->invalid_fields = array();
	}

	protected function form() {
		return $this->form;
	}

	function invalidFields() {
		return $this->invalid_fields;
	}

	function isValid() {
		return count($this->invalid_fields) == 0;
	}

	function runProcedure($procedure) {
		$this->invalid_fields = array();

		foreach ($procedure as $phase) {
			$params = array();

			if (!is_array($phase['fields']))
				$phase['fields'] = array($phase['fields']);

			if (!isset($phase['params']))
				$phase['params'] = array();

			foreach ($phase['fields'] as $p)
				$params[] = $this->form()->value($p);

			foreach ($phase['params'] as $p)
				$params[] = $p;

			$function = 'validate' . ucfirst($phase['type']);

			$status = call_user_func_array('FormHandler::' . $function, $params);

			if ($status != NO_ERROR)
				$this->invalid_fields = array_merge($this->invalid_fields, $phase['fields']);
		}

		$this->validated = true;

		return count($this->invalid_fields) == 0;
	}

	static function validateEmail($value) {
		return FormHandler::validateRegex($value, '/^[\w_\.]+@[\w]+\.[\w]+$/')
			== NO_ERROR ? NO_ERROR : 5;
	}

	static function validatePassword($value1, $value2=null) {
		if (strlen($value1) < 8)
			return 2;

		if (!is_null($value2) && $value1 != $value2)
			return 3;

		return NO_ERROR;
	}

	static function validateRegex($value, $regex) {
		return preg_match($regex, $value) ? NO_ERROR : 31;
	}

	static function validateNumber($value) {
		return ctype_digit($value) ? NO_ERROR : 101;
	}

	static function validateString($string, $minLength=0, $maxLength=0) {
		return self::validateLength($string, $minLength, $maxLength);
	}

	static function validateLength($value, $minimum, $maximum=0) {
		$len = strlen($value);

		return $len < $minimum ? 102 : ($len <= $maximum || !$maximum ? NO_ERROR : 103);
	}
}

?>
*** MyForm.php
<?php

class MyForm extends Form {

	function __construct($id, $attrs=array()) {
		parent::__construct($id, $attrs);

		$this->addNode(array(
			'id' => 'username',
			'label' => _('Username'),
			'required' => true,
		));

		$this->addNode(array(
			'name' => 'password',
			'label' => _('Password'),
			'type' => 'password',
			'required' => true,
		));

		$this->addNode(array(
			'name' => 'password2',
			'label' => _('Password again'),
			'type' => 'password',
			'required' => true,
		));

		$this->addNode(array(
			'name' => 'email',
			'label' => _('Email address'),
		));

		$this->addNode(array(
			'name' => 'birthday',
			'label' => _('Birthday'),
		));

		$this->addNode(array(
			'name' => 'shoe',
			'label' => _('Shoe size'),
		));

		$this->addNode(array(
			'type' => 'textarea',
			'name' => 'description',
			'label' => _('Informal description'),
			'attributes' => array('rows' => 6, 'cols' => 15),
		));

		$this->setHandler(new MyHandler());
	}
}

?>
*** MyHandler.php
<?php

class MyHandler extends FormHandler {

	function validate() {
		$procedure = array(
			array(
				'fields' => 'username',
				'type' => 'string',
				'params' => array(3, 32),
			),

			array(
				'fields' => array('password', 'password2'),
				'type' => 'password',
				'trim' => false,
			),

			array(
				'fields' => 'email',
				'type' => 'email',
			),

			array(
				'fields' => 'birthday',
				'type' => 'regex',
				'params' => array('/^\d{4}-\d{2}-\d{2}$/'),
			),

			array(
				'fields' => 'shoe',
				'type' => 'number',
			),

			array(
				'fields' => 'description',
				'type' => 'string',
			),

		);

		return $this->runProcedure($procedure);
	}

	function process() {
		return true;
	}
}

?>
*** FormView.php
<?php

class FormView {

	private $form;

	function __construct() {

	}

	function setForm(Form $form) {
		$this->form = $form;
	}

	function form() {
		return $this->form;
	}

	function show() {

		$id = $this->form->id();
		$action = $this->form->action();
		$method = $this->form->method();

		printf(
			'<form id="%s" action="%s" method="%s">',
			$this->form()->id(),
			$this->form()->action(),
			$this->form()->method()
		);

		$nodes = $this->form->nodes();

		foreach ($nodes as $group => $fields) {
			$tmp = '';

			foreach ($fields as $field)
				$tmp .= $this->renderFormField($field);

			if ($group != '_TOP_') {
				$tmp = <<<BLOCK

<fieldset>
	<legend>{$group}</legend>
	{$tmp}
</fieldset>

BLOCK;
			}

			print $tmp;
		}

		print '
				<div class="form-buttons">
					<button type="submit">Send</button>
				</div>
			</form>
		';
	}

	/**
	 * Renders a form element into HTML
	 *
	 * @param $data array of specs for the element
	 * @return HTML for the element
	 **/
	protected function renderFormField($data) {
		$type = $data['type'];
		$id = isset($data['id']) ? $data['id'] : '';
		$label = Util::escapeHtml($data['label']);
		$value = Util::escapeHtml($data['value']);
		$html = '';
		$class = 'field';
		$name = $data['name'];

		$label_text = $label;

		if ($data['required'])
			$class .= ' required-field';

		if (!empty($data['error']))
			$class .= ' error';

		$class = trim($class);

		if (!in_array($type, array('radio', 'checkbox', 'hidden'))) {
			$html .= "<label for=\"{$id}\"";

			if (!empty($class))
				$html .= " class=\"{$class}\"";

			$html .= ">{$label_text}</label>" . PHP_EOL;
		}

		switch ($type) {
			case 'text':
			case 'radio':
			case 'checkbox':
			case 'password':

				$name = $data['name'];

				if ($type == 'checkbox')
					$name .= '[]';

				if ($type == 'text' || $type == 'password') {
					$html .= sprintf(
						'<input title="Input Field" type="%s" name="%s" id="%s"',
						$type, $name, $id
					);

					if (!empty($class))
						$html .= sprintf(' class="%s"', $class);

					if (!empty($value))
						$html .= sprintf(' value="%s"', $value);

					$html .= '/>' . PHP_EOL;
				} else {
					$i = 1;

					foreach ($data['options'] as $val => $label) {
						$val = Util::escapeHtml($val);
						$label = Util::escapeHtml($label);
						$for = $id . '-' . $i;
						$check = Form::valueMatches($val, $value) ? ' checked="checked"' : '';

						$html .= <<<BLOCK

<span class="tick-wrap">
	<label for="{$for}">{$label}</label>
	<input type="{$type}" name="{$name}" id="{$for}" value="{$val}" {$check}/>
</span>

BLOCK;
						$i++;
					}
				}

				break;

			case 'textarea':
				$html .= "<textarea name=\"{$id}\" id=\"{$id}\"";

				if (!empty($class))
					$html .= " class=\"{$class}\"";

				$html .= ">{$value}</textarea>";
				break;

			case 'select':
				$html .= "<select name=\"{$id}\" id=\"{$id}\">" . PHP_EOL;

				foreach ($data['options'] as $val => $label) {
					$val = Util::escapeHtml($val);
					$name = Util::escapeHtml($label);

					$check = Form::valueMatches($val, $value) ? 'selected="selected"' : '';
					$html .= "<option value=\"{$val}\" {$check}>{$label}</option>" . PHP_EOL;
				}

				$html .= '</select>' . PHP_EOL;

				break;

			case 'hidden':

				if (is_array($value))
					$name .= '[]';

				else
					$value = array($value);

				foreach ($value as $val)
					$html .= "<input type=\"hidden\" name=\"{$name}\" value=\"{$val}\"/>" . PHP_EOL;

				break;
		}

		if (!empty($data['error'])) {
			ob_start();
			$this->html('input_error');

			$html .= ob_get_contents();
			ob_end_clean();
		}

		return $html;
	}

	protected function renderError($message) {
		return '<div class="error-message">' . $message . '</div>' . PHP_EOL;
	}


}

class Util {

	/**
	 * Prepares a string or an array of values to be printed.
	 *
	 * @param $value scalar or array value
	 * @return printable version of given value
	 **/
	static function escapeHtml($value) {
		if (is_array($value)) {
			foreach ($value as $key => $val)
				$value[$key] = htmlspecialchars($val);
		} else
			$value = htmlspecialchars($value);

		return $value;
	}

}

?>
*** test.php
<?php

$classes = array(
	'Form',
	'FormHandler',
	'FormView',
	'MyHandler',
	'MyForm',
	'FormFactory',
);

foreach ($classes as $c) {
	require sprintf('classes/%s.php', $c);
}

session_start();

$form = FormFactory::form('TestForm', 'MyForm');
$view = new FormView();
$view->setForm($form);

$form->readUserInput();
$form->saveSession();

if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'submit_form')
	var_dump($form->validate());
?>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTX XHTML 1.1//EN"
	"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
	<title>Forms2</title>

	<style type="text/css">

	form {
		width: 30em;
		border: 4px solid red;
	}

	label {
		width: 10em;
		display: inline-block;
		clear: left;
	}

	input {
		width: 15em;
	}

	.error-message {
		color: red;
		font-weight: bold;
	}

	</style>
</head>
<body>

<div id="wrap">
	<?php $view->show() ?>
</div>

</body>
</html>

qeijo [28.12.2011 21:00:53]

#

Pikkusen turha räpellys niin suoraviivaisen toiminnon tähden.

The Alchemist [28.12.2011 21:07:36]

#

qeijo kirjoitti:

Pikkusen turha räpellys niin suoraviivaisen toiminnon tähden.

Oman systeemini pointti ei ole lomakkeen validoinnissa vaan ihan jossain muualla.

latenleffahylly [28.12.2011 23:43:57]

#

Huh mikä pätkä koodia.. nähtävästi olen PHP:n osalta tasolla -1 tai jopa enemmän. Tavallaan tarkoitin, että olisi hieno nähdä miten ammattilainen tekisi lomakkeeni oikein (tehokkaasti/minimaalinen koodi)

..mutta olenhan jo melkein loppusuoralla. Alkemistin koodi vaikuttaa sellaiselta reseptiltä, että ties millainen lomake syntyy, ties vaikka veisi käyttäjän mukanaan. No anyway, toivottavasti perjantaina lomakkeeni lopullisesti valmis, niin tämän topiicin voi clousata, vaikka luultavasti laitan lomakkeen tänne vielä kaikkien haukuttavaksi ;) hauskaa iltaa kaikille!


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta