Diferencia entre revisiones de «PHP Paso de datos con formularios»

De MediaWiki
Ir a la navegación Ir a la búsqueda
 
(No se muestran 39 ediciones intermedias del mismo usuario)
Línea 1: Línea 1:
 
== Introdución ==
 
== Introdución ==
  
−
* Sabemos que a través da etiqueta <form> podemos enviar datos dunha páxina a outra.
+
* A través da etiqueta <code><form></code> podemos enviar datos dunha páxina a outra.
−
: Os principais elementos html que poden enviar información dentro dun formulario son [https://www.w3schools.com/html/html_form_elements.asp os indicados neste enlace].
+
: Os principais elementos HTML que poden enviar información dentro dun formulario son [https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/form os elementos asociados aos formularios HTML].
  
 +
* Dentro da etiqueta <code>form</code> temos, entre outros:
 +
:* O atributo '''method''', que normalmente toma os valores '''get''' ou '''post'''.
 +
:* O atributo '''action''', que indica a URL que vai procesar os datos enviados polo formulario.
  
−
* Dentro da etiqueta form temos:
+
* Se queremos que o formulario se procese na mesma páxina podemos deixar o atributo <code>action</code> baleiro:
−
:* O atributo '''method''' que pode tomar os valores '''get''' ou '''post'''.
 
−
:* O atributo '''action''' que indica a páxina que ten que procesar os datos enviados polo formulario. Aquí poñeremos unha páxina php e moitas veces será a mesma páxina que ten o formulario, polo que podemos empregar como action: $_SERVER['PHP_SELF'] da forma:
 
−
:::<syntaxhighlight lang="php" line enclose="div" highlight="1" >
 
−
<form name="form1" action="<?php echo $_SERVER['PHP_SELF']; ?>" method="POST">
 
  
−
</form>
+
::<syntaxhighlight lang="html" line enclose="div" highlight="1">
 +
<form name="form1" action="" method="post">
 +
 
 +
</form>
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 +
: Outra posibilidade é empregar <code>$_SERVER['PHP_SELF']</code>. Neste caso debemos escapar o seu valor antes de introducilo no HTML:
  
 +
::<syntaxhighlight lang="php" line enclose="div" highlight="1">
 +
<form name="form1"
 +
      action="<?php echo htmlspecialchars($_SERVER['PHP_SELF'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?>"
 +
      method="post">
  
−
* <u>Nota:</u> Para pasar datos dunha páxina a outra tamén podemos empregar formularios con campos ocultos (hidden).
+
</form>
 +
</syntaxhighlight>
  
−
== Método GET ==
+
* <u>Importante:</u> os datos recibidos do cliente non deben considerarse fiables. Isto é válido tanto para GET como para POST. A validación HTML ou JavaScript mellora a experiencia de usuario, pero a validación de seguridade debe facerse tamén no servidor.
  
−
* Neste caso os datos son enviados na URL e pasan a páxina indicada polo etiqueta '''action''' do formulario no seguinte formato: http://paxinadestino.php?param1=valor&param2=valor&.....
+
* <u>Nota:</u> Para pasar datos dunha páxina a outra tamén podemos empregar campos ocultos (<code>hidden</code>). Un campo oculto non é secreto: o usuario pode velo e modificalo, polo que tamén debe validarse no servidor.
−
* Se vos fixades cando facedes unha busca en google (por exemplo buscade 'php') na url aparecerá algo coma isto: https://www.google.es/search?q=php&ie=utf-8&oe=utf-8&client=firefox-b&gfe_rd=cr&ei=a1XgV4CmOu6s8wfuibOQAQ
 
−
: Os datos que aparecen despois do signo '?' son parámetros:?'''q'''=php&'''ie'''=utf-8&'''oe'''=utf-8
 
  
  
−
* '''Desvantaxes:'''
+
== Método GET ==
  
−
:* Calquera persoa pode ver os valores das variables polo que dito método non é moi seguro.
+
* Co método GET os datos envíanse como parámetros na URL. Por exemplo:
−
:* O usuario pode cambiar o valor de ditas variables polo que pode suceder que o sitio amose ou faga algo diferente do permitido.
 
−
:* O usuario pode obter información non actual se usa unha URL con datos non actualizados.
 
−
:* O tamaño do que podemos enviar na URL está limitado (depende de cada navegador, pero pode estar entre 2KB-8KB)
 
  
−
* '''Vantaxes:'''
+
::<syntaxhighlight lang="text">
 +
https://exemplo.test/buscar.php?q=php&orde=asc
 +
</syntaxhighlight>
  
−
:* A través da URL podemos pasar datos dunha páxina a outra sen necesidade de ter un formulario por medio.  
+
: Os datos situados despois do signo <code>?</code> forman a cadea de consulta ou ''query string''. Os diferentes parámetros sepáranse normalmente mediante <code>&</code>.
  
 +
* '''Características do método GET:'''
 +
:* Os parámetros son visibles na URL.
 +
:* A URL pode gardarse nos marcadores, copiarse ou compartirse conservando os parámetros.
 +
:* Os parámetros poden quedar rexistrados no historial do navegador, nos logs do servidor e noutros sistemas intermedios.
 +
:* O usuario pode modificar libremente os valores enviados. Polo tanto, '''os datos GET sempre deben validarse no servidor'''.
 +
:* Non existe un límite único de tamaño establecido por PHP para unha URL completa; os límites prácticos dependen do navegador, do servidor web, dos proxies e da infraestrutura empregada.
 +
:* GET é apropiado principalmente para operacións de consulta que non deberían modificar o estado da aplicación.
  
 +
* As variables enviadas deste xeito están dispoñibles en PHP mediante o array superglobal [https://www.php.net/manual/es/reserved.variables.get.php $_GET].
  
−
* As variables que se pasan desta forma poden ser referenciadas en PHP a través da matriz global [http://php.net/manual/es/reserved.variables.get.php $_GET['param']].
+
* Vexamos un exemplo utilizando un formulario.
  
 +
'''Arquivo: UD2_Form_Ex1_eleccion_pelicula.php'''
  
−
* Vexamos agora un exemplo <u>utilizando un formulario</u>:
+
::<syntaxhighlight lang="html" line enclose="div">
 +
<!DOCTYPE html>
 +
<html lang="gl">
 +
<head>
 +
    <meta charset="UTF-8">
 +
    <title>Formulario</title>
 +
</head>
 +
<body>
 +
    <form id="frmPelicula" method="get" action="UD2_Form_Ex1_datos_pelicula.php">
 +
        <div>
 +
            <label for="lstPelicula">Película preferida:</label>
 +
            <select id="lstPelicula" name="lstPelicula">
 +
                <option value="ET">E.T. O extraterrestre</option>
 +
                <option value="Superman">Supermán</option>
 +
            </select>
 +
        </div>
  
−
'''Arquivo: UD2_Form_Ex1_eleccion_pelicula.php'''
+
        <div>
−
::<syntaxhighlight lang="php" line enclose="div" highlight="27" >
+
            <label for="lstXenero">Xénero preferido:</label>
−
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+
            <select id="lstXenero" name="lstXenero">
−
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
                 <option value="Acción">Acción</option>
−
<html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
+
                 <option value="Ciencia ficción">Ciencia ficción</option>
−
    <head>
+
             </select>
−
        <meta charset="utf-8" />
+
        </div>
−
        <title>Formulario</title>
 
−
        <style>
 
−
            .formularios{
 
−
                width: 30%;
 
−
                margin: 0px auto;
 
−
            }
 
−
            .etiqueta{
 
−
                float: left;
 
−
                width: 190px;
 
−
            }
 
−
            .botonesformularios{
 
−
                width: 50%;
 
−
                margin: 30px auto;
 
−
                text-align: center;
 
−
            }
 
−
            select {
 
−
                width:50%;
 
−
            }
 
−
        </style>
 
−
    </head>
 
−
    <body>
 
−
        <form id="frmPelicula" class="formularios" method="get" action="UD2_Form_Ex1_datos_pelicula.php">
 
−
            <div>   
 
−
                 <div class='etiqueta'>Película preferida:</div>
 
−
                <select name="lstPelicula"><option>E.T. El extraterrestre</option><option>Supermán</option></select>
 
−
            </div>               
 
−
            <div>
 
−
                 <div class='etiqueta'>Xénero preferido:</div>
 
−
                <select name="lstXenero"><option>Acción</option><option>Ciencia ficción</option></select>
 
−
             </div>              
 
−
            <div class="botonesformularios">
 
−
                <input type='submit' value="ENVIAR" />
 
−
            </div>
 
−
        </form>
 
  
−
     </body>  
+
        <button type="submit">ENVIAR</button>
 +
     </form>
 +
</body>
 
</html>
 
</html>
−
</php>
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
−
:* Fixarse que enviamos os datos utilizando o '''método get'''.
+
:* Fixarse en que enviamos os datos utilizando o '''método GET'''.
  
 
'''Arquivo: UD2_Form_Ex1_datos_pelicula.php'''
 
'''Arquivo: UD2_Form_Ex1_datos_pelicula.php'''
−
::<syntaxhighlight lang="php" line enclose="div" highlight="13,14" >
 
−
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 
−
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
  
−
<html>
+
::<syntaxhighlight lang="php" line enclose="div">
−
    <head>
+
<!DOCTYPE html>
−
        <meta charset="UTF-8">
+
<html lang="gl">
−
        <title></title>
+
<head>
−
    </head>
+
    <meta charset="UTF-8">
−
    <body>
+
    <title>Datos da película</title>
−
        <?php
+
</head>
−
        echo 'Os datos que escolliches son:<br/><br/>';
+
<body>
−
       
+
<?php
−
        $pelicula = $_GET['lstPelicula'];
+
$pelicula = $_GET['lstPelicula'] ?? '';
−
        $xenero = $_GET['lstXenero'];
+
$xenero = $_GET['lstXenero'] ?? '';
  
−
        printf("PELICULA:%s<br/>XÉNERO:%s",$pelicula,$xenero);
+
$peliculaHtml = htmlspecialchars($pelicula, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
−
        ?>
+
$xeneroHtml = htmlspecialchars($xenero, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
−
       
+
 
−
    </body>
+
echo 'Os datos que escolliches son:<br><br>';
 +
printf('PELÍCULA: %s<br>XÉNERO: %s', $peliculaHtml, $xeneroHtml);
 +
?>
 +
</body>
 
</html>
 
</html>
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 +
* <u>Nota:</u> O operador <code>??</code> permite indicar un valor alternativo cando unha clave non existe. Deste xeito evitamos acceder directamente a unha clave inexistente de <code>$_GET</code>.
 +
 +
* <u>Nota:</u> Para visualizar durante o desenvolvemento o conxunto de valores recibidos pódese empregar [https://www.php.net/manual/es/function.var-dump.php var_dump()]:
  
 +
::<syntaxhighlight lang="php">
 +
var_dump($_GET);
 +
</syntaxhighlight>
  
  
 
== Método POST ==
 
== Método POST ==
  
−
* No método post de envío de datos, os mesmos van 'dentro' da páxina web, no corpo do formulario, e polo tanto non son visibles para o usuario.
+
* Co método POST os datos do formulario envíanse no corpo da petición HTTP e non forman parte da URL.
 +
 
 +
* Isto '''non significa que POST sexa secreto nin que os seus datos sexan fiables'''. Un usuario pode inspeccionar e modificar unha petición POST empregando as ferramentas de desenvolvemento do navegador, <code>curl</code>, Postman ou outras ferramentas. Para protexer os datos durante o transporte debe empregarse HTTPS.
  
−
* Tamén está limitado o tamaño do que se pode enviar (por exemplo se enviamos un arquivo adxunto) a un tamaño de 8MB.
+
* O tamaño máximo dos datos recibidos mediante POST está condicionado, entre outras cousas, pola directiva [https://www.php.net/manual/es/ini.core.php#ini.post-max-size post_max_size] de PHP. O seu valor por defecto é 8M, pero pode ser modificado pola configuración do servidor.
−
: Podemos cambiar dito límite:
 
−
::* Modificando a directiva [http://php.net/manual/es/ini.core.php#ini.post-max-size post_max_size] do arquivo php.ini de PHP.
 
−
::* Modificando o arquivo .htaccess do noso sitio web e engadindo a directa: php_value post_max_size 20M (isto é necesario no caso de aloxamentos compartidos ou se non temos acceso ao arquivo php.ini).
 
  
−
: <u>Nota:</u> No caso de subir arquivos tamén pode ser necesario modificar a [http://php.net/manual/es/ini.core.php#ini.upload-max-filesize directiva upload_max_filesize] do arquivo php.ini
+
: No caso da subida de arquivos tamén intervén [https://www.php.net/manual/es/ini.core.php#ini.upload-max-filesize upload_max_filesize], cuxo valor por defecto é 2M. Para permitir arquivos de maior tamaño, <code>post_max_size</code> debe ser maior que <code>upload_max_filesize</code>.
−
: Podedes ver [http://angelfernandezgonzalez.blogspot.com.es/2015/10/subir-archivos-de-mas-de-8mb-en.html neste enlace] como aumentar o tamaño dos arquivos subidos nun aloxamento compartido.
 
  
−
+
* A forma de modificar estas directivas depende de como estea instalado PHP. O arquivo <code>.htaccess</code> só permite empregar directivas <code>php_value</code> cando PHP funciona como módulo de Apache e a configuración do servidor o permite. Con CGI/FastCGI ou PHP-FPM poden existir outros mecanismos, por exemplo <code>.user.ini</code>, segundo a configuración do servidor.
−
* As variables que se pasan desta forma poden ser referenciadas en PHP a través da matriz global [http://php.net/manual/es/reserved.variables.post.php $_POST['param']].
 
  
 +
* As variables enviadas mediante POST están dispoñibles no array superglobal [https://www.php.net/manual/es/reserved.variables.post.php $_POST].
  
−
* Modificamos o exemplo anterior para que envíe os datos con POST:
+
* Modificamos o exemplo anterior para que empregue POST.
  
 
'''Arquivo: UD2_Form_Ex2_eleccion_pelicula.php'''
 
'''Arquivo: UD2_Form_Ex2_eleccion_pelicula.php'''
−
::<syntaxhighlight lang="php" line enclose="div" highlight="27" >
 
−
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 
−
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
−
<html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
 
−
    <head>
 
−
        <meta charset="utf-8" />
 
−
        <title>Formulario</title>
 
−
        <style>
 
−
            .formularios{
 
−
                width: 30%;
 
−
                margin: 0px auto;
 
−
            }
 
−
            .etiqueta{
 
−
                float: left;
 
−
                width: 190px;
 
−
            }
 
−
            .botonesformularios{
 
−
                width: 50%;
 
−
                margin: 30px auto;
 
−
                text-align: center;
 
−
            }
 
−
            select {
 
−
                width:50%;
 
−
            }
 
−
        </style>
 
−
    </head>
 
−
    <body>
 
−
        <form id="frmPelicula" class="formularios" method="post" action="UD2_Form_Ex2_datos_pelicula.php">
 
−
            <div>   
 
−
                <div class='etiqueta'>Película preferida:</div>
 
−
                <select name="lstPelicula"><option>E.T. El extraterrestre</option><option>Supermán</option></select>
 
−
            </div>               
 
−
            <div>
 
−
                <div class='etiqueta'>Xénero preferido:</div>
 
−
                <select name="lstXenero"><option>Acción</option><option>Ciencia ficción</option></select>
 
−
            </div>               
 
−
            <div class="botonesformularios">
 
−
                <input type='submit' value="ENVIAR" />
 
−
            </div>
 
−
        </form>
 
  
−
     </body>  
+
::<syntaxhighlight lang="html" line enclose="div">
 +
<!DOCTYPE html>
 +
<html lang="gl">
 +
<head>
 +
    <meta charset="UTF-8">
 +
    <title>Formulario</title>
 +
</head>
 +
<body>
 +
    <form id="frmPelicula" method="post" action="UD2_Form_Ex2_datos_pelicula.php">
 +
        <div>
 +
            <label for="lstPelicula">Película preferida:</label>
 +
            <select id="lstPelicula" name="lstPelicula">
 +
                <option value="ET">E.T. O extraterrestre</option>
 +
                <option value="Superman">Supermán</option>
 +
            </select>
 +
        </div>
 +
 
 +
        <div>
 +
            <label for="lstXenero">Xénero preferido:</label>
 +
            <select id="lstXenero" name="lstXenero">
 +
                <option value="Acción">Acción</option>
 +
                <option value="Ciencia ficción">Ciencia ficción</option>
 +
            </select>
 +
        </div>
 +
 
 +
        <button type="submit">ENVIAR</button>
 +
     </form>
 +
</body>
 
</html>
 
</html>
−
</php>
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
−
:* Fixarse que enviamos os datos utilizando o '''método post'''.
+
'''Arquivo: UD2_Form_Ex2_datos_pelicula.php'''
  
−
'''Arquivo: UD2_Form_Ex2_datos_pelicula.php'''
+
::<syntaxhighlight lang="php" line enclose="div">
−
::<syntaxhighlight lang="php" line enclose="div" highlight="13,14" >
+
<!DOCTYPE html>
−
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+
<html lang="gl">
−
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
<head>
 +
    <meta charset="UTF-8">
 +
     <title>Datos da película</title>
 +
</head>
 +
<body>
 +
<?php
 +
$pelicula = $_POST['lstPelicula'] ?? '';
 +
$xenero = $_POST['lstXenero'] ?? '';
  
−
<html>
+
$peliculaHtml = htmlspecialchars($pelicula, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
−
    <head>
+
$xeneroHtml = htmlspecialchars($xenero, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
−
        <meta charset="UTF-8">
 
−
        <title></title>
 
−
    </head>
 
−
    <body>
 
−
        <?php
 
−
        echo 'Os datos que escolliches son:<br/><br/>';
 
−
       
 
−
        $pelicula = $_POST['lstPelicula'];
 
−
        $xenero = $_POST['lstXenero'];
 
  
−
        printf("PELICULA:%s<br/>XÉNERO:%s",$pelicula,$xenero);
+
echo 'Os datos que escolliches son:<br><br>';
−
        ?>
+
printf('PELÍCULA: %s<br>XÉNERO: %s', $peliculaHtml, $xeneroHtml);
−
       
+
?>
−
    </body>
+
</body>
 
</html>
 
</html>
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 +
* <u>Nota:</u> Durante o desenvolvemento podemos comprobar os datos recibidos con:
 +
 +
::<syntaxhighlight lang="php">
 +
var_dump($_POST);
 +
</syntaxhighlight>
 +
  
 
== Comprobando se hai datos ==
 
== Comprobando se hai datos ==
  
−
* Normalmente a páxina que recibe os datos vai facer algún tipo de tratamento con eles (por exemplo, facer unha alta nunha táboa dunha base de datos, consultar en base aos criterios recibidos,...)
+
* A páxina que recibe un formulario normalmente vai validar os datos e realizar algunha operación con eles. Por exemplo, pode facer unha consulta, gardar información ou modificar datos existentes.
 +
 
 +
* A validación no cliente mediante HTML ou JavaScript é útil, pero '''non substitúe a validación no servidor'''.
  
−
* Polo tanto será necesario realizar algún tipo de validación para comprobar se se envían os datos necesarios e estes teñen un formato correcto.
+
=== Comprobando o método da petición ===
−
: Lembrar repasar [http://wiki.cifprodolfoucha.es/index.php?title=PHP_Funci%C3%B3ns_para_xerar_c%C3%B3digo._Constantes_e_variables#Comprobaci.C3.B3n_de_definici.C3.B3n_e_asignaci.C3.B3n as funcións que comproban se un campo ten datos] e [http://wiki.cifprodolfoucha.es/index.php?title=PHP_Funci%C3%B3ns_para_xerar_c%C3%B3digo._Constantes_e_variables#Comprobaci.C3.B3n_do_tipo_de_dato as funcións que comproban o tipo de dato dunha variable].
 
  
 +
* Unha forma segura de saber se unha páxina foi enviada mediante POST é consultar <code>$_SERVER['REQUEST_METHOD']</code>:
  
 +
::<syntaxhighlight lang="php" line enclose="div">
 +
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
 +
    exit('Non se enviou o formulario mediante POST');
 +
}
 +
</syntaxhighlight>
  
−
* O primeiro que teríamos que comprobar é se o formulario recibe datos. Para iso utilizaremos as funcións vistas [https://wiki.cifprodolfoucha.es/index.php?title=PHP_Constantes_e_variables._%C3%81mbito.#Comprobaci.C3.B3n_de_definici.C3.B3n_e_asignaci.C3.B3n nunha sección anterior da wiki].
+
* Se queremos saber se chegou un campo concreto podemos utilizar <code>isset()</code>, <code>array_key_exists()</code> ou o operador <code>??</code>, segundo o caso:
  
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
+
::<syntaxhighlight lang="php" line enclose="div">
 +
$nome = $_POST['txtNome'] ?? '';
 +
 
 +
if ($nome === '') {
 +
    echo 'O campo nome non ten datos';
 +
}
 +
</syntaxhighlight>
 +
 
 +
* <u>Nota:</u> [https://www.php.net/manual/es/function.exit.php exit] e <code>die</code> finalizan a execución do script.
 +
 
 +
=== Tipo dos datos recibidos ===
 +
 
 +
* Os valores simples procedentes dos campos dun formulario chegan normalmente como cadeas de caracteres. Por exemplo, aínda que un <code>input</code> conteña o texto <code>25</code>, o valor recibido será unha cadea.
 +
 
 +
* Non obstante, <code>$_POST</code> tamén pode conter arrays se empregamos nomes do tipo <code>campo[]</code> ou estruturas como <code>usuario[nome]</code>. Os arquivos subidos non aparecen en <code>$_POST</code>, senón en <code>$_FILES</code>.
 +
 
 +
* Por este motivo non debemos facer un ''cast'' antes de validar o dato. Primeiro comprobamos que a cadea ten o formato esperado e despois facemos a conversión.
 +
 
 +
: Exemplo para unha idade formada só por díxitos:
 +
 
 +
::<syntaxhighlight lang="php" line enclose="div">
 +
$idadeTexto = $_POST['txtIdade'] ?? '';
 +
 
 +
if (is_string($idadeTexto) && ctype_digit($idadeTexto)) {
 +
    $idade = (int) $idadeTexto;
 +
} else {
 +
    echo 'A idade non é un número enteiro válido';
 +
}
 +
</syntaxhighlight>
  
−
if (empty($_GET)) die('Non se enviaron datos');  // Isto é válido para action=get no formulario
+
* Tamén podemos empregar filtros de validación, como veremos máis adiante.
−
if (!isset($_POST["enviar"]))  die('Non se enviaron datos');  // Isto é válido para action=post no formulario, sendo enviar o nome do botón que xera o action do formulario
 
  
−
</syntaxhighlight>
+
=== Comprobando se chegan datos de varios formularios diferentes á mesma páxina ===
  
−
<u>Nota:</u> A [http://www.w3schools.com/php/func_misc_die.asp función die] ao igual que a [http://php.net/manual/es/function.exit.php función exit] finaliza a páxina php.
+
* Se varios formularios envían datos á mesma páxina é preferible identificar explicitamente a orixe cun campo oculto, en lugar de depender do botón de envío. O botón pode non aparecer na petición nalgúns modos de envío, por exemplo ao premer Intro nun campo.
  
−
: O tratamento que queiramos darlle á páxina cando non se envíen datos será decidido polo programador, pudendo enviarlle un aviso ao usuario en enviando de novo a páxina do formulario.
+
'''Formulario da primeira páxina:'''
−
: Tede que ter en conta que aínda que fagades unha validación dos campos utilizando javascript, isto pódese saltar, xa que a páxina está no cliente. Ademais un usuario podería cargar a páxina de tratamento da información (a que está indicada no action do formulario) sen pasar polo formulario.
 
  
 +
::<syntaxhighlight lang="html">
 +
<form method="post" action="terceira.php">
 +
    <input type="hidden" name="formulario" value="primeira">
 +
    ...
 +
    <button type="submit">Enviar</button>
 +
</form>
 +
</syntaxhighlight>
  
−
* O segundo será verificar que os campos teñan algún valor (se é obrigatorio) e que o tipo de dato sexa o correcto. Para verificar o tipo de dato coas funcións is_XXXXX (coma por exemplo is_string) non se poden utilizar con datos que veñan a través dun formulario, xa que todos eles son avaliados como de tipo String.
+
'''Formulario da segunda páxina:'''
−
: É dicir, un dato que garde a idade, por exemplo, se teño a variable $idade=3; e aplico a función is_string($idade) vai devolver false, pero se o dato da idade (valor 3) ven dun formulario e aplico a mesma función is_string($_POST['txtIdade']) vai devolver true.
 
  
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
+
::<syntaxhighlight lang="html">
−
if (empty($_POST['txtNome'])) echo('Campo nome non ten datos');
+
<form method="post" action="terceira.php">
 +
    <input type="hidden" name="formulario" value="segunda">
 +
    ...
 +
    <button type="submit">Enviar</button>
 +
</form>
 
</syntaxhighlight>
 
</syntaxhighlight>
  
−
* <u>'''IMPORTANTE:'''</u> Todos os datos que veñen no array global $_POST son de tipo String. Teremos que facer un 'cast' ao tipo de dato ou ven utilizar a '''función filter_var''' que será explicada a continuación.
+
'''Código de terceira.php:'''
  
−
: Facendo o cast podemos saber se é un número ou non da seguinte forma:
+
::<syntaxhighlight lang="php">
 +
$orixe = $_POST['formulario'] ?? null;
  
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
+
if ($orixe === 'primeira') {
−
if (ctype_digit($_POST['txtIdade'])){
+
    // Datos enviados dende primeira.php
−
    $idade = (int)$_POST['txtIdade'];
+
} elseif ($orixe === 'segunda') {
−
}
+
    // Datos enviados dende o formulario de segunda.php
−
else {
+
} else {
−
    echo 'Non é numérico integer';
+
    // A páxina cargouse directamente ou chegou unha orixe non válida
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
−
: Usamos a [http://php.net/manual/es/function.ctype-digit.php función ctype_digit] para comprobar se temos díxitos numéricos no dato enviado.
+
* <u>Importante:</u> un campo oculto tamén pode ser modificado polo usuario. Só serve para identificar a intención do formulario; o seu valor debe comprobarse no servidor.
−
: O cast (int) vai devolver 0 en caso de que non poida convertelo a Integer.
+
 
 +
== Validación, normalización e escapado dos campos ==
  
 +
* Ao traballar con datos externos convén diferenciar tres operacións:
  
−
=== Comprobando se chegan datos de varios formularios diferentes á mesma páxina ===
+
:* '''Validación''': comprobar se un dato cumpre as regras esperadas. Por exemplo, comprobar se unha idade é un enteiro entre 0 e 130.
 +
:* '''Normalización ou saneamento''': transformar un dato cando realmente o necesitemos, por exemplo aplicar <code>trim()</code> para eliminar espazos ao principio e ao final.
 +
:* '''Escapado da saída''': adaptar un dato ao contexto no que se vai amosar. Para escribir texto dentro de HTML empregaremos normalmente <code>htmlspecialchars()</code>.
  
−
* Imaxinemos o caso de que temos unha páxina de nome primeira.php. Dita páxina ten un formulario cuxo 'action' apunta a unha segunda páxina de nome: segunda.php
+
* Non debemos asumir que un dato queda seguro para todos os contextos por aplicarlle un filtro xenérico. A protección depende do uso:
−
: Imaxinemos que dita páxina segunda.php ten a súa vez un formulario cuxo action apunta a ela mesma.
+
:* HTML → <code>htmlspecialchars()</code>.
 +
:* SQL → consultas preparadas con parámetros.
 +
:* URL → <code>rawurlencode()</code> ou <code>http_build_query()</code>.
 +
:* Números, correos, IP, etc. → validación específica.
  
−
: Neste caso temos que identificar 3 posibles casos cando carguemos a páxina segunda.php:
+
=== No cliente ===
−
:* A páxina se carga vindo do primeiro formulario. Para identificar este caso podemos facer uso do botón de tipo 'submit' e definilo da seguinte maneira:
 
−
::: <input name='btnEnviar1' type='submit' value='Envía datos páxina 1' />
 
−
:: Na páxina segunda.php podemos poñer a seguinte condición para saber se ven da primeira.php:
 
−
::: if (!empty($_POST['btnEnviar1'])) { }  => Sabemos que vimos da primeira
 
  
−
:* A páxina se carga vindo de segunda.php
+
* HTML permite realizar validacións básicas mediante atributos como:
−
:: Podemos facer algo parecido á anterior e definir un name para o botón que fai o submit dende a segunda:
+
:* <code>required</code>: indica que o campo é obrigatorio.
−
::: <input name='btnEnviar2' type='submit' value='Envía datos páxina 1' />
+
:* <code>maxlength</code>, <code>minlength</code>, <code>min</code> e <code>max</code>: limitan lonxitudes ou rangos.
−
:: Na páxina segunda.php podemos poñer a seguinte condición para saber se ven da segunda.php:
+
:* <code>type="email"</code>, <code>type="number"</code>, <code>type="date"</code>, etc.
−
::: if (!empty($_POST['btnEnviar2'])) { }  => Sabemos que vimos da segunda páxina.
+
:* <code>accept</code> nun campo de tipo <code>file</code>: axuda a indicar ao navegador os tipos de ficheiro que se queren seleccionar.
  
−
:* Se cargamos a páxina segunda.php directamente, o sabemos porque:
+
* Estas comprobacións poden ser evitadas polo usuario. Por iso deben repetirse no servidor as regras que afecten á integridade ou á seguridade da aplicación.
−
::: if (empty($_POST['btnEnviar1']) && empty($_POST['btnEnviar2'])) { }  => Sabemos que cargamos a páxina directamente sen vir dende primeira.php nin de segunda.php
 
  
−
== Función para validar / corrixir campos ==
+
=== No servidor: filter_var() ===
  
−
* '''No cliente:'''
+
* PHP dispón da función [https://www.php.net/manual/es/function.filter-var.php filter_var()], que permite validar ou sanear unha variable segundo o filtro indicado.
−
: * Para validar os campos podemos empregar javascript e algún dos atributos que teñen as etiquetas HTML.
 
−
:: Calquera das dúas formas son vulnerables de que un usuario salte as regras de validación, polo que suele ser necesario facer a validación no servidor por seguridade (ver máis adiante métodos de ataque por formularios).
 
−
:: A nivel de HTML temos as seguintes propiedaes:
 
−
::* '''required''': Impide campos baleiros.
 
−
::* '''Lonxitude de cadeas''' con atributos coma maxlength, max, min...
 
−
::* '''Atributo type''' para establecer o tipo de dato que vai levar a caixa de texto: mail, text, number, date ...
 
−
::* '''Atributo accept''' para os input de tipo File que van subir un arquivo ao servidor. Con este atributo podemos especificar o tipo de arquivos que está permitido subir (exemplo accept=".pdf"),
 
  
 +
* Sintaxe simplificada:
  
 +
::<syntaxhighlight lang="php">
 +
filter_var($valor, $filtro, $opcion);
 +
</syntaxhighlight>
  
 +
* Parámetros:
 +
:* <code>$valor</code>: dato que queremos comprobar.
 +
:* <code>$filtro</code>: constante <code>FILTER_VALIDATE_*</code> ou <code>FILTER_SANITIZE_*</code>.
 +
:* <code>$opcion</code>: opcións ou ''flags'' adicionais do filtro.
  
 +
=== Filtros para a validación ===
  
−
<br />
+
:* <code>FILTER_VALIDATE_BOOLEAN</code>: valida un valor booleano.
−
* '''No servidor:'''
+
:* <code>FILTER_VALIDATE_EMAIL</code>: valida unha dirección de correo electrónico.
 +
:* <code>FILTER_VALIDATE_FLOAT</code>: valida un número de punto flotante.
 +
:* <code>FILTER_VALIDATE_INT</code>: valida un número enteiro.
 +
:* <code>FILTER_VALIDATE_IP</code>: valida unha dirección IP.
 +
:* <code>FILTER_VALIDATE_REGEXP</code>: valida mediante unha expresión regular.
 +
:* <code>FILTER_VALIDATE_URL</code>: valida unha URL.
  
−
* O proceso de validación-corrección de campos dun formulario soe ser dar bastante traballo.
+
=== Filtros de saneamento ===
−
: Para facernos a vida un pouco máis doada, temos unha extensión en PHP que permite a validación de campos dunha forma moi sinxela.
 
  
−
* A función que fai dita validación chámase [http://php.net/manual/es/function.filter-var.php función filter_var()]
+
* Algúns filtros de saneamento que continúan dispoñibles son:
 +
:* <code>FILTER_SANITIZE_EMAIL</code>
 +
:* <code>FILTER_SANITIZE_ENCODED</code>
 +
:* <code>FILTER_SANITIZE_NUMBER_FLOAT</code>
 +
:* <code>FILTER_SANITIZE_NUMBER_INT</code>
 +
:* <code>FILTER_SANITIZE_SPECIAL_CHARS</code>
 +
:* <code>FILTER_SANITIZE_FULL_SPECIAL_CHARS</code>
 +
:* <code>FILTER_SANITIZE_URL</code>
  
−
: A función filter_var permítenos filtrar unha variable segundo o filtro indicado.  
+
* <u>Importante en PHP 8:</u>
−
: Parámetros:
+
:* <code>FILTER_SANITIZE_MAGIC_QUOTES</code> foi eliminado en PHP 8.0 e non debe empregarse.
 +
:* <code>FILTER_SANITIZE_STRING</code> e <code>FILTER_SANITIZE_STRIPPED</code> están obsoletos dende PHP 8.1. Para escapar texto que vai ser mostrado en HTML empregaremos directamente <code>htmlspecialchars()</code>.
  
−
::*$var: Variable que se quere filtrar
+
* Un filtro de saneamento non substitúe unha validación. Por exemplo, se esperamos un enteiro é preferible rexeitar <code>12abc</code> como inválido en lugar de transformalo silenciosamente en <code>12</code>.
−
::*$filter: Filtro que se desexa aplicar. Será una constante numérica (indicadas a continuación)
 
−
::*$options: Conxunto de opcións que modifican o funcionamento do filtro. Pode ser unha constante numérica ou un array
 
  
−
: Valor devolto:
+
=== Exemplos ===
  
−
:* O dato filtrado ou false se o filtro faia.
+
==== Validando ====
  
 +
* Validando un número enteiro:
  
−
=== Filtros para a validación ===
+
::<syntaxhighlight lang="php" line enclose="div">
 +
$valor = '123';
  
−
:* FILTER_VALIDATE_BOOLEAN => Valida a variable coma un booleano.
+
$enteiro = filter_var($valor, FILTER_VALIDATE_INT);
−
:* FILTER_VALIDATE_EMAIL => Valida a variable coma unha dirección de correo electrónico correcta.
 
−
:* FILTER_VALIDATE_FLOAT => Valida que a variable sexa do tipo float.
 
−
:* FILTER_VALIDATE_INT => Valida a variable coma un número enteiro.
 
−
:* FILTER_VALIDATE_IP => Valida a variable coma unha dirección IP.
 
−
:* FILTER_VALIDATE_REGEXP => Valida a variable contra unha expresión regular enviada na variable de opcións.
 
−
:* FILTER_VALIDATE_URL => Valida o valor coma unha URL de acordo coa RFC 2396.
 
  
 +
if ($enteiro === false) {
 +
    echo 'Valor incorrecto';
 +
} else {
 +
    echo "Valor correcto: $enteiro";
 +
}
 +
</syntaxhighlight>
  
−
=== Filtros para a corrección ===
+
* Validando un número flotante:
  
−
:* FILTER_SANITIZE_EMAIL => Elimina tódolos caracteres execepto letras, números e !#$%&’*+-/=?^_`{|}~@.[].
+
::<syntaxhighlight lang="php" line enclose="div">
−
:* FILTER_SANITIZE_ENCODED => Codifica a cadea coma unha URL válida.
+
$valor = '-1.45';
−
:* FILTER_SANITIZE_MAGIC_QUOTES => Aplica a [http://php.net/manual/es/function.addslashes.php función addslashes].
 
−
:* FILTER_SANITIZE_NUMBER_FLOAT => Elimina tódolos caracteres excepto números, +- e '''opcionalmente''' ,.eE Se non poñemos un flag, non admitirá números decimais.
 
−
:* FILTER_SANITIZE_NUMBER_INT => Elimina tódolos caracteres excepto números e os signos + -
 
−
:* FILTER_SANITIZE_SPECIAL_CHARS => Escapa caracteres HTML e caracteres con ASCII menor a 32.
 
  
−
:* FILTER_SANITIZE_STRING => Elimina etiquetas, opcionalmente elimina ou codifica caracteres especiais. Pode levar como terceiro parámetro algún destes flag´s:
+
$decimal = filter_var($valor, FILTER_VALIDATE_FLOAT);
−
::* FILTER_FLAG_NO_ENCODE_QUOTES - Non codifica as comillas simples ou dobres (non as substitúe por códigos &#39 => ' por exemplo)
 
−
::* FILTER_FLAG_STRIP_LOW - Elimina caracteres cun código ASCII < 32
 
−
::* FILTER_FLAG_STRIP_HIGH - Elimina caracteres cun código ASCII  > 127
 
−
::* FILTER_FLAG_ENCODE_LOW - Codifica caracteres cun código ASCII  < 32
 
−
::* FILTER_FLAG_ENCODE_HIGH - Codifica caracteres cun código ASCII  > 127
 
−
::* FILTER_FLAG_ENCODE_AMP - Codifica o carácter "&" con &amp;
 
−
:: Nota: Pódense combinar varios flag´s con |
 
  
−
:* FILTER_SANITIZE_STRIPPED => Alias do filtro anterior.
+
if ($decimal === false) {
−
:* FILTER_SANITIZE_URL => Elimina tódolos caracteres excepto números, letras y $-_.+!*’(),{}|\\^~[]`<>#%”;/?:@&=
+
    echo 'Valor incorrecto';
 +
} else {
 +
    echo "Valor correcto: $decimal";
 +
}
 +
</syntaxhighlight>
  
−
=== Exemplos ===
+
* Validando un correo electrónico:
  
−
=== Validando ===
+
::<syntaxhighlight lang="php" line enclose="div">
−
* Validando un número enteiro
+
$correo = 'usuario@example.com';
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
 
−
<?php 
 
−
    $var = 123; 
 
−
    echo filter_var($var, FILTER_VALIDATE_INT);
 
−
?>
 
−
</syntaxhighlight>
 
−
:: Devolve o número xa que é un enteiro.
 
  
−
* Validando un número enteiro
+
if (filter_var($correo, FILTER_VALIDATE_EMAIL) === false) {
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
+
    echo 'Correo incorrecto';
−
<?php 
+
} else {
−
    $var = -2.343; 
+
    echo 'Correo correcto';
−
    if(filter_var($var, FILTER_VALIDATE_INT) === false){
+
}
−
        echo 'Valor incorrecto';
+
</syntaxhighlight>
−
    }else{
 
−
        echo 'Valor correcto';
 
−
    }
 
−
?>
 
−
</syntaxhighlight>  
 
−
:: Devolve 'Valor incorrecto' xa que non é un enteiro e polo tanto a función devolve false.
 
  
−
* Validando un número enteiro
+
==== Escapando texto para HTML ====
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
 
−
<?php 
 
−
    $var = '-2'; 
 
−
    if(filter_var($var, FILTER_VALIDATE_INT) === false){ 
 
−
        echo 'Valor incorrecto'; 
 
−
    }else{ 
 
−
        echo 'Valor correcto'; 
 
−
    } 
 
−
?>
 
−
</syntaxhighlight>
 
−
:: Devolve 'Valor correcto'.
 
  
 +
* Se queremos mostrar nunha páxina un texto recibido do usuario, escapámolo no momento da saída:
  
−
* Validando un número flotante
+
::<syntaxhighlight lang="php" line enclose="div">
−
::<syntaxhighlight lang="php" line enclose="div" highlight="2,3" >
+
$texto = '<script>alert("ola")</script>';
−
<?php
 
−
    $var = '-1.45'; 
 
−
    if(filter_var($var, FILTER_VALIDATE_FLOAT) === false){ 
 
−
        echo 'Valor incorrecto'; 
 
−
    }else{ 
 
−
        echo 'Valor correcto'; 
 
−
    } 
 
−
?>
 
−
</syntaxhighlight>  
 
−
:: Devolve 'Valor correcto'.
 
  
 +
echo htmlspecialchars(
 +
    $texto,
 +
    ENT_QUOTES | ENT_SUBSTITUTE,
 +
    'UTF-8'
 +
);
 +
</syntaxhighlight>
  
−
* Validando un email
+
: O navegador mostrará o texto recibido, pero non interpretará as etiquetas como código HTML.
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
 
−
<?php 
 
−
    $var = 'casa@hotmail'; 
 
−
    if (!filter_var($var,FILTER_VALIDATE_EMAIL)){
 
−
        echo "Valor incorrecto";
 
−
    }else{ 
 
−
        echo 'Valor correcto'; 
 
−
    } 
 
−
?>
 
−
</syntaxhighlight>
 
−
:: Devolve 'Valor incorrecto'.
 
  
−
=== Corrixindo os valores ===
+
* <u>Importante:</u> <code>strip_tags()</code> e <code>htmlspecialchars()</code> non fan o mesmo. <code>strip_tags()</code> elimina etiquetas; <code>htmlspecialchars()</code> codifica os caracteres especiais para que se mostren como texto.
  
−
* Cunha serie de filtros (SANITIZE) podemos eliminar aqueles caracteres que non queremos que aparezan nun determinado campo.
+
==== Saneando un número flotante ====
  
−
* Eliminando etiquetas dentro dun text-area.
+
::<syntaxhighlight lang="php" line enclose="div">
−
::<syntaxhighlight lang="php" line enclose="div" highlight="5" >
+
$numero = '53.3pp';
−
<?php 
 
−
    $var = '<isto non está permitido> Isto si aparece';
 
−
    echo filter_var($var,FILTER_SANITIZE_STRING);
 
−
</syntaxhighlight>
 
−
:: Devolve 'Isto si aparece'.
 
  
 +
$resultado = filter_var(
 +
    $numero,
 +
    FILTER_SANITIZE_NUMBER_FLOAT,
 +
    FILTER_FLAG_ALLOW_FRACTION
 +
);
  
−
* Corrixindo un número flotante.
+
var_dump($resultado);
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
+
</syntaxhighlight>
−
<?php
 
−
$number="53.3pp";
 
  
−
var_dump(filter_var($number, FILTER_SANITIZE_NUMBER_FLOAT,
+
: Neste caso o resultado será a cadea <code>53.3</code>. Esta operación transforma o dato; non é equivalente a comprobar que o dato orixinal fose un decimal válido.
−
FILTER_FLAG_ALLOW_FRACTION));  // Este flag fai que o caracter 'punto' sexa válido (separador de decimais)
 
−
?>  
 
−
</syntaxhighlight>  
 
−
:: Devolve '53.3'.
 
  
 
=== Establecendo criterios ===
 
=== Establecendo criterios ===
  
−
* Podemos indicar que rango de valores son aceptables polo filtro.
+
* Podemos validar un enteiro dentro dun rango:
 +
 
 +
::<syntaxhighlight lang="php" line enclose="div">
 +
$valor = 25;
  
−
* Validando un número enteiro dentro dun rango
+
$opcion = [
−
::<syntaxhighlight lang="php" line enclose="div" highlight="5,6" >
+
     'options' => [
−
<?php 
 
−
    $var = -200; 
 
−
     $opciones = array(
 
−
        'options' => array(
 
 
         'min_range' => 10,
 
         'min_range' => 10,
−
         'max_range' => 100
+
         'max_range' => 100,
−
     ));
+
     ],
−
     
+
];
−
    if(filter_var($var, FILTER_VALIDATE_INT, $opciones) === false){
+
 
−
        echo 'Valor incorrecto';
+
$resultado = filter_var($valor, FILTER_VALIDATE_INT, $opcion);
−
    }else{
+
 
−
        echo 'Valor correcto';
+
if ($resultado === false) {
−
    }
+
    echo 'Valor incorrecto';
−
?>
+
} else {
−
</syntaxhighlight>  
+
    echo "Valor correcto: $resultado";
−
:: Devolve 'Valor incorrecto'
+
}
 +
</syntaxhighlight>
 +
 
 +
* Tamén podemos indicar un valor por defecto:
  
 +
::<syntaxhighlight lang="php" line enclose="div">
 +
$valor = -200;
  
−
* Podemos facer unha modificación para que en caso incorrecto devolva sempre un valor 'por defecto':
+
$opcion = [
−
::<syntaxhighlight lang="php" line enclose="div" highlight="5" >
+
     'options' => [
−
<?php 
+
         'default' => 3,
−
    $var = -200; 
 
−
     $opciones = array(
 
−
        'options' => array(
 
−
         'default' => 3, // valor a retornar se o filtro faiaa
 
 
         'min_range' => 10,
 
         'min_range' => 10,
−
         'max_range' => 100
+
         'max_range' => 100,
−
     ));
+
     ],
−
     
+
];
−
    $var = filter_var($var, FILTER_VALIDATE_INT, $opciones);
 
−
    echo $var;
 
  
−
?>
+
$resultado = filter_var($valor, FILTER_VALIDATE_INT, $opcion);
−
</syntaxhighlight>  
+
echo $resultado;
−
:: Neste caso $var valerá '3' en caso de que non se cumpra o filtro.
+
</syntaxhighlight>
  
 +
: Neste caso obteremos <code>3</code> se o valor non supera a validación.
  
−
* No caso dun número flotante podemos indicar cal é o carácter separado dos díxitos decimais.
+
* Para un número decimal podemos indicar outro separador decimal mediante a opción <code>decimal</code>:
−
::<syntaxhighlight lang="php" line enclose="div" highlight="2,5" >
 
−
<?php 
 
−
    $var = '1,23'; 
 
−
    $opciones = array(
 
−
        'options' => array(
 
−
        'decimal' => ','
 
−
    ));
 
−
     
 
−
    if(filter_var($var, FILTER_VALIDATE_FLOAT, $opciones) === false){ 
 
−
        echo 'Valor incorrecto'; 
 
−
    }else{ 
 
−
        echo 'Valor correcto'; 
 
−
    } 
 
−
?>   
 
  
−
</syntaxhighlight>  
+
::<syntaxhighlight lang="php" line enclose="div">
−
:: Devolve 'Valor correcto'
+
$valor = '1,23';
  
 +
$opcion = [
 +
    'options' => [
 +
        'decimal' => ',',
 +
    ],
 +
];
  
−
*<u>Nota:</u> Podemos facer uso da [http://php.net/manual/en/function.filter-input.php función filter_input] que permite obter un dato dun formulario e validar ao mesmo tempo.
+
$resultado = filter_var($valor, FILTER_VALIDATE_FLOAT, $opcion);
  
−
== Seguridade no uso dos formularios ==
+
if ($resultado === false) {
 +
    echo 'Valor incorrecto';
 +
} else {
 +
    echo "Valor correcto: $resultado";
 +
}
 +
</syntaxhighlight>
  
  
−
* Cando manexamos formularios temos que ter precaución co manexo de datos, xa que un usuario malintencionado podería poñer en risco o noso sitio web.
+
== Seguridade no uso dos formularios ==
  
−
=== Inxección de CSS ===
+
* Os datos dun formulario están controlados polo cliente e poden ser modificados. A aplicación debe validar a entrada e aplicar a protección apropiada no contexto no que vai empregar cada dato.
  
−
* Este problema aparece normalmente cando temos unha caixa de texto / textarea no que imos a engadir/consultar, cos datos que veñen, a unha táboa dunha base de datos.
+
=== Inxección SQL ===
  
 +
* Unha inxección SQL pode producirse cando unha aplicación constrúe unha consulta concatenando directamente datos controlados polo usuario.
  
−
: Unha consulta clásica de validación de usuario podería ser:
+
: Exemplo '''incorrecto''':
−
::* $_POST['usuarios'] = 'angel'
 
−
::* $_POST['password'] = '12345678'
 
−
::<syntaxhighlight lang="php" line enclose="div" highlight="2,5" >
 
−
<?php
 
−
            $nome=$_POST['usuarios'];
 
−
            $password=$_POST['password'];
 
−
                     
 
−
            printf("select count(*) from USUARIOS where nome='%s' and password='%s'",$nome,$password);
 
−
?>
 
−
</syntaxhighlight>
 
−
: Neste caso se a consulta devolve 1 entón é un usuario da nosa base de datos.
 
−
: A consulta a executar no xestor de base de datos sería: select count(*) from USUARIOS where <b>nome='angel'</b> and <b>password='12345678'</b>
 
  
−
* Agora <u>modifiquemos o campo password</u> e imaxinemos que o usuario pon isto:
+
::<syntaxhighlight lang="php" line enclose="div">
−
::* $_POST['password'] = "' or password like '%";
+
$nome = $_POST['usuario'] ?? '';
−
: O resultado da consulta sería: select count(*) from USUARIOS where nome='angel' and password=' ' or password like '%'
+
$password = $_POST['password'] ?? '';
  
−
* Como vemos estamos saltándonos a seguridade debido a que:
+
$sql = "SELECT count(*) FROM usuarios
−
:* O usuario pode engadir máis texto do permitido (incluso poñendo restricións de javascript, estas poden ser saltadas).
+
        WHERE nome='$nome' AND password='$password'";
−
:* O usuario pode engadir 'texto' que non debería estar permitido.
+
</syntaxhighlight>
  
−
<u>Nota:</u> En vez de modificar unha consulta (como no exemplo) podería executar ordes SQL como de inserción, borrado ou modificación de rexistros.
+
* O problema non se resolve eliminando comiñas mediante filtros HTML nin empregando <code>htmlspecialchars()</code>. HTML e SQL son contextos diferentes.
  
−
==== Solución a inxección de CSS ====
+
==== Solución á inxección SQL ====
  
−
* A mellor solución é utilizar procedementos almacenados. Sempre que o xestor o permite.
+
* A solución habitual é empregar '''consultas preparadas con parámetros'''. Os datos e o código SQL envíanse ao xestor de base de datos de forma separada.
−
: Vexamos un exemplo en Mysql:
 
  
−
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
: Exemplo con MySQLi:
−
DELIMITER //
 
−
CREATE PROCEDURE country_hos
 
−
(IN log VARCHAR(20),IN pass VARCHAR(30))
 
−
BEGIN
 
−
  SELECT count(*) FROM USUARIOS
 
−
  WHERE login = log and password = pass;
 
−
END //
 
−
DELIMITER ;
 
−
</syntaxhighlight>
 
  
−
: Nesta solución temos:
+
::<syntaxhighlight lang="php" line enclose="div">
−
::* O usuario aínda que envíe máis información so se vai coller 20 caracteres para o login e 30 para o password.
+
$stmt = $pdo->prepare(
−
::* Aínda que engada 'código SQL' non se vai a executar xa que dito código vai ir todo na campo password.
+
    'SELECT id, nome, password_hash
 +
    FROM usuarios
 +
    WHERE nome = :nome'
 +
);
  
−
: Loxicamente isto non quita que busquemos formas de validar os datos introducidos.
+
$stmt->execute([
 +
    'nome' => $nome
 +
]);
  
 +
$usuario = $stmt->fetch(PDO::FETCH_ASSOC);
 +
</syntaxhighlight>
  
−
* Outra opción que temos é a de utilizar os [http://wiki.cifprodolfoucha.es/index.php?title=PHP_Paso_de_datos_con_formularios#Filtros_para_a_correcci.C3.B3n filtros de corrección vistos anteriormente] como por exemplo FILTER_SANITIZE_STRING que fai un escape dos caracteres como as cominllas simples ou dobres.
+
* Cando traballemos con contrasinais reais non debemos gardalos nin comparalos en texto claro. Empregaremos <code>password_hash()</code> para gardar o ''hash'' e <code>password_verify()</code> para comprobar o contrasinal.
  
−
* Outra opción é a de utilizar funcións que provea o S.X.B.D. utilizado.
+
* <code>mysqli_real_escape_string()</code> pode ser útil nalgúns contextos específicos, pero '''non é a técnica principal que debemos ensinar para construír consultas con datos externos'''. A opción preferente son as consultas preparadas.
−
: No caso de mysql temos a [http://php.net/manual/es/mysqli.real-escape-string.php función mysqli_real_escape_string]. O veremos cando cheguemos a sección de acceso a datos.
 
  
−
* Tamén podemos empregar a [http://php.net/manual/es/mysqli.prepare.php función mysqli::prepare] como [http://wiki.cifprodolfoucha.es/index.php?title=PHP_Operaci%C3%B3ns_BD_Mysql#Optimizaci.C3.B3n:_Sentenzas_Prepared veremos posteriormente en acceso a datos].
+
* Un procedemento almacenado tampouco evita por si só unha inxección SQL: dependerá de como estea implementado e de se constrúe SQL dinámico internamente.
  
 
=== Cross-Site Scripting (XSS) ===
 
=== Cross-Site Scripting (XSS) ===
  
−
* Máis información [https://es.wikipedia.org/wiki/Cross-site_scripting neste enlace].
+
* Un ataque XSS pode producirse cando datos controlados por un usuario se incorporan a unha páxina e o navegador os interpreta como HTML ou JavaScript.
  
 +
: Por exemplo, imaxinemos que recibimos:
  
−
* Este tipo de ataque é debido a que un usuario engade código script (coma javascript) á información que imos amosar na páxina web.
+
::<syntaxhighlight lang="html" line enclose="div">
−
: Polo tanto teremos que <u>vixiar os datos que VISUALICEMOS na páxina</u>.
+
<script>alert('Exemplo XSS')</script>
 +
</syntaxhighlight>
  
 +
: Se facemos directamente:
  
−
: Un exemplo sería se introducimos dentro dun text-area este código:
+
::<syntaxhighlight lang="php">
−
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
echo $_GET['texto'];
−
<script>window.location="http://www.google.es";</script>
+
</syntaxhighlight>
−
</syntaxhighlight>  
 
  
−
: O que vai pasar é que o navegador cando cargue a páxina vai cargar a páxina de google indicada no código javascript.
+
: o navegador podería interpretar o contido como marcado ou código.
−
: Fixarse no perigo que supón xa que un atacante pode levar a un usuario a outro sitio web diferente.
 
  
−
* Outro exemplo de ataque por URL:
+
==== Solucións ao ataque Cross-Site Scripting (XSS) ====
  
−
:: http://localhost/PhpOlaMundo/Formularios/datosFormulario.php?texto=<script>window.location="http://www.google.es";</script>
+
* Para mostrar texto dentro de HTML empregaremos normalmente:
  
−
: Se amosamos o dato: $_GET['texto'] teremos o mesmo problema que o anterior.
+
::<syntaxhighlight lang="php">
 +
$texto = $_GET['texto'] ?? '';
  
 +
echo htmlspecialchars(
 +
    $texto,
 +
    ENT_QUOTES | ENT_SUBSTITUTE,
 +
    'UTF-8'
 +
);
 +
</syntaxhighlight>
  
−
* Este código pode vir embebido nunha folla de estilos CSS, nun atributo dunha etiqueta HTML, dentro do corpo da páxina,na URL...
+
* <code>htmlspecialchars()</code> non elimina o texto: codifica os caracteres con significado especial en HTML para que se mostren como texto.
 +
: Os parámetros empregados teñen o seguinte significado:
 +
:* '''$texto''': cadea que queremos converter para poder amosala de forma segura dentro de HTML.
 +
:* '''ENT_QUOTES''': indica que, ademais dos caracteres especiais de HTML, se deben converter tanto as comiñas dobres (") como as comiñas simples (').
 +
:* '''ENT_SUBSTITUTE''': emprégase para que en caso de enviar caracteres non válidos UTF-8 os sustitúa polo carácter �.
 +
: Isto é especialmente importante cando o dato vai ser empregado dentro dun atributo HTML.
  
−
<u>Importante:</u> Fixarse que neste tipo de ataques temos que evitar que o que visualizamos leve código de script embebido. É diferente ao anterior no que supoñía un ataque á base de datos.
+
: <u>Visualmente non notaredes nada no navegador, pero se mirades o código fonte de páxina podedes comprobar como cambia as comillas e outros caracteres por códigos.</u>
  
  
−
* Outro exemplo é se empregamos a variable $_SERVER['PHP_SELF'] como parámetro nun action dun formulario da forma:
+
* O escapado debe realizarse '''no momento da saída e segundo o contexto'''. Un valor preparado para HTML non queda automaticamente preparado para SQL, JavaScript, CSS ou unha URL.
  
−
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
* <code>strip_tags()</code> pode eliminar etiquetas HTML, pero non debe considerarse unha alternativa xeral ao escapado contextual.
−
<form method="post" action="<?php echo $_SERVER['PHP_SELF']); ?>" name="formulario_perfumes">
 
  
−
......
+
* Se queremos utilizar <code>$_SERVER['PHP_SELF']</code> nun atributo HTML, debemos escapar o valor:
  
 +
::<syntaxhighlight lang="php">
 +
<form method="post"
 +
      action="<?php echo htmlspecialchars($_SERVER['PHP_SELF'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?>">
 
</form>
 
</form>
−
</syntaxhighlight>  
+
</syntaxhighlight>
  
 +
: Para un formulario que se procesa na mesma páxina tamén podemos empregar simplemente:
 +
 +
::<syntaxhighlight lang="html">
 +
<form method="post" action="">
 +
</form>
 +
</syntaxhighlight>
  
−
==== Solucións ao ataque Cross-Site Scripting (XSS) ====
+
=== Ataques CSRF (Cross-Site Request Forgery) ===
  
 +
* Os ataques CSRF trataranse [https://wiki.cifprodolfoucha.es/index.php?title=PHP_Sesi%C3%B3ns#Ataques_CSRF_.28Cross-Site_Request_Forgery.29 cando vexamos as variables de sesión na UD4].
  
−
* Igual que no caso anterior podemos utilizar a [http://wiki.cifprodolfoucha.es/index.php?title=PHP_Paso_de_datos_con_formularios#Funci.C3.B3n_para_validar_.2F_corrixir_campos función filter_var] cos filtros FILTER_SANITIZE_MAGIC_QUOTES ou FILTER_SANITIZE_STRING para eliminar dita posibilidade.
+
=== Normas xerais ===
  
−
* En PHP podemos facer uso das seguintes funcións:
+
* Como normas xerais:
−
:* [http://php.net/manual/es/function.strip-tags.php Función strip_tags]: Dita función fai o mesmo que FILTER_SANITIZE_STRING e ademais podemos indicar cales etiquetas son permitidas.
+
:* Validaremos no servidor todos os datos procedentes do cliente.
−
:* [http://php.net/manual/es/function.htmlentities.php Función htmlentities]: Dita función substitúe os caracteres das etiquetas en códigos HTML para a súa visualización.
+
:* Nos formularios que modifican o estado da aplicación empregaremos protección contra CSRF.
−
:: Se aplicamos dita función ao exemplo anterior teríamos esta saída coma código fonte de páxina: & lt;script& gt;window.location=& quot;http://www.google.es& quot;;& lt;/script& gt;
+
:* Para mostrar texto dentro de HTML empregaremos <code>htmlspecialchars(..., ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')</code>.
−
:: Fixarse que os caracteres < e > son substituídos por & lt; e & gt; respectivamente.
+
:* Para SQL empregaremos consultas preparadas con parámetros.
−
:: (Hai posto un espazo en branco entre & e o código xa que se non a wiki o visualizará como html)
+
:* Para contrasinais empregaremos <code>[https://www.php.net/manual/es/function.password-hash.php password_hash()]</code> e <code>[https://www.php.net/manual/es/function.password-verify.php password_verify()]</code>.
 +
:* Non empregaremos filtros HTML para intentar evitar inxección SQL.
 +
:* Sanearemos ou normalizaremos datos só cando as regras da aplicación o requiran; non substituiremos a validación por unha transformación silenciosa.
  
 +
== Obtendo variables de formularios (POST/GET) e filtrando ao mesmo tempo ==
  
−
: No caso do formulario poderíamos poñer:
+
* PHP dispón da función [https://www.php.net/manual/es/function.filter-input.php filter_input()], que permite obter unha variable externa e aplicarlle opcionalmente un filtro.
−
<form method="post" action="<?php echo htmlentities($_SERVER['PHP_SELF']); ?>" name="formulario_perfumes">
 
  
−
......
+
* Sintaxe actual:
  
−
</form>
+
::<syntaxhighlight lang="php">
 +
filter_input(
 +
    int $type,
 +
    string $var_name,
 +
    int $filter = FILTER_DEFAULT,
 +
    array|int $options = 0
 +
): mixed
 
</syntaxhighlight>
 
</syntaxhighlight>
  
−
== Obtendo variables de formularios (post/get) e filtrado ao mesmo tempo ==
+
* Parámetros:
 +
:* '''type''': <code>INPUT_GET</code>, <code>INPUT_POST</code>, <code>INPUT_COOKIE</code>, <code>INPUT_SERVER</code> ou <code>INPUT_ENV</code>.
 +
:* '''var_name''': nome da variable.
 +
:* '''filter''': filtro a aplicar.
 +
:* '''options''': opcións ou ''flags''.
  
−
* En PHP , a partires da versión 5, podemos recuperar o valor dun campo dun formulario e aplicarlle un filtro ao mesmo tempo.
+
* <u>Importante:</u> <code>FILTER_DEFAULT</code> é un alias de <code>FILTER_UNSAFE_RAW</code>; é dicir, por defecto non se aplica unha limpeza automática.
−
: A función que permite facer isto é [http://php.net/manual/es/function.filter-input.php filter_input].
 
  
−
* Sintaxe:
+
* <code>filter_input()</code> traballa sobre o valor orixinal proporcionado pola SAPI. Se modificamos previamente <code>$_GET</code> ou <code>$_POST</code> dentro do programa e queremos filtrar ese novo valor, empregaremos <code>filter_var()</code>.
−
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
 
−
mixed filter_input ( int $type , string $variable_name [, int $filter = FILTER_DEFAULT [, mixed $options ]] )
 
−
</syntaxhighlight>  
 
  
−
: Parámetros:
+
* Exemplo validando un identificador enteiro recibido por GET:
−
:* '''type''': INPUT_GET, INPUT_POST, INPUT_COOKIE, INPUT_SERVER o INPUT_ENV.
 
−
:* '''variable_name''': Nome da variable, ven ser o nome da control html en caso dun formulario.
 
−
:* '''filter''': O ID do filtro a aplicar. Xa os vimos neste mesmo punto da wiki.
 
−
:* '''options''': Array asociativo de opcións. Se o filtro acepta opcións, se poden engadir nun array asociativo baixo la clave "flags".
 
  
−
: Valores devoltos:
+
::<syntaxhighlight lang="php" line enclose="div">
 +
$id = filter_input(
 +
    INPUT_GET,
 +
    'id',
 +
    FILTER_VALIDATE_INT,
 +
    [
 +
        'options' => [
 +
            'min_range' => 1,
 +
        ],
 +
    ]
 +
);
  
−
:* En caso de éxito, valor da variable pedida, FALSE se o filtro faia ou NULL se a variable variable_name non está definida.  
+
if ($id === null) {
 +
    echo 'Non se enviou o parámetro id.';
 +
} elseif ($id === false) {
 +
    echo 'O identificador non é válido.';
 +
} else {
 +
    echo 'Identificador válido: ' . $id;
 +
}
 +
</syntaxhighlight>
  
−
* Por exemplo:
+
* Exemplo para obter texto e mostralo en HTML:
−
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
 
  
−
<?php
+
::<syntaxhighlight lang="php" line enclose="div">
−
$buscar_html = filter_input(INPUT_GET, 'buscar', FILTER_SANITIZE_SPECIAL_CHARS);
+
$buscar = filter_input(INPUT_GET, 'buscar', FILTER_UNSAFE_RAW);   // Por defecto todos os datos que veñen en post e get son cadeas. Non ten sentido filtrar por 'cadeas'
−
$buscar_url = filter_input(INPUT_GET, 'buscar', FILTER_SANITIZE_ENCODED);
 
−
echo "Has buscado $buscar_html.\n";
 
−
echo "<a href='?buscar=$buscar_url'>Buscar de nuevo</a>";
 
−
?>
 
  
−
</syntaxhighlight>
+
if (!is_string($buscar)) {  // Con isto aseguramos que sexa unha cadea e non un array, por exemplo....
 +
    $buscar = '';
 +
}
  
−
* Información obtida de [http://php.net/manual/es/function.filter-input.php php.net].
+
$buscarHtml = htmlspecialchars(
 +
    $buscar,
 +
    ENT_QUOTES | ENT_SUBSTITUTE,
 +
    'UTF-8'
 +
);
  
 +
echo "Buscaches: $buscarHtml";
 +
</syntaxhighlight>
  
 
== Redireccionamento de páxinas ==
 
== Redireccionamento de páxinas ==
  
−
* Pode ser interesante cando realizamos un tratamento cos datos que veñen dun formulario ter a posibilidade de mandar ao usuario a unha páxina concreta.  
+
* Despois de procesar un formulario pode ser necesario redireccionar ao usuario a outra páxina.
  
−
: Imaxinemos o caso de que intentamos cargar a páxina de tratamento dun formulario previo ou que enviamos datos que non están completos. Neses casos queremos volver a enviar ao usuario á páxina de entrada de datos.
+
* Podemos facelo coa [https://www.php.net/manual/es/function.header.php función header()]:
  
−
* Para facelo podemos usar a [http://php.net/manual/es/function.header.php función header] de PHP.
+
::<syntaxhighlight lang="php" line enclose="div">
 +
header('Location: /nova_paxina.php');
 +
exit;
 +
</syntaxhighlight>
  
−
: Dita función serve para enviar encabezados ao navegador do cliente. Uns destes encabezados serve para redireccionar a páxina do clienta. A forma de facelo é:
+
:<u><b>Importante:</b></u>
 +
:* A cabeceira debe enviarse antes de producir saída cara ao cliente. Non debe haber HTML, <code>echo</code>, <code>printf</code> nin espazos enviados antes da chamada a <code>header()</code>.
 +
:* Despois dun redireccionamento é recomendable executar <code>exit;</code> para impedir que continúe o resto do script.
  
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
+
* Se queremos enviar parámetros GET nunha redirección temos dúas opcións:
−
<?php
+
 
−
header('Location: http://www.nova_paxina.es/');
+
:* Construír a cadea mediante <code>http_build_query()</code>:
 +
 
 +
::<syntaxhighlight lang="php" line enclose="div">
 +
$parametros = http_build_query([
 +
    'parametro1' => 'valor 1',
 +
    'parametro2' => 'valor 2',
 +
]);
 +
 
 +
header('Location: /nova_paxina.php?' . $parametros);
 +
exit;
 +
</syntaxhighlight>
 +
: Esta é a opción recomendable <u>se temos varios parámetros a enviar</u>. Automaticamente xa fai o url_encode.
 +
 
 +
 
 +
:* Empregar urlencode:
 +
::<syntaxhighlight lang="php" line enclose="div">
 +
header(
 +
            'Location: /nova_paxina.php?parametro=' .
 +
                urlencode('Valor a enviar')
 +
        );
 
exit;
 
exit;
−
?>
+
</syntaxhighlight>
−
</syntaxhighlight>  
+
 
 +
 
 +
 
 +
'''IMPORTANTE:''' Lembrar poñer <u>exit</u> xa que o script continúa a execución se non está posto.
 +
 
  
−
<u><b>Importante:</b></u> É NECESARIO QUE A CHAMADA Á FUNCIÓN header SE FAGA <u>ANTES</u> DE ENVIAR NINGÚN DATO AO CLIENTE.
 
−
: Quere isto dicir que ao cliente non lle pode chegar ningunha etiqueta HTML antes de facer  o header-location. Non vale se temos un echo/printf de calquera cousa ou código HTML enviado <u>antes</u> da orde header.
 
  
 +
* Na páxina de destino poderemos obter os datos mediante <code>$_GET</code> e validalos.
  
 +
* Para inspeccionar as cabeceiras HTTP xa non é necesario instalar complementos específicos. Os navegadores actuais dispoñen das ferramentas de desenvolvemento: normalmente podemos abrir a lapela '''Rede/Network''', seleccionar unha petición e revisar as súas cabeceiras.
  
−
* Un complemento interesante para visualizar a cabeceira dunha páxina: https://addons.mozilla.org/es/firefox/addon/live-http-headers/
 
−
: Ao instalalo teremos a posibilidade de capturar os datos de cabeceira da páxina se imos ao menú '''Herramientas''' => '''Live HTTP Headers'''.
 
  
−
<gallery caption="Complemento Live HTTP Headers" widths="350" heights="300px" perrow="2">
 
−
Image:Php_paso_datos_1.JPG| Unha vez instalado aparece unha nova opción de menú en Herramientas.
 
−
Image:Php_paso_datos_2.JPG| Coa ventá aberta, se cargamos unha páxina aparecen os datos enviados como cabeceira ao navegador web dende o servidor.
 
−
</gallery>
 
  
−
: Coa [http://php.net/manual/es/function.header.php función header] imos poder manipular os datos enviados ao cliente dende o servidor.
+
 
 +
<br />
  
 
== Gardando imaxes no servidor ==
 
== Gardando imaxes no servidor ==
  
−
* Nos formularios temos a posibilidade de subir arquivos ao servidor.
+
* '''NOTA:''' Este punto non vai entrar no exame, pero é convinte que o coñezades para poder subir un arquivo ao servidor web...
−
: Un uso típico pode ser o de gardar documentos ou dar de alta produtos con imaxes asociadas. Neste último caso, o que gardaremos na base de datos será o nome do arquivo da imaxe (por exemplo 'imaxe.jpg') e cando recuperemos os datos da base de datos 'construiremos' unha etiqueta <img src='http://www.meusitio.es/IMAXES/imaxe.jpg' /> sendo IMAXES o cartafol onde están gardadas as imaxes no meu servidor web que atende a peticións coa URL: http://www.meusitio.es
+
: Polo tanto facede o exemplo que vai a continuación.
 +
 
 +
 
 +
* Os formularios permiten subir arquivos ao servidor. Un caso típico é asociar unha imaxe a un produto.
  
 +
* A subida de arquivos require especial atención á seguridade:
 +
:* Non debemos confiar no nome orixinal enviado polo cliente.
 +
:* Non debemos confiar en <code>$_FILES['...']['type']</code>, xa que esa información é proporcionada polo cliente e PHP non a verifica.
 +
:* Debemos comprobar o código de erro, o tamaño e o tipo real do arquivo.
 +
:* É recomendable xerar un nome novo no servidor.
 +
:* Para arquivos que non necesiten ser servidos directamente polo servidor web é preferible almacenalos fóra do <code>DocumentRoot</code>.
 +
:* Nunca debemos permitir a execución de código nun cartafol de subidas.
  
−
* O primeiro que teremos que facer é crear un cartafol onde queiramos deixar as imaxes no noso servidor web.
+
=== Cartafol de destino ===
−
: Como vimos [http://wiki.cifprodolfoucha.es/index.php?title=PHP_Instalaci%C3%B3n_Apache na wiki nun punto anterior], o servidor web Apache 'apunta' fisicamente por defecto a '/var/www/html'.
 
−
: Crearemos (podedes comprobar a onde apunta mirando o arquivo de configuración /etc/apache2/sites-available/000-default.conf, como está explicado [http://wiki.cifprodolfoucha.es/index.php?title=DRUPAL_Instalaci%C3%B3n#Arquivos_de_configuraci.C3.B3n na wiki]) un cartafol de nome '''IMAXES''' e darémoslle permiso de escritura ao usuario que utiliza o servidor Apache para acceder ao sistema de arquivos (www-data). Isto último está explicado [http://wiki.cifprodolfoucha.es/index.php?title=DRUPAL_Instalaci%C3%B3n#Arquivos_de_configuraci.C3.B3n no seguinte punto da wiki].
 
  
 +
* Nun exemplo sinxelo no que Apache serve directamente as imaxes e o <code>DocumentRoot</code> é <code>/var/www/html</code>, poderiamos crear un cartafol específico:
  
−
* Supoñemos que document_root apunta a /var/www/html e que estades a utilizar como servidor web Apache instalado de forma independente (sen XAMPP)
+
::<syntaxhighlight lang="bash" line enclose="div">
−
: Supoñemos que estades a utilizar Apache como servidor web e que o usuario-grupo que utiliza é www-data
 
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
 
 
cd /var/www/html
 
cd /var/www/html
 
mkdir IMAXES
 
mkdir IMAXES
 
chown www-data:www-data IMAXES
 
chown www-data:www-data IMAXES
−
chmod 755 IMAXES
+
chmod 750 IMAXES
−
</syntaxhighlight>  
+
</syntaxhighlight>
  
 +
* O usuario e grupo reais dependen da instalación. En Debian/Ubuntu é habitual <code>www-data</code>; noutras instalacións pode ser diferente.
  
 +
* Evitaremos permisos <code>777</code>. O proceso que executa PHP só necesita os permisos necesarios para escribir no directorio.
  
−
: Se estiverades a utilizar XAMPP, teredes que mirar o arquivo de configuración do Apache que no caso de XAMPP é o httpd.conf que se atopa no cartafol /opt/lampp/etc
+
=== Páxina do formulario ===
−
::<syntaxhighlight lang="php" line enclose="div" highlight="10,11" >
 
−
<IfModule unixd_module>
 
−
#
 
−
# If you wish httpd to run as a different user or group, you must run
 
−
# httpd as root initially and it will switch.
 
−
#
 
−
# User/Group: The name (or #number) of the user/group to run httpd as.
 
−
# It is usually good practice to create a dedicated user and group for
 
−
# running httpd, as with most system services.
 
−
#
 
−
User daemon
 
−
Group daemon
 
−
</IfModule>
 
−
</syntaxhighlight>
 
  
−
:: Nas liñas 10 e 11 se indica o usuario e grupo que utiliza o Apache de XAMPP para acceder aos cartafoles.
+
* Para subir arquivos é obrigatorio utilizar <code>enctype="multipart/form-data"</code>.
−
:: Deberedes cambiar a liña do script anterior de '''chown www-data:www-data IMAXES''' a '''chown daemon:daemon IMAXES'''
 
  
−
 
+
::<syntaxhighlight lang="html" line enclose="div">
−
 
 
−
 
 
−
 
 
−
'''Páxina do formulario'''
 
−
* A páxina onde se atopa o formulario para subir entre outros campos o campo imaxe debe ter as seguintes características.
 
−
: Información obtida de [http://www.w3schools.com/php/php_file_upload.asp www3schools].
 
−
::<syntaxhighlight lang="php" line enclose="div" highlight="5,7" >
 
 
<!DOCTYPE html>
 
<!DOCTYPE html>
−
<html>
+
<html lang="gl">
 +
<head>
 +
    <meta charset="UTF-8">
 +
    <title>Subida de imaxes</title>
 +
</head>
 
<body>
 
<body>
  
 
<form action="GardaCampos.php" method="post" enctype="multipart/form-data">
 
<form action="GardaCampos.php" method="post" enctype="multipart/form-data">
−
     Selecciona unha imaxe para subir:
+
     <label for="fileToUpload">Selecciona unha imaxe:</label>
−
     <input type="file" name="fileToUpload" id="fileToUpload">
+
     <input
−
     <input type="submit" value="Subir Imaxe" name="btnSubirImaxe">
+
        type="file"
 +
        name="fileToUpload"
 +
        id="fileToUpload"
 +
        accept=".jpg,.jpeg,.png,.gif"
 +
        required
 +
    >
 +
 
 +
     <button type="submit">Subir imaxe</button>
 
</form>
 
</form>
  
 
</body>
 
</body>
 
</html>
 
</html>
−
</syntaxhighlight>  
+
</syntaxhighlight>
 +
 
 +
* O atributo <code>accept</code> axuda ao usuario a seleccionar determinados ficheiros, pero '''non é unha medida de seguridade'''. O servidor debe comprobar igualmente o arquivo recibido.
 +
 
 +
=== Páxina que procesa os datos ===
 +
 
 +
* O seguinte exemplo:
 +
:* comproba o método da petición;
 +
:* comproba o código de erro;
 +
:* limita o tamaño;
 +
:* verifica que o arquivo procede dunha subida HTTP;
 +
:* detecta o MIME real mediante <code>finfo</code>;
 +
:* só permite determinados tipos de imaxe;
 +
:* xera un nome aleatorio;
 +
:* move o arquivo ao directorio definitivo.
  
−
'''IMPORTANTE''':
+
::<syntaxhighlight lang="php" line enclose="div">
−
:* Liña 5: Debemos de escribir '''enctype="multipart/form-data"''' na etiqueta form en caso de querer subir arquivos a un servidor.
+
<?php
−
:* Liña 7: O elemento HTML para poder seleccionar unha imaxe debe ser de tipo '''file'''
 
  
−
* Agora o Action do formulario pode 'apuntar' á mesma páxina ou a outra para procesar a subida do arquivo.
+
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
 +
    exit('Método non permitido.');
 +
}
  
 +
if (!isset($_FILES['fileToUpload'])) {
 +
    exit('Non se recibiu ningún arquivo.');
 +
}
  
−
'''Páxina que procesa os datos'''
+
$arquivo = $_FILES['fileToUpload'];
−
:Pode ser a mesma do formulario ou outra diferente. No exemplo se chama 'GardaCampos.php'.
 
  
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
+
if ($arquivo['error'] !== UPLOAD_ERR_OK) {
−
<?php
+
     exit('Erro na subida. Código: ' . $arquivo['error']);
−
    // Enviamos datos
+
}
−
if(isset($_POST["btnSubirImaxe"])) {
 
−
     if(is_uploaded_file($_FILES["fileToUpload"]["tmp_name"])){  // Subido ao cartafol temporal do servidor
 
−
        $target_dir = $_SERVER['DOCUMENT_ROOT'] . "/IMAXES/";  // Gardaremos a imaxe no cartafol a onde apunta o servidor web máis /IMAXES => /var/www/html/IMAXES
 
−
        $target_file = $target_dir . basename($_FILES["fileToUpload"]["name"]); // Colle o nome da imaxe. Isto é necesario se a imaxe ten unha ruta posta no seu nome.
 
  
−
        $tipo = $_FILES["fileToUpload"]["type"];
+
// Límite propio da aplicación: 2 MiB.
−
        if ($tipo=='image/jpeg')  {  // Neste exemplo verficamos que o arquivo sexa unha imaxe jpg
+
$tamanoMaximo = 2 * 1024 * 1024;
−
            if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file)) {
 
−
                echo "O arquivo ". basename( $_FILES["fileToUpload"]["name"]). " foi subido ao servidor.";
 
−
            } else {
 
−
                printf("Houbo un erro subindo o arquivo:%s",$_FILES['fileToUpload']['error']);
 
−
            }
 
−
        }
 
−
        else{
 
−
            echo ("O tipo de arquivo non é jpg");
 
−
        }
 
−
    }
 
−
    else{
 
−
        printf("Houbo un erro subindo o arquivo:%s",$_FILES['fileToUpload']['error']);
 
−
    }
 
  
 +
if ($arquivo['size'] > $tamanoMaximo) {
 +
    exit('O arquivo supera o tamaño máximo permitido.');
 +
}
  
 +
if (!is_uploaded_file($arquivo['tmp_name'])) {
 +
    exit('O arquivo non procede dunha subida HTTP válida.');
 
}
 
}
−
?>
 
−
</syntaxhighlight>
 
−
* En caso de erro, podemos ver o valor de $_FILES['fileToUpload']['error']:
 
−
:* UPLOAD_ERR_OK (0): Sen erros. O arquivo subiuse con éxito.
 
−
:* UPLOAD_ERR_INI_SIZE (1): O arquivo é máis grande que o tamaño máximo permitido especificado na configuración upload_max_filesize en php.ini.
 
−
:* UPLOAD_ERR_FORM_SIZE (2): O arquivo é máis grande que o tamaño máximo permitido especificado no formulario HTML mediante o atributo MAX_FILE_SIZE.
 
−
:* UPLOAD_ERR_PARTIAL (3): O arquivo subiuse parcialmente.
 
−
:* UPLOAD_ERR_NO_FILE (4): Non se subiu ningún arquivo. Isto pode ocorrer cando o campo de arquivo no formulario está baleiro ou se omite.
 
−
:* UPLOAD_ERR_NO_TMP_DIR (6): Falta o directorio temporal. Isto pode deberse a unha configuración incorrecta no servidor.
 
−
:* UPLOAD_ERR_CANT_WRITE (7): Non se puido escribir o arquivo no disco. Isto pode deberse a problemas de permisos no directorio de destino.
 
−
:* UPLOAD_ERR_EXTENSION (8): Unha extensión PHP detivo a subida do arquivo. Isto pode ocorrer cando unha extensión PHP específica bloquea a subida do arquivo.
 
  
 +
// Detectamos o MIME a partir do contido real do arquivo.
 +
$finfo = new finfo(FILEINFO_MIME_TYPE);
 +
$mime = $finfo->file($arquivo['tmp_name']);
  
−
: <u>'''Nota importante:'''</u> Algunhas veces ['error'] sempre devolve false (0) e non se establece correctamente o error.
+
$tiposPermitidos = [
−
: Para saber que pasa, temporalmente podemos poñer ao principio da páxina:
+
    'image/jpeg' => 'jpg',
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
+
    'image/png' => 'png',
−
    error_reporting(E_ALL);
+
    'image/gif'  => 'gif',
−
    ini_set('display_errors', 1);
+
];
−
</syntaxhighlight>
 
  
 +
if ($mime === false || !isset($tiposPermitidos[$mime])) {
 +
    exit('Tipo de arquivo non permitido.');
 +
}
  
 +
$extension = $tiposPermitidos[$mime];
  
−
* ALgunhas función que pode ser de utilidade:
+
// Non empregamos o nome enviado polo cliente como nome definitivo.
−
:* [http://php.net/manual/es/function.basename.php función basename] devolve a última parte dunha ruta. Así se teño c:\carpeta1\carpeta2\arquivo.jpg devolverá 'arquivo.jpg'.
+
$nomeSeguro = bin2hex(random_bytes(16)) . '.' . $extension;
−
:: Por exemplo:
 
−
:::$target_dir = $_SERVER['DOCUMENT_ROOT'] . "/IMAXES/";  // Gardaremos a imaxe no cartafol a onde apunta o servidor web máis /IMAXES => /var/www/html/IMAXES
 
−
:::$target_file = $target_dir . basename($_FILES["fileToUpload"]["name"]); // Colle o nome da imaxe. Isto é necesario se a imaxe ten unha ruta posta no seu nome.
 
  
 +
$directorioDestino = __DIR__ . '/IMAXES';
  
−
:* A [http://php.net/manual/es/function.pathinfo.php función pathinfo] garda a extensión do arquivo a subir.
+
if (!is_dir($directorioDestino) || !is_writable($directorioDestino)) {
−
:: Por exemplo:
+
    exit('O directorio de destino non existe ou non permite escritura.');
−
:::$imageFileType = pathinfo($target_file,PATHINFO_EXTENSION);
+
}
  
−
:* A [http://php.net/manual/es/function.getimagesize.php función getimagesize] devolve un array con información da imaxe. En caso de que non sexa unha imaxe devolve false.
+
$destino = $directorioDestino . DIRECTORY_SEPARATOR . $nomeSeguro;
−
:::Por exemplo:
 
−
:::$check = getimagesize($_FILES["fileToUpload"]["tmp_name"]);
 
−
:::if($check !== false) {
 
−
:::    echo "O arquivo é unha imaxe - " . $check["mime"] . ".";
 
−
:::    } else {
 
−
:::        echo "O arquivo non é unha imaxe.";
 
−
:::    }
 
  
 +
if (!move_uploaded_file($arquivo['tmp_name'], $destino)) {
 +
    exit('Non foi posible gardar o arquivo.');
 +
}
  
 +
echo 'Imaxe gardada correctamente co nome: '
 +
    . htmlspecialchars($nomeSeguro, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
 +
</syntaxhighlight>
  
−
* Debemos ter en conta que podemos subir outro tipo de arquivos, como documentos,...iso dependerá da nosa aplicación.
+
* Neste exemplo empregamos a clase [https://www.php.net/manual/es/class.finfo.php finfo] coa constante <code>FILEINFO_MIME_TYPE</code>. Non é necesario chamar a <code>finfo_close()</code>; en PHP 8.5 esa función está obsoleta porque o obxecto se libera automaticamente.
−
: Podemos mellorar o noso código introducindo novas condicións.
 
  
 +
=== Códigos de erro de subida ===
  
 +
* O campo <code>$_FILES['fileToUpload']['error']</code> pode ter, entre outros, os seguintes valores:
 +
:* <code>UPLOAD_ERR_OK (0)</code>: subida correcta.
 +
:* <code>UPLOAD_ERR_INI_SIZE (1)</code>: o arquivo supera <code>upload_max_filesize</code>.
 +
:* <code>UPLOAD_ERR_FORM_SIZE (2)</code>: supera <code>MAX_FILE_SIZE</code>, se o formulario enviou ese campo.
 +
:* <code>UPLOAD_ERR_PARTIAL (3)</code>: subida parcial.
 +
:* <code>UPLOAD_ERR_NO_FILE (4)</code>: non se seleccionou ningún arquivo.
 +
:* <code>UPLOAD_ERR_NO_TMP_DIR (6)</code>: non existe o directorio temporal.
 +
:* <code>UPLOAD_ERR_CANT_WRITE (7)</code>: PHP non puido escribir no disco.
 +
:* <code>UPLOAD_ERR_EXTENSION (8)</code>: unha extensión de PHP detivo a subida.
  
−
* '''Verificar se o arquivo existe antes de subilo''':
+
=== Funcións útiles ===
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
 
−
<?php
 
−
if (file_exists($target_file)) {
 
−
    echo "Ese arquivo xa existe.";
 
−
    $uploadOk = 0;
 
−
}
 
−
?>
 
−
</syntaxhighlight>
 
  
 +
:* [https://www.php.net/manual/es/function.basename.php basename()]: devolve a última parte dunha ruta. Pode ser útil para mostrar o nome orixinal, pero non debemos confiar nel como única medida de seguridade nin utilizalo necesariamente como nome final.
 +
:* [https://www.php.net/manual/es/function.pathinfo.php pathinfo()]: permite obter partes dun nome de ficheiro. A extensión enviada polo usuario non demostra o tipo real do arquivo.
 +
:* [https://www.php.net/manual/es/function.getimagesize.php getimagesize()]: pode obter información dunha imaxe e pode empregarse como comprobación adicional cando a aplicación espera imaxes.
 +
:* [https://www.php.net/manual/es/function.is-uploaded-file.php is_uploaded_file()]: comproba que o ficheiro temporal procede dunha subida HTTP.
 +
:* [https://www.php.net/manual/es/function.move-uploaded-file.php move_uploaded_file()]: move de forma segura un ficheiro subido ao seu destino.
  
 +
=== Limitar o tamaño ===
  
−
* '''Limitar o tipo do arquivo''':
+
* Ademais dos límites de PHP, a aplicación debería establecer o seu propio límite:
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
 
−
<?php
 
−
if($imageFileType != "jpg" && $imageFileType != "png" && $imageFileType != "jpeg"
 
−
&& $imageFileType != "gif" ) {
 
−
    echo "Soamente arquivos JPG, JPEG, PNG & GIF están permitidos.";
 
−
    $uploadOk = 0;
 
−
}
 
−
?>
 
−
</syntaxhighlight>
 
  
 +
::<syntaxhighlight lang="php">
 +
$tamanoMaximo = 2 * 1024 * 1024;
  
 +
if ($_FILES['fileToUpload']['size'] > $tamanoMaximo) {
 +
    exit('O arquivo é demasiado grande.');
 +
}
 +
</syntaxhighlight>
  
−
* '''Limitar o tamaño do arquivo''':
+
* HTML permite enviar un campo oculto <code>MAX_FILE_SIZE</code>:
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
 
−
<?php
 
−
// Check file size
 
−
if ($_FILES["fileToUpload"]["size"] > 500000) {
 
−
    echo "O arquivo ten máis de 500KB. Non se pode subir";
 
−
    $uploadOk = 0;
 
−
}
 
−
?>
 
−
</syntaxhighlight>  
 
  
−
:* Para o tamaño do arquivo podemos empregar outra aproximación, que consiste en poñer un campo oculto <u>antes do campo de tipo file</u> da seguinte forma:
+
::<syntaxhighlight lang="html">
−
::<syntaxhighlight lang="php" line enclose="div" highlight="3" >
 
 
<form action="GardaCampos.php" method="post" enctype="multipart/form-data">
 
<form action="GardaCampos.php" method="post" enctype="multipart/form-data">
−
    Selecciona unha imaxe para subir:
+
     <input type="hidden" name="MAX_FILE_SIZE" value="2097152">
−
     <input type="hidden" name="MAX_FILE_SIZE" value="10000" />  
+
     <input type="file" name="fileToUpload">
−
     <input type="file" name="fileToUpload" id="fileToUpload">
+
     <button type="submit">Subir</button>
−
     <input type="submit" value="Subir Imaxe" name="btnSubirImaxe">
 
 
</form>
 
</form>
−
</syntaxhighlight>  
+
</syntaxhighlight>
−
: O nome ten que ser MAX_FILE_SIZE e o value é o tamaño máximo en bytes.
+
 
 +
* <u>Importante:</u> este campo está controlado polo cliente e pode ser modificado. Non substitúe os límites configurados en PHP nin a comprobación do tamaño no servidor.
  
−
:: Cando se procesa o formulario:
+
=== Configuración en php.ini ===
−
::<syntaxhighlight lang="php" line enclose="div" highlight="5,6" >
 
−
<?php
 
−
if(isset($_POST["btnSubirImaxe"])) {
 
−
    $target_dir = $_SERVER['DOCUMENT_ROOT'] . "/IMAXES/";  // Gardaremos a imaxe no cartafol a onde apunta o servidor web máis /IMAXES => /var/www/html/IMAXES
 
−
    $target_file = $target_dir . basename($_FILES["fileToUpload"]["name"]);  // Colle o nome da imaxe. Isto é necesario se a imaxe ten unha ruta posta no seu nome.
 
−
    if ($_FILES['fileToUpload']['error']>0)
 
−
        die("Houbo un erro subindo o arquivo: " . $_FILES['fileToUpload']['error']);  // Neste exemplo dará de erro o valor 2 => UPLOAD_ERR_FORM_SIZE (visto anteriormente)
 
  
−
    ......
+
* As directivas principais son:
  
−
?>
+
::<syntaxhighlight lang="ini">
−
</syntaxhighlight>  
+
upload_max_filesize = 2M
 +
post_max_size = 8M
 +
</syntaxhighlight>
  
 +
* Estes son os valores por defecto de PHP 8.5, pero un servidor real pode ter outros valores.
  
 +
* <code>post_max_size</code> debe ser maior que <code>upload_max_filesize</code> se queremos permitir a subida dun ficheiro dese tamaño.
  
−
: Comentar que o referente ao tamaño dos arquivos, imos ter a limitación imposta polo propio PHP.
+
* Para saber que arquivo de configuración está cargando PHP en liña de comandos podemos executar:
−
: Se estades non aloxamento compartido non ides poder modificar o arquivo de configuración para permitir subir arquivos de máis de 8MB.
 
−
: Existen dúas entradas no arquivo de configuración php.ini que son:
 
−
:: '''upload_max_filesize = 2M;'''
 
−
:: '''post_max_size = 8M;'''
 
  
−
: Estas directivas indican o tamaño máximo dun arquivo a subir e o tamaño máximo dun formulario incluíndo todos os seus campos.
+
::<syntaxhighlight lang="bash">
−
: Lembrar que o arquivo de configuración de PHP se atopa en:
+
php --ini
−
:* /etc/php5/apache2/php.ini se estados a utilizar un servidor web Apache instalado independentemente.
+
</syntaxhighlight>
−
:* /etc/php5/cli/php.ini se estados a utilizar XAMPP.
 
  
−
: Se queredes aumentar o tamaño deberedes cambiar ambas directivas aos valores desexados.
+
* Nunha instalación Debian/Ubuntu poden existir arquivos diferentes segundo a SAPI empregada, por exemplo:
  
−
: Tedes [http://angelfernandezgonzalez.blogspot.com.es/2015/10/subir-archivos-de-mas-de-8mb-en.html no meu Blog] unha entrada do que hai que facer no caso de estar nun aloxamento compartido.
+
::<syntaxhighlight lang="text">
−
:: No blog se explica os pasos para aplicar no Moodle, pero podería aplicarse en outros escenarios.
+
/etc/php/8.5/apache2/php.ini
 +
/etc/php/8.5/fpm/php.ini
 +
/etc/php/8.5/cli/php.ini
 +
</syntaxhighlight>
  
 +
* <u>Nota:</u> a ruta exacta depende da instalación e da versión instalada.
  
−
:: Teríamos varias opcións, sempre supoñendo que o aloxamento compartido deixe crear arquivos php.ini ou .htaccess que sobrescriban a configuración do servidor web Apache (isto normalmente é así).
+
* Se PHP funciona como módulo de Apache e o servidor o permite, algunhas directivas poden configurarse mediante <code>.htaccess</code>:
−
::* Opción a)
 
−
:::: Crear un arquivo '''php.ini''' no cartafol raíz do noso sitio web e poñer:
 
−
:::::<syntaxhighlight lang="php" line enclose="div" highlight="" >
 
−
upload_max_filesize = 64M
 
−
post_max_size = 64M
 
−
max_execution_time = 300
 
−
</syntaxhighlight>  
 
−
::::: No exemplo estamos a limitar en 64MB o tamaño da subida e o tempo de execución en 5 minutos.
 
  
−
::* Opción b)
+
::<syntaxhighlight lang="apache">
−
:::: Crear un arquivo '''.htaccess''' no cartafol raíz do noso sitio web e poñer:
 
−
:::::<syntaxhighlight lang="php" line enclose="div" highlight="" >
 
 
php_value upload_max_filesize 64M
 
php_value upload_max_filesize 64M
−
php_value post_max_size 64M
+
php_value post_max_size 70M
 
php_value max_execution_time 300
 
php_value max_execution_time 300
 
php_value max_input_time 300
 
php_value max_input_time 300
−
</syntaxhighlight>  
+
</syntaxhighlight>
−
::::: No exemplo estamos a limitar en 64MB o tamaño da subida e o tempo de execución en 5 minutos.
+
 
 +
* Se se utiliza CGI/FastCGI ou PHP-FPM, <code>php_value</code> en <code>.htaccess</code> pode non funcionar. Dependendo do servidor poden empregarse <code>.user.ini</code>, a configuración do ''pool'' de PHP-FPM ou a configuración proporcionada polo servizo de aloxamento.
  
 +
== Verificando os datos dun formulario ==
  
−
<br />
+
* Podemos comprobar varios campos obrigatorios percorrendo un array cos seus nomes.
  
−
== Verificando os datos dun formulario ==
+
::<syntaxhighlight lang="php" line enclose="div">
 +
$camposObrigatorios = ['txtNome', 'txtEmail'];
  
−
* Unha forma rápida de verificar que certos datos dun formulario teñan información sería:
+
$erros = [];
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
 
−
<?php 
 
−
// Campos obrigatorios
 
−
$campos_obrigatorios = array('txtNome', 'txtEmail');
 
  
−
// Comprobamos que os campos obrigatorios teñan datos
+
foreach ($camposObrigatorios as $campo) {
−
$erro = false;
+
    if (!isset($_POST[$campo]) || !is_string($_POST[$campo]) || trim($_POST[$campo]) === '') {
−
foreach ($campos_obrigatorios as $campo) {
+
        $erros[] = "O campo $campo é obrigatorio.";
−
  if (empty($_POST[$campo])) $erro = true;
+
    }
 
}
 
}
  
−
if ($erro) {
+
if ($erros !== []) {
−
  // Non existe algún dos campos obrigatorios
+
    foreach ($erros as $erro) {
−
}  
+
        echo htmlspecialchars($erro, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '<br>';
−
 
+
    }
 +
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
−
== Creando arrays cos datos dun formulario ==
+
* Empregamos <code>trim()</code> porque unha cadea formada só por espazos tampouco debería considerarse un valor válido para estes campos.
  
−
* Algunhas veces, cando xeramos formularios dinamicamente, teremos que asignar un nome a cada campo do formulario. Pero neste tipo de formularios dinámicos non sabemos cantos 'nomes' imos xerar.
+
* <u>Nota:</u> Non sempre debemos empregar <code>empty()</code> para validar campos obrigatorios, xa que a cadea <code>"0"</code> considérase baleira por <code>empty()</code> e nalgúns formularios pode ser un valor válido.
  
−
* Imaxinemos unha páxina na que queremos xerar dinamicamente un formulario de alta de novos exemplares dun libro determinado.
 
−
: Así temos un libros cos seus datos (titulo, isbn,...)
 
−
: Dispoñemos de exemplares no que queremos gardar: estado (bo, mal, normal).
 
  
−
: Cando eu dea de alta o libro vou dar os seus datos máis o número de exemplares. Imaxinemos que poño que do libro 'A illa do tesouro' teño 3 exemplares.
+
== Creando arrays cos datos dun formulario ==
−
: Este dato pode variar dun libro a outro e non sei de antemán cantos exemplares vou ter.
 
  
−
: Se creo agora o formulario para eses 3 exemplares tería que crear algo así:
+
* Nos formularios xerados dinamicamente pode haber un número variable de campos. HTML permite empregar corchetes no atributo <code>name</code> para que PHP reciba os valores nun array.
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
 
−
<input type='text' name='txtEstado1' />
 
−
<input type='text' name='txtEstado2' />
 
−
<input type='text' name='txtEstado3' />
 
−
</syntaxhighlight>
 
  
−
Cando eu preme o 'action' do formulario os datos pasará á seguinte páxina e eu tería que saber cantos 'txtEstado?' se envían.
+
* Imaxinemos que queremos introducir o estado de varios exemplares dun libro e que o número de exemplares non se coñece de antemán.
  
−
* Unha forma moito máis sinxela de facelo é a seguinte:
+
: En lugar de crear nomes diferentes:
  
−
* Definimos o campo que vai levar o estado có seguinte nome: txtEstado'''[ ]'''
+
::<syntaxhighlight lang="html">
−
: Creamos cada input desta forma:
+
<input type="text" name="txtEstado1">
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
+
<input type="text" name="txtEstado2">
−
for($cont=0;$cont<$numExemplares;$cont++){
+
<input type="text" name="txtEstado3">
−
<input type='text' name='txtEstado[]' />
 
−
}
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
−
* Cando estes datos cheguen á seguinte páxina o dato: $_POST['txtEstado'] vai ser un array cos datos de todos os exemplares e xa podemos traballar con el como ata o de agora.
+
: podemos empregar:
  
 +
::<syntaxhighlight lang="php">
 +
<?php for ($cont = 0; $cont < $numExemplares; $cont++): ?>
 +
    <input type="text" name="txtEstado[]">
 +
<?php endfor; ?>
 +
</syntaxhighlight>
  
 +
* Ao recibir o formulario, <code>$_POST['txtEstado']</code> será un array.
  
 +
* Debemos comprobar que realmente recibimos un array antes de percorrelo:
  
−
<br />
+
::<syntaxhighlight lang="php">
−
== Exercicios propostos ==
+
$estados = $_POST['txtEstado'] ?? [];
  
−
* '''Exercicio 1:''' Crea unha páxina cun formulario e dous campos, nome e idade. O método de envío debe ser aquel que o os datos van dentro do corpo do formulario. O destino do formulario é a mesma páxina (non vale empregar o URL escrita a man). O campo de nome non debe deixar escribir máis de vinte caracteres e o de idade dous caracteres. Terá un botón Enviar Datos. En caso de que se envíen os datos, amosará nunha lista os datos enviados. No caso de que non se envíe algún dato debe amosar un texto indicando que o dato é obrigatorio e amosar outra vez o formulario. Deberás verificar que o que se envía e un número (idade) de dous caracteres e un nome (cadea) quitando todos os caracteres que poidan dar lugar a algún tipo de ataque de inxección de código. Deberá amosar en forma de lista os datos enviados e non amosar o formulario.
+
if (!is_array($estados)) {
 +
    $estados = [];
 +
}
  
 +
foreach ($estados as $estado) {
 +
    if (!is_string($estado)) {
 +
        continue;
 +
    }
  
 +
    $estado = trim($estado);
  
−
* '''Exercicio 2:''' Fai o necesario para xestionar as operacións de alta, baixa e modifcación dunha lista de froitas. Se debe asegurar que no texto da froita non se poida empregar nun ataque XSS ou de inxección de código. Se ven baleiro o campo de texto debe amosar o erro. Debe amosarse en forma de táboa a lista de froitas engadidas. Ao carón de cada froita debe aparecer un botón cun texto eliminar, que borre dito elemento da lista de froitas. Se non hai elementos na lista debe amosar o texto 'Cesta baleira'. Nun arquivo separado de nome operacionsFroitas, se gardará nunha variable o array de froitas e as funcións para verificar que o nome da froita sexa correcto e cambiar no nome os caracteres non permitidos. Se ven baleiro debe devolver false. Outra función para engadir unha froita ao array e se a froita xa estivera engadidad non facer nada, una función que dada unha froita, a elimine do array se existe. Dito arquivo é necesario para o funcionamento da páxina principal e sabemos que non vai cargarse nada máis que unha vez. Terá de nome operaciónsFroitas.php e estará gardado no cartafol /utilidades/.
+
    // Validar $estado segundo as regras da aplicación.
 +
}
 +
</syntaxhighlight>
  
  
 +
== Exercicios propostos ==
  
−
* '''Exercicio 3:''' Crea nun arquivo separado de nome /utilidades/usuarios, un array de datos de usuario (cada usuario ten: id, nome, tfno, email). Nese mesmo arquivo define unha función de nome obterUsuarioPorID que en base a un id, busque no array de usuarios, toda a información do usuario con ese id e que devolva toda a información dese usuario se existe. Se non existe debe devolver null. Amosa unha páxina de nome listaUsuarios, unha lista de nomes de usuario, en forma de enlace. Ao premer no nome, deberá amosar toda a información do usuario noutra páxina diferente de nome detalleUsuario . Esa segunda páxina terá un enlace para ir á primeira páxina e volver a listar os usuarios.
+
* '''Exercicio 1:''' Crea unha páxina cun formulario e dous campos, nome e idade. O método de envío será POST e o destino será a mesma páxina, sen escribir manualmente o seu nome na URL. O nome será obrigatorio e terá unha lonxitude máxima de 20 caracteres. A idade deberá ser un número enteiro de dous díxitos. Todos os datos recibidos deberán validarse no servidor. Ao amosar datos introducidos polo usuario na páxina HTML deberán escaparse correctamente para evitar ataques XSS. O formulario terá un botón "Enviar datos". Se algún dato non é válido, amosarase unha mensaxe de erro e volverase amosar o formulario conservando os valores correctos introducidos. Se todos os datos son válidos, amosaranse nunha lista e non se volverá amosar o formulario. Debes diferenciar entre validación da entrada e escapado da saída HTML.
−
: Se envías un id que non existe deberá amosar unha mensaxe. Comproba como podes modificar na URL o número de ID a buscar.
 
  
 +
* '''Exercicio 2:''' Fai o necesario para xestionar a alta, baixa e baleirado dunha lista de froitas. O nome dunha froita debe validarse no servidor e todos os valores deben escaparse ao seren mostrados en HTML. Se o campo está baleiro ou non cumpre o formato permitido, amosarase un erro. A lista amosarase nunha táboa e ao carón de cada froita haberá un botón para eliminala. Se non hai elementos amosarase "Cesta baleira". Nun arquivo separado <code>/utilidades/operacionsFroitas.php</code> gardaranse as funcións necesarias. O arquivo é imprescindible e só debe cargarse unha vez, polo que se empregará <code>require_once</code>. Para manter a lista entre peticións neste exercicio pódense empregar campos ocultos, lembrando que os seus valores poden ser modificados polo cliente e deben validarse de novo. Máis adiante substituiremos este mecanismo por sesións.
  
 +
* '''Exercicio 3:''' Crea nun arquivo separado <code>/utilidades/usuarios.php</code> un array de usuarios. Cada usuario terá: id, nome, teléfono e correo electrónico. Nese mesmo arquivo define unha función <code>obterUsuarioPorID()</code> que reciba un enteiro e devolva o usuario correspondente ou <code>null</code> se non existe. Crea unha páxina <code>listaUsuarios.php</code> que amose os nomes como enlaces a <code>detalleUsuario.php</code>. O identificador enviarase mediante GET. Na páxina de detalle debes validar que o ID recibido é un enteiro positivo e escapar os datos antes de amosalos. Proba a modificar manualmente o parámetro <code>id</code> da URL.
  
  
 +
=== Solución Exercicios propostos ===
  
 +
==== Solución Exercicio 1 ====
  
 +
::<syntaxhighlight lang="php" enclose="div">
 +
<?php
  
 +
$nome = '';
 +
$idadeTexto = '';
 +
$idade = null;
 +
$erros = [];
 +
$formularioEnviado = $_SERVER['REQUEST_METHOD'] === 'POST';
  
 +
if ($formularioEnviado) {
 +
    $nomeRecibido = $_POST['nome'] ?? '';
 +
    $idadeRecibida = $_POST['idade'] ?? '';
  
 +
    // Comprobamos que os datos recibidos sexan cadeas.
 +
    // Un usuario podería manipular a petición e enviar un array.
 +
    if (!is_string($nomeRecibido)) {
 +
        $nomeRecibido = '';
 +
    }
  
 +
    if (!is_string($idadeRecibida)) {
 +
        $idadeRecibida = '';
 +
    }
  
 +
    $nome = trim($nomeRecibido);
 +
    $idadeTexto = trim($idadeRecibida);
  
 +
    /*
 +
    * Validación do nome.
 +
    *
 +
    * PHP non dispón dun FILTER_VALIDATE específico para nomes de persoa.
 +
    * Neste exemplo comprobamos unicamente as regras da nosa aplicación:
 +
    * que exista e que non supere os 20 caracteres.
 +
    */
 +
    if ($nome === '') {
 +
        $erros[] = 'O campo nome é obrigatorio.';
 +
    } elseif (mb_strlen($nome) > 20) {
 +
        $erros[] = 'O campo nome non pode ter máis de 20 caracteres.';
 +
    }
  
 +
    /*
 +
    * Validación da idade.
 +
    *
 +
    * Empregamos FILTER_VALIDATE_INT para comprobar que realmente
 +
    * representa un número enteiro.
 +
    *
 +
    * Ademais, limitamos o valor entre 10 e 99 porque o exercicio
 +
    * indica que a idade debe ter dous díxitos.
 +
    */
 +
    $idadeValidada = filter_var(
 +
        $idadeTexto,
 +
        FILTER_VALIDATE_INT,
 +
        [
 +
            'options' => [
 +
                'min_range' => 10,
 +
                'max_range' => 99
 +
            ]
 +
        ]
 +
    );
  
 +
    if ($idadeTexto === '') {
 +
        $erros[] = 'O campo idade é obrigatorio.';
 +
    } elseif ($idadeValidada === false) {
 +
        $erros[] = 'A idade debe ser un número enteiro de dous díxitos.';
 +
    } else {
 +
        $idade = $idadeValidada;
 +
    }
 +
}
  
 +
function escaparHtml(string $valor): string
 +
{
 +
    return htmlspecialchars(
 +
        $valor,
 +
        ENT_QUOTES | ENT_SUBSTITUTE,
 +
        'UTF-8'
 +
    );
 +
}
  
−
 
+
$datosValidos = $formularioEnviado && $erros === [];
−
<br />
+
?>
−
=== Solución Exercicios propostos ===
 
−
 
 
−
* '''Solución Exercicio 1:''' Crea unha páxina cun formulario e dous campos, nome e idade. O método de envío debe ser aquel que o os datos van dentro do corpo do formulario. O destino do formulario é a mesma páxina (non vale empregar o URL escrita a man). O campo de nome non debe deixar escribir máis de vinte caracteres e o de idade dous caracteres. Terá un botón Enviar Datos. En caso de que se envíen os datos, amosará nunha lista os datos enviados. No caso de que non se envíe algún dato debe amosar un texto indicando que o dato é obrigatorio e amosar outra vez o formulario. Deberás verificar que o que se envía e un número (idade) de dous caracteres e un nome (cadea) quitando todos os caracteres que poidan dar lugar a algún tipo de ataque de inxección de código. Deberá amosar en forma de lista os datos enviados e non amosar o formulario.
 
−
 
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
 
<!DOCTYPE html>
 
<!DOCTYPE html>
 
<html lang="gl">
 
<html lang="gl">
 
<head>
 
<head>
 
     <meta charset="UTF-8">
 
     <meta charset="UTF-8">
−
     <title>Formulario en Galego</title>
+
     <title>Formulario</title>
 
</head>
 
</head>
 
<body>
 
<body>
−
<?php
 
−
$nome = "";
 
−
$idade = "";
 
−
$erros = array();
 
−
$formulario_mostrado = true; // Variable para controlar si se muestra el formulario
 
  
 +
<?php if (!$datosValidos): ?>
 +
 +
    <h1>Formulario</h1>
  
−
if (isset($_POST['btnEnviar'])) {  // Vimos de premer o botón btnEnviar
+
    <?php if ($formularioEnviado && $erros !== []): ?>
−
    // Recoller e limpar os datos enviados
+
        <h2>Erros</h2>
−
    $nome = filter_input(INPUT_POST, 'nome', FILTER_SANITIZE_STRING);
+
        <ul>
−
     $idade = filter_input(INPUT_POST, 'idade', FILTER_SANITIZE_STRING);
+
            <?php foreach ($erros as $erro): ?>
 +
                <li><?php echo escaparHtml($erro); ?></li>
 +
            <?php endforeach; ?>
 +
        </ul>
 +
     <?php endif; ?>
  
−
     // Validación dos datos
+
     <form method="post" action="">
−
    if (empty($nome)) {
+
        <div>
−
        $erros[] = "O campo nome é obrigatorio.";
+
            <label for="nome">Nome:</label>
−
    } elseif (strlen($nome) > 20) {
+
            <input
−
         $erros[] = "O campo nome non pode ter máis de 20 caracteres.";
+
                type="text"
−
    }
+
                id="nome"
 +
                name="nome"
 +
                maxlength="20"
 +
                required
 +
                value="<?php echo escaparHtml($nome); ?>"
 +
            >
 +
         </div>
  
−
    if (empty($idade)) {
+
        <div>
−
        $erros[] = "O campo idade é obrigatorio.";
+
            <label for="idade">Idade:</label>
−
    } elseif (!(filter_input(INPUT_POST, 'idade', FILTER_VALIDATE_INT) && $idade > 0)) {
+
            <input
−
        $erros[] = "A idade debe ser un número positivo.";
+
                type="text"
−
    } elseif (strlen($idade) != 2) {
+
                id="idade"
−
        $erros[] = "A idade debe ter exactamente dous caracteres.";
+
                name="idade"
−
    }
+
                inputmode="numeric"
−
}
+
                maxlength="2"
−
else{  // Pode vir de premer no enlace cando amosa os datos
+
                size="3"
−
    $erros[] = "Non enviaches datos.";
+
                required
−
}
+
                value="<?php echo escaparHtml($idadeTexto); ?>"
−
// Comprobar si hay errores
+
            >
−
if (empty($erros)) {
+
        </div>
−
  $formulario_mostrado = false; // No mostrar el formulario si no hay errores
 
−
}
 
−
?>
 
  
−
<?php
+
         <button type="submit">Enviar datos</button>
−
if ($formulario_mostrado) {
+
    </form>
−
    // Mostrar el formulario
 
−
    ?>
 
−
    <h2>Formulario</h2>
 
−
    <form method="POST" action="<?php echo htmlentities($_SERVER["PHP_SELF"]); ?>">
 
−
         <label for="nome">Nome:</label>
 
−
        <input type="text" id="nome" name="nome" maxlength="20" value="<?php echo $nome; ?>"><br><br>
 
  
−
        <label for="idade">Idade:</label>
+
<?php else: ?>
−
        <input type="text" id="idade" name="idade" maxlength="2" size="3" value="<?php echo $idade; ?>"><br><br>
 
  
−
         <input type="submit" name="btnEnviar" value="Enviar Datos"><br><br>
+
    <h1>Datos enviados</h1>
−
     </form>
+
    <ul>
 +
         <li>Nome: <?php echo escaparHtml($nome); ?></li>
 +
        <li>Idade: <?php echo $idade; ?></li>
 +
     </ul>
  
−
<?php
+
<?php endif; ?>
−
}
 
−
if(empty($erros)) {
 
−
    // Mostrar os datos enviados nunha lista
 
−
    echo "<h2>Datos enviados:</h2>";
 
−
    echo "<ul>";
 
−
    echo "<li>Nome: $nome</li>";
 
−
    echo "<li>Idade: $idade</li>";
 
−
    echo "</ul>";
 
−
}
 
−
else {
 
−
    // Mostrar erros
 
−
    echo "<h2>Erros:</h2>";
 
−
    echo "<ul>";
 
−
    foreach ($erros as $erro) {
 
−
        echo "<li>$erro</li>";
 
−
    }
 
−
    echo "</ul>";
 
−
}
 
−
?>
 
  
 
</body>
 
</body>
Línea 1117: Línea 1194:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 +
* Fixarse en que o nome non se transforma con filtros HTML antes de validalo. Gardamos o valor lóxico e aplicamos <code>htmlspecialchars()</code> unicamente cando o inserimos na saída HTML.
 +
 +
<br />
  
 +
==== Solución Exercicio 2 ====
  
−
<br />
+
: '''Arquivo /utilidades/operacionsFroitas.php'''
−
* '''Solución Exercicio 2:''' Fai o necesario para xestionar as operacións de alta, baixa e modifcación dunha lista de froitas. Se debe asegurar que no texto da froita non se poida empregar nun ataque XSS ou de inxección de código. Se ven baleiro o campo de texto debe amosar o erro. Debe amosarse en forma de táboa a lista de froitas engadidas. Ao carón de cada froita debe aparecer un botón cun texto eliminar, que borre dito elemento da lista de froitas. Se non hai elementos na lista debe amosar o texto 'Cesta baleira'. Nun arquivo separado de nome operacionsFroitas, se gardará nunha variable o array de froitas e as funcións para verificar que o nome da froita sexa correcto e cambiar no nome os caracteres non permitidos. Se ven baleiro debe devolver false. Outra función para engadir unha froita ao array e se a froita xa estivera engadidad non facer nada, una función que dada unha froita, a elimine do array se existe. Dito arquivo é necesario para o funcionamento da páxina principal e sabemos que non vai cargarse nada máis que unha vez. Terá de nome operaciónsFroitas.php e estará gardado no cartafol /utilidades/.
 
  
−
: '''Arquivo /utilidades/operacionsFroitas.inc.php'''
+
::<syntaxhighlight lang="php" enclose="div">
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
 
<?php
 
<?php
−
// Array para gardar as froitas
 
−
$listaFroitas = [];
 
  
 
/**
 
/**
−
  * Función para verificar se o nome da froita é válido
+
  * Valida e normaliza o nome dunha froita.
−
  * Devolve o nome cos caracteres non permitidos escapados ou false en caso de que esta baleiro
+
  *
−
  * @param string nome: nome da froita a chequear
+
  * @param string $nome Nome recibido.
−
  * */  
+
  * @return string|false Nome normalizado ou false se non é válido.
−
function verificarNomeFroita($nome) {
+
*/
−
     // Verificamos que o nome non estea baleiro e que conteña caracteres validos
+
function verificarNomeFroita(string $nome): string|false
−
     $nome = filter_var($nome, FILTER_SANITIZE_STRING);
+
{
−
     if (empty($nome)) return false;
+
     $nome = trim($nome);
−
     else return $nome;
+
 
 +
     if ($nome === '') {
 +
        return false;
 +
    }
 +
 
 +
    if (mb_strlen($nome) > 40) {
 +
        return false;
 +
    }
 +
 
 +
    // Permitimos letras Unicode, espazos, apóstrofos e guións.
 +
     if (!preg_match("/^[\p{L}\p{M} '-]+$/u", $nome)) {  // => Non tedes por que saber expresións regulares que se ven noutro módulo.
 +
        return false;
 +
     }
 +
 
 +
    return $nome;
 
}
 
}
  
 
/**
 
/**
−
  * Función para engadir unha froita á lista
+
  * Engade unha froita se aínda non existe.
−
  * @param string nome: nome da froita
+
*
−
  */  
+
* @param array $lista Lista actual.
−
function engadirFroita($nomeFroita) {
+
  * @param string $froita Froita a engadir.
−
    global $listaFroitas;
+
* @return array Nova lista.
−
   
+
  */
−
     if (!in_array($nomeFroita, $listaFroitas)) {
+
function engadirFroita(array $lista, string $froita): array
−
         $listaFroitas[] = $nomeFroita;
+
{
 +
     if (!in_array($froita, $lista, true)) {
 +
         $lista[] = $froita;
 
     }
 
     }
−
 
+
 
 +
    return $lista;
 
}
 
}
  
 
/**
 
/**
−
  * Función para eliminar unha froita da lista
+
  * Elimina unha froita da lista.
−
  *   @param string nome: nome da froita a chequear
+
*
−
  */  
+
* @param array $lista Lista actual.
 +
  * @param string $froita Froita a eliminar.
 +
* @return array Nova lista.
 +
  */
 +
function eliminarFroita(array $lista, string $froita): array
 +
{
 +
    $indice = array_search($froita, $lista, true);
  
−
function eliminarFroita($nomeFroita) {
 
−
    global $listaFroitas;
 
−
    $indice = array_search($nomeFroita, $listaFroitas);
 
 
     if ($indice !== false) {
 
     if ($indice !== false) {
−
         unset($listaFroitas[$indice]);
+
         unset($lista[$indice]);
 +
        $lista = array_values($lista);
 
     }
 
     }
 +
 +
    return $lista;
 
}
 
}
 +
</syntaxhighlight>
 +
 +
: '''Arquivo xestionFroitas.php'''
 +
 +
::<syntaxhighlight lang="php" enclose="div">
 +
<?php
 +
 +
require_once __DIR__ . '/utilidades/operacionsFroitas.php';
 +
 +
function escaparHtml(string $valor): string
 +
{
 +
    return htmlspecialchars(
 +
        $valor,
 +
        ENT_QUOTES | ENT_SUBSTITUTE,
 +
        'UTF-8'
 +
    );
 +
}
 +
 +
$listaFroitas = [];
 +
$erro = null;
 +
 +
// Recuperamos a lista dos campos ocultos.
 +
// Estes valores veñen do cliente, polo que os validamos novamente.
 +
$listaRecibida = $_POST['listaFroitas'] ?? [];
 +
 +
if (is_array($listaRecibida)) {
 +
    foreach ($listaRecibida as $froitaRecibida) {
 +
        if (!is_string($froitaRecibida)) {
 +
            continue;
 +
        }
 +
 +
        $froitaValida = verificarNomeFroita($froitaRecibida);
  
−
/*
+
        if ($froitaValida !== false) {
−
* Función para obter a lista de froitas
+
            $listaFroitas = engadirFroita($listaFroitas, $froitaValida);
−
*/
+
        }
−
function obterListaFroitas() {
+
     }
−
    global $listaFroitas;
 
−
     return $listaFroitas;
 
 
}
 
}
−
?>
 
  
−
</syntaxhighlight>
+
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
 +
    if (isset($_POST['btnBaleirar'])) {
 +
        $listaFroitas = [];
 +
    }
 +
 
 +
    if (isset($_POST['btnEngadir'])) {
 +
        $novaFroita = $_POST['novaFroita'] ?? '';
 +
 
 +
        if (!is_string($novaFroita)) {
 +
            $novaFroita = '';
 +
        }
 +
 
 +
        $novaFroita = verificarNomeFroita($novaFroita);
 +
 
 +
        if ($novaFroita === false) {
 +
            $erro = 'O nome da froita non é válido.';
 +
        } else {
 +
            $listaFroitas = engadirFroita($listaFroitas, $novaFroita);
 +
        }
 +
    }
  
 +
    if (isset($_POST['btnBorrar']) && is_string($_POST['btnBorrar'])) {
 +
        $froitaAEliminar = verificarNomeFroita($_POST['btnBorrar']);
  
−
: '''Arquivo xestionFroitas.php'''
+
        if ($froitaAEliminar !== false) {
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
+
            $listaFroitas = eliminarFroita($listaFroitas, $froitaAEliminar);
 +
        }
 +
    }
 +
}
 +
?>
 
<!DOCTYPE html>
 
<!DOCTYPE html>
−
<html>
+
<html lang="gl">
 
<head>
 
<head>
−
     <title>Xestión de Lista de Froitas</title>
+
    <meta charset="UTF-8">
 +
     <title>Xestión de froitas</title>
 
</head>
 
</head>
 
<body>
 
<body>
−
    <h1>Xestión de Lista de Froitas</h1>
 
  
−
     <?php
+
<h1>Xestión de froitas</h1>
−
     // Incluímos o arquivo operacionsFroitas.inc.php
+
 
−
     require 'utilidades/operacionsFroitas.inc.php';
+
<?php if ($erro !== null): ?>
 +
     <p><?php echo escaparHtml($erro); ?></p>
 +
<?php endif; ?>
 +
 
 +
<form method="post" action="">
 +
 
 +
     <?php foreach ($listaFroitas as $clave => $froita): ?>
 +
        <input
 +
            type="hidden"
 +
            name="listaFroitas[<?php echo $clave; ?>]"
 +
            value="<?php echo escaparHtml($froita); ?>"
 +
        >
 +
     <?php endforeach; ?>
  
−
     // Xa tiñamos froitas no listado en forma de campos ocultos
+
     <?php if ($listaFroitas === []): ?>
−
    if (isset($_POST['listaFroitas'])) $listaFroitas = $_POST['listaFroitas'];
+
        <p>Cesta baleira.</p>
−
     if (isset($_POST['btnBaleirar'])) $listaFroitas = array();
+
     <?php else: ?>
 +
        <table>
 +
            <thead>
 +
            <tr>
 +
                <th>Froita</th>
 +
                <th>Acción</th>
 +
            </tr>
 +
            </thead>
 +
            <tbody>
  
−
    // Comprobamos se se enviou unha nova froita => pulsa botón
+
            <?php foreach ($listaFroitas as $froita): ?>
−
    if (isset($_POST['btnEngadir']) && isset($_POST['novaFroita'])) {  // Non verifico empty xa que o fai a función
+
                <tr>
−
        $novaFroita = verificarNomeFroita($_POST['novaFroita']);
+
                    <td><?php echo escaparHtml($froita); ?></td>
−
        // Verificamos se o nome da froita é válido
+
                    <td>
−
        if ($novaFroita) {
+
                        <button
−
            engadirFroita($novaFroita);
+
                            type="submit"
−
        } else {
+
                            name="btnBorrar"
−
            echo "<p class='erro'>Non introducido o nome da froita.</p>";
+
                            value="<?php echo escaparHtml($froita); ?>"
−
        }
+
                        >
−
    }
+
                            Eliminar
 +
                        </button>
 +
                    </td>
 +
                </tr>
 +
            <?php endforeach; ?>
  
−
    // Comprobamos se se enviou unha froita para eliminar
+
            </tbody>
−
     if (isset($_POST['btnBorrar'])) {
+
        </table>
−
        $froitaAEliminar = $_POST['btnBorrar'];
+
     <?php endif; ?>
−
        eliminarFroita($froitaAEliminar);
 
−
    }
 
  
−
     // Obtemos a lista de froitas
+
     <div>
−
     $listaFroitas = obterListaFroitas();
+
        <label for="novaFroita">Nome da froita:</label>
 +
        <input type="text" id="novaFroita" name="novaFroita" maxlength="40">
 +
        <button type="submit" name="btnEngadir" value="1">Engadir froita</button>
 +
     </div>
  
−
     // Amosamos a táboa de froitas ou a mensaxe de cesta baleira
+
     <?php if ($listaFroitas !== []): ?>
−
    if (empty($listaFroitas)) {
+
         <button type="submit" name="btnBaleirar" value="1">Baleirar cesta</button>
−
        echo "<p>Cesta baleira.</p>";
+
    <?php endif; ?>
−
    }
 
−
    echo "<h2>Operacións Froita</h2>";
 
−
    printf('<form method="post" action="%s">',htmlentities($_SERVER['PHP_SELF']));
 
−
        echo "<table>";
 
−
         echo "<tr><th>Froita</th><th>Acción</th></tr>";
 
−
        foreach ($listaFroitas as $key=>$froita) {
 
−
            echo "<tr>";
 
−
            echo "<td>$froita";
 
−
            printf('<input type="hidden" value="%s" name="listaFroitas[%s]" />',$froita,$key);
 
−
            echo "</td>";
 
−
            printf("<td><button type='submit' id='btnBorrar' name='btnBorrar' value='%s'>Borrar</button></td>",$froita);
 
−
            echo "</tr>";
 
−
        }
 
−
        echo "</table>";
 
−
    ?>
 
  
−
        <label for="novaFroita">Nome da Froita:</label>
+
</form>
−
        <input type="text" id="novaFroita" name="novaFroita">
 
−
        <button type="submit" name='btnEngadir' value='engadir'>Engadir Froita</button>
 
−
        <?php if(!empty($listaFroitas)) ?>
 
−
            <div><button type="submit" name='btnBaleirar' value='baleirar'>Baleirar Cesta</button></div>
 
−
           
 
  
−
    </form>
 
 
</body>
 
</body>
 
</html>
 
</html>
 +
</syntaxhighlight>
  
−
</syntaxhighlight>
+
* <u>Nota:</u> Este exercicio conserva o estado mediante campos ocultos porque as sesións aínda non se estudaron. Os campos ocultos poden ser manipulados polo cliente; por iso a solución volve validar todas as froitas recibidas. Nunha aplicación real, para manter estado interno empregaremos normalmente sesións ou persistencia no servidor.
  
 +
<br />
  
 +
==== Solución Exercicio 3 ====
  
−
<br />
+
: '''Arquivo utilidades/usuarios.php'''
−
* '''Solución Exercicio 3:''' Crea nun arquivo separado de nome /utilidades/usuarios, un array de datos de usuario (cada usuario ten: id, nome, tfno, email). Nese mesmo arquivo define unha función de nome obterUsuarioPorID que en base a un id, busque no array de usuarios, toda a información do usuario con ese id e que devolva toda a información dese usuario se existe. Se non existe debe devolver null. Amosa unha páxina de nome listaUsuarios, unha lista de nomes de usuario, en forma de enlace. Ao premer no nome, deberá amosar toda a información do usuario noutra páxina diferente de nome detalleUsuario . Esa segunda páxina terá un enlace para ir á primeira páxina e volver a listar os usuarios.
 
−
: Se envías un id que non existe deberá amosar unha mensaxe. Comproba como podes modificar na URL o número de ID a buscar.
 
  
−
: '''Páxina usuarios.inc.php'''
+
::<syntaxhighlight lang="php" enclose="div">
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
 
<?php
 
<?php
−
// Array de datos de usuarios (id, nome, tfno, email)
+
 
 
$usuarios = [
 
$usuarios = [
−
     ["id" => 1, "nome" => "Usuario 1", "tfno" => "123456789", "email" => "usuario1@example.com"],
+
     [
−
     ["id" => 2, "nome" => "Usuario 2", "tfno" => "987654321", "email" => "usuario2@example.com"],
+
        'id' => 1,
−
     ["id" => 3, "nome" => "Usuario 3", "tfno" => "555555555", "email" => "usuario3@example.com"]
+
        'nome' => 'Usuario 1',
 +
        'tfno' => '123456789',
 +
        'email' => 'usuario1@example.com',
 +
    ],
 +
     [
 +
        'id' => 2,
 +
        'nome' => 'Usuario 2',
 +
        'tfno' => '987654321',
 +
        'email' => 'usuario2@example.com',
 +
    ],
 +
     [
 +
        'id' => 3,
 +
        'nome' => 'Usuario 3',
 +
        'tfno' => '555555555',
 +
        'email' => 'usuario3@example.com',
 +
    ],
 
];
 
];
  
−
// Función para obter os datos dun usuario a partir do ID
+
/**
−
function obterUsuarioPorID($id) {
+
* Obtén os datos dun usuario a partir do seu ID.
 +
*/
 +
function obterUsuarioPorID(int $id): ?array
 +
{
 
     global $usuarios;
 
     global $usuarios;
 +
 
     foreach ($usuarios as $usuario) {
 
     foreach ($usuarios as $usuario) {
−
         if ($usuario["id"] == $id) {
+
         if ($usuario['id'] === $id) {
 
             return $usuario;
 
             return $usuario;
 
         }
 
         }
 
     }
 
     }
−
     return null; // Devolve null se o usuario non se atopa
+
 
 +
     return null;
 
}
 
}
−
?>
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
−
: '''Páxina listaUsuarios.php'''
+
: '''Arquivo listaUsuarios.php'''
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
+
 
 +
::<syntaxhighlight lang="php" enclose="div">
 +
<?php
 +
 
 +
require_once __DIR__ . '/utilidades/usuarios.php';
 +
 
 +
function escaparHtml(string $valor): string
 +
{
 +
    return htmlspecialchars(
 +
        $valor,
 +
        ENT_QUOTES | ENT_SUBSTITUTE,
 +
        'UTF-8'
 +
    );
 +
}
 +
?>
 
<!DOCTYPE html>
 
<!DOCTYPE html>
−
<html>
+
<html lang="gl">
 
<head>
 
<head>
−
     <title>Listado de Usuarios</title>
+
    <meta charset="UTF-8">
 +
     <title>Listado de usuarios</title>
 
</head>
 
</head>
 
<body>
 
<body>
−
    <h1>Listado de Usuarios</h1>
 
  
 +
<h1>Listado de usuarios</h1>
 +
 +
<ul>
 +
<?php foreach ($usuarios as $usuario): ?>
 
     <?php
 
     <?php
−
     require_once 'utilidades/usuarios.inc.php';
+
     $url = 'detalleUsuario.php?' . http_build_query([
 +
        'id' => $usuario['id'],
 +
    ]);
 +
    ?>
 +
    <li>
 +
        <a href="<?php echo escaparHtml($url); ?>">
 +
            <?php echo escaparHtml($usuario['nome']); ?>
 +
        </a>
 +
    </li>
 +
<?php endforeach; ?>
 +
</ul>
  
−
    // Amosar a lista de usuarios como enlaces
 
−
    echo "<ul>";
 
−
    foreach ($usuarios as $usuario) {
 
−
        echo "<li><a href='detalleUsuario.php?id={$usuario["id"]}'>{$usuario["nome"]}</a></li>";
 
−
    }
 
−
    echo "</ul>";
 
−
    ?>
 
 
</body>
 
</body>
 
</html>
 
</html>
 +
</syntaxhighlight>
 +
 +
: '''Arquivo detalleUsuario.php'''
 +
 +
::<syntaxhighlight lang="php" enclose="div">
 +
<?php
  
−
</syntaxhighlight>
+
require_once __DIR__ . '/utilidades/usuarios.php';
 +
 
 +
function escaparHtml(string $valor): string
 +
{
 +
    return htmlspecialchars(
 +
        $valor,
 +
        ENT_QUOTES | ENT_SUBSTITUTE,
 +
        'UTF-8'
 +
    );
 +
}
 +
 
 +
$idRecibido = $_GET['id'] ?? null;
 +
 
 +
$usuarioSeleccionado = null;
 +
$erro = null;
 +
 
 +
if ($idRecibido === null) {
 +
    $erro = 'Non se indicou ningún identificador.';
 +
} elseif (!is_string($idRecibido)) {
 +
    $erro = 'O identificador non é válido.';
 +
} else {
 +
    $idUsuario = filter_var(
 +
        $idRecibido,
 +
        FILTER_VALIDATE_INT,
 +
        [
 +
            'options' => [
 +
                'min_range' => 1,
 +
            ],
 +
        ]
 +
    );
  
 +
    if ($idUsuario === false) {
 +
        $erro = 'O identificador non é válido.';
 +
    } else {
 +
        $usuarioSeleccionado = obterUsuarioPorID($idUsuario);
  
−
; '''Páxina detalleUsuario.php'''
+
        if ($usuarioSeleccionado === null) {
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
+
            $erro = 'Non existe ningún usuario con ese identificador.';
 +
        }
 +
    }
 +
}
 +
?>
 
<!DOCTYPE html>
 
<!DOCTYPE html>
−
<html>
+
<html lang="gl">
 
<head>
 
<head>
−
     <title>Detalles do Usuario</title>
+
    <meta charset="UTF-8">
 +
     <title>Detalles do usuario</title>
 
</head>
 
</head>
 
<body>
 
<body>
−
    <h1>Detalles do Usuario</h1>
 
  
−
     <?php
+
<h1>Detalles do usuario</h1>
−
     require 'utilidades/usuarios.inc.php';
+
 
 +
<?php if ($erro !== null): ?>
 +
 
 +
     <p><?php echo escaparHtml($erro); ?></p>
 +
 
 +
<?php else: ?>
 +
 
 +
     <dl>
 +
        <dt>Nome</dt>
 +
        <dd><?php echo escaparHtml($usuarioSeleccionado['nome']); ?></dd>
 +
 
 +
        <dt>Teléfono</dt>
 +
        <dd><?php echo escaparHtml($usuarioSeleccionado['tfno']); ?></dd>
 +
 
 +
        <dt>Correo</dt>
 +
        <dd><?php echo escaparHtml($usuarioSeleccionado['email']); ?></dd>
 +
    </dl>
 +
 
 +
<?php endif; ?>
  
−
    // Verificar se se pasou un ID válido por parámetro
+
<p><a href="listaUsuarios.php">Volver á lista de usuarios</a></p>
−
    if (isset($_GET["id"])) {
 
−
        $idUsuario = $_GET["id"];
 
−
        // Chamar á función para obter os datos do usuario
 
−
        $usuarioSeleccionado = obterUsuarioPorID($idUsuario);
 
  
−
        // Amosar os detalles do usuario
 
−
        if ($usuarioSeleccionado !== null) {
 
−
            echo "<p>Nome: {$usuarioSeleccionado["nome"]}</p>";
 
−
            echo "<p>Teléfono: {$usuarioSeleccionado["tfno"]}</p>";
 
−
            echo "<p>Email: {$usuarioSeleccionado["email"]}</p>";
 
−
            echo "<p><a href='listaUsuarios.php'>Volver á lista de usuarios</a></p>";
 
−
        } else {
 
−
            echo "<p>Usuario non encontrado.</p>";
 
−
        }
 
−
    } else {
 
−
        echo "<p>ID de usuario non especificado.</p>";
 
−
    }
 
−
    ?>
 
 
</body>
 
</body>
 
</html>
 
</html>
−
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
−
 
+
<br> -- [[Usuario:angelfg|Ángel D. Fernández González]] -- (2026).
−
<br> -- [[Usuario:angelfg|Ángel D. Fernández González]] -- (2016).
 

Revisión actual del 07:40 6 oct 2026

Introdución

  • A través da etiqueta <form> podemos enviar datos dunha páxina a outra.
Os principais elementos HTML que poden enviar información dentro dun formulario son os elementos asociados aos formularios HTML.
  • Dentro da etiqueta form temos, entre outros:
  • O atributo method, que normalmente toma os valores get ou post.
  • O atributo action, que indica a URL que vai procesar os datos enviados polo formulario.
  • Se queremos que o formulario se procese na mesma páxina podemos deixar o atributo action baleiro:
1 <form name="form1" action="" method="post">
2 
3 </form>
Outra posibilidade é empregar $_SERVER['PHP_SELF']. Neste caso debemos escapar o seu valor antes de introducilo no HTML:
1 <form name="form1"
2       action="<?php echo htmlspecialchars($_SERVER['PHP_SELF'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?>"
3       method="post">
4 
5 </form>
  • Importante: os datos recibidos do cliente non deben considerarse fiables. Isto é válido tanto para GET como para POST. A validación HTML ou JavaScript mellora a experiencia de usuario, pero a validación de seguridade debe facerse tamén no servidor.
  • Nota: Para pasar datos dunha páxina a outra tamén podemos empregar campos ocultos (hidden). Un campo oculto non é secreto: o usuario pode velo e modificalo, polo que tamén debe validarse no servidor.


Método GET

  • Co método GET os datos envíanse como parámetros na URL. Por exemplo:
https://exemplo.test/buscar.php?q=php&orde=asc
Os datos situados despois do signo ? forman a cadea de consulta ou query string. Os diferentes parámetros sepáranse normalmente mediante &.
  • Características do método GET:
  • Os parámetros son visibles na URL.
  • A URL pode gardarse nos marcadores, copiarse ou compartirse conservando os parámetros.
  • Os parámetros poden quedar rexistrados no historial do navegador, nos logs do servidor e noutros sistemas intermedios.
  • O usuario pode modificar libremente os valores enviados. Polo tanto, os datos GET sempre deben validarse no servidor.
  • Non existe un límite único de tamaño establecido por PHP para unha URL completa; os límites prácticos dependen do navegador, do servidor web, dos proxies e da infraestrutura empregada.
  • GET é apropiado principalmente para operacións de consulta que non deberían modificar o estado da aplicación.
  • As variables enviadas deste xeito están dispoñibles en PHP mediante o array superglobal $_GET.
  • Vexamos un exemplo utilizando un formulario.

Arquivo: UD2_Form_Ex1_eleccion_pelicula.php

 1 <!DOCTYPE html>
 2 <html lang="gl">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>Formulario</title>
 6 </head>
 7 <body>
 8     <form id="frmPelicula" method="get" action="UD2_Form_Ex1_datos_pelicula.php">
 9         <div>
10             <label for="lstPelicula">Película preferida:</label>
11             <select id="lstPelicula" name="lstPelicula">
12                 <option value="ET">E.T. O extraterrestre</option>
13                 <option value="Superman">Supermán</option>
14             </select>
15         </div>
16 
17         <div>
18             <label for="lstXenero">Xénero preferido:</label>
19             <select id="lstXenero" name="lstXenero">
20                 <option value="Acción">Acción</option>
21                 <option value="Ciencia ficción">Ciencia ficción</option>
22             </select>
23         </div>
24 
25         <button type="submit">ENVIAR</button>
26     </form>
27 </body>
28 </html>
  • Fixarse en que enviamos os datos utilizando o método GET.

Arquivo: UD2_Form_Ex1_datos_pelicula.php

 1 <!DOCTYPE html>
 2 <html lang="gl">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>Datos da película</title>
 6 </head>
 7 <body>
 8 <?php
 9 $pelicula = $_GET['lstPelicula'] ?? '';
10 $xenero = $_GET['lstXenero'] ?? '';
11 
12 $peliculaHtml = htmlspecialchars($pelicula, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
13 $xeneroHtml = htmlspecialchars($xenero, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
14 
15 echo 'Os datos que escolliches son:<br><br>';
16 printf('PELÍCULA: %s<br>XÉNERO: %s', $peliculaHtml, $xeneroHtml);
17 ?>
18 </body>
19 </html>
  • Nota: O operador ?? permite indicar un valor alternativo cando unha clave non existe. Deste xeito evitamos acceder directamente a unha clave inexistente de $_GET.
  • Nota: Para visualizar durante o desenvolvemento o conxunto de valores recibidos pódese empregar var_dump():
var_dump($_GET);


Método POST

  • Co método POST os datos do formulario envíanse no corpo da petición HTTP e non forman parte da URL.
  • Isto non significa que POST sexa secreto nin que os seus datos sexan fiables. Un usuario pode inspeccionar e modificar unha petición POST empregando as ferramentas de desenvolvemento do navegador, curl, Postman ou outras ferramentas. Para protexer os datos durante o transporte debe empregarse HTTPS.
  • O tamaño máximo dos datos recibidos mediante POST está condicionado, entre outras cousas, pola directiva post_max_size de PHP. O seu valor por defecto é 8M, pero pode ser modificado pola configuración do servidor.
No caso da subida de arquivos tamén intervén upload_max_filesize, cuxo valor por defecto é 2M. Para permitir arquivos de maior tamaño, post_max_size debe ser maior que upload_max_filesize.
  • A forma de modificar estas directivas depende de como estea instalado PHP. O arquivo .htaccess só permite empregar directivas php_value cando PHP funciona como módulo de Apache e a configuración do servidor o permite. Con CGI/FastCGI ou PHP-FPM poden existir outros mecanismos, por exemplo .user.ini, segundo a configuración do servidor.
  • As variables enviadas mediante POST están dispoñibles no array superglobal $_POST.
  • Modificamos o exemplo anterior para que empregue POST.

Arquivo: UD2_Form_Ex2_eleccion_pelicula.php

 1 <!DOCTYPE html>
 2 <html lang="gl">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>Formulario</title>
 6 </head>
 7 <body>
 8     <form id="frmPelicula" method="post" action="UD2_Form_Ex2_datos_pelicula.php">
 9         <div>
10             <label for="lstPelicula">Película preferida:</label>
11             <select id="lstPelicula" name="lstPelicula">
12                 <option value="ET">E.T. O extraterrestre</option>
13                 <option value="Superman">Supermán</option>
14             </select>
15         </div>
16 
17         <div>
18             <label for="lstXenero">Xénero preferido:</label>
19             <select id="lstXenero" name="lstXenero">
20                 <option value="Acción">Acción</option>
21                 <option value="Ciencia ficción">Ciencia ficción</option>
22             </select>
23         </div>
24 
25         <button type="submit">ENVIAR</button>
26     </form>
27 </body>
28 </html>

Arquivo: UD2_Form_Ex2_datos_pelicula.php

 1 <!DOCTYPE html>
 2 <html lang="gl">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>Datos da película</title>
 6 </head>
 7 <body>
 8 <?php
 9 $pelicula = $_POST['lstPelicula'] ?? '';
10 $xenero = $_POST['lstXenero'] ?? '';
11 
12 $peliculaHtml = htmlspecialchars($pelicula, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
13 $xeneroHtml = htmlspecialchars($xenero, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
14 
15 echo 'Os datos que escolliches son:<br><br>';
16 printf('PELÍCULA: %s<br>XÉNERO: %s', $peliculaHtml, $xeneroHtml);
17 ?>
18 </body>
19 </html>
  • Nota: Durante o desenvolvemento podemos comprobar os datos recibidos con:
var_dump($_POST);


Comprobando se hai datos

  • A páxina que recibe un formulario normalmente vai validar os datos e realizar algunha operación con eles. Por exemplo, pode facer unha consulta, gardar información ou modificar datos existentes.
  • A validación no cliente mediante HTML ou JavaScript é útil, pero non substitúe a validación no servidor.

Comprobando o método da petición

  • Unha forma segura de saber se unha páxina foi enviada mediante POST é consultar $_SERVER['REQUEST_METHOD']:
1 if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
2     exit('Non se enviou o formulario mediante POST');
3 }
  • Se queremos saber se chegou un campo concreto podemos utilizar isset(), array_key_exists() ou o operador ??, segundo o caso:
1 $nome = $_POST['txtNome'] ?? '';
2 
3 if ($nome === '') {
4     echo 'O campo nome non ten datos';
5 }
  • Nota: exit e die finalizan a execución do script.

Tipo dos datos recibidos

  • Os valores simples procedentes dos campos dun formulario chegan normalmente como cadeas de caracteres. Por exemplo, aínda que un input conteña o texto 25, o valor recibido será unha cadea.
  • Non obstante, $_POST tamén pode conter arrays se empregamos nomes do tipo campo[] ou estruturas como usuario[nome]. Os arquivos subidos non aparecen en $_POST, senón en $_FILES.
  • Por este motivo non debemos facer un cast antes de validar o dato. Primeiro comprobamos que a cadea ten o formato esperado e despois facemos a conversión.
Exemplo para unha idade formada só por díxitos:
1 $idadeTexto = $_POST['txtIdade'] ?? '';
2 
3 if (is_string($idadeTexto) && ctype_digit($idadeTexto)) {
4     $idade = (int) $idadeTexto;
5 } else {
6     echo 'A idade non é un número enteiro válido';
7 }
  • Tamén podemos empregar filtros de validación, como veremos máis adiante.

Comprobando se chegan datos de varios formularios diferentes á mesma páxina

  • Se varios formularios envían datos á mesma páxina é preferible identificar explicitamente a orixe cun campo oculto, en lugar de depender do botón de envío. O botón pode non aparecer na petición nalgúns modos de envío, por exemplo ao premer Intro nun campo.

Formulario da primeira páxina:

<form method="post" action="terceira.php">
    <input type="hidden" name="formulario" value="primeira">
    ...
    <button type="submit">Enviar</button>
</form>

Formulario da segunda páxina:

<form method="post" action="terceira.php">
    <input type="hidden" name="formulario" value="segunda">
    ...
    <button type="submit">Enviar</button>
</form>

Código de terceira.php:

$orixe = $_POST['formulario'] ?? null;

if ($orixe === 'primeira') {
    // Datos enviados dende primeira.php
} elseif ($orixe === 'segunda') {
    // Datos enviados dende o formulario de segunda.php
} else {
    // A páxina cargouse directamente ou chegou unha orixe non válida
}
  • Importante: un campo oculto tamén pode ser modificado polo usuario. Só serve para identificar a intención do formulario; o seu valor debe comprobarse no servidor.

Validación, normalización e escapado dos campos

  • Ao traballar con datos externos convén diferenciar tres operacións:
  • Validación: comprobar se un dato cumpre as regras esperadas. Por exemplo, comprobar se unha idade é un enteiro entre 0 e 130.
  • Normalización ou saneamento: transformar un dato cando realmente o necesitemos, por exemplo aplicar trim() para eliminar espazos ao principio e ao final.
  • Escapado da saída: adaptar un dato ao contexto no que se vai amosar. Para escribir texto dentro de HTML empregaremos normalmente htmlspecialchars().
  • Non debemos asumir que un dato queda seguro para todos os contextos por aplicarlle un filtro xenérico. A protección depende do uso:
  • HTML → htmlspecialchars().
  • SQL → consultas preparadas con parámetros.
  • URL → rawurlencode() ou http_build_query().
  • Números, correos, IP, etc. → validación específica.

No cliente

  • HTML permite realizar validacións básicas mediante atributos como:
  • required: indica que o campo é obrigatorio.
  • maxlength, minlength, min e max: limitan lonxitudes ou rangos.
  • type="email", type="number", type="date", etc.
  • accept nun campo de tipo file: axuda a indicar ao navegador os tipos de ficheiro que se queren seleccionar.
  • Estas comprobacións poden ser evitadas polo usuario. Por iso deben repetirse no servidor as regras que afecten á integridade ou á seguridade da aplicación.

No servidor: filter_var()

  • PHP dispón da función filter_var(), que permite validar ou sanear unha variable segundo o filtro indicado.
  • Sintaxe simplificada:
filter_var($valor, $filtro, $opcion);
  • Parámetros:
  • $valor: dato que queremos comprobar.
  • $filtro: constante FILTER_VALIDATE_* ou FILTER_SANITIZE_*.
  • $opcion: opcións ou flags adicionais do filtro.

Filtros para a validación

  • FILTER_VALIDATE_BOOLEAN: valida un valor booleano.
  • FILTER_VALIDATE_EMAIL: valida unha dirección de correo electrónico.
  • FILTER_VALIDATE_FLOAT: valida un número de punto flotante.
  • FILTER_VALIDATE_INT: valida un número enteiro.
  • FILTER_VALIDATE_IP: valida unha dirección IP.
  • FILTER_VALIDATE_REGEXP: valida mediante unha expresión regular.
  • FILTER_VALIDATE_URL: valida unha URL.

Filtros de saneamento

  • Algúns filtros de saneamento que continúan dispoñibles son:
  • FILTER_SANITIZE_EMAIL
  • FILTER_SANITIZE_ENCODED
  • FILTER_SANITIZE_NUMBER_FLOAT
  • FILTER_SANITIZE_NUMBER_INT
  • FILTER_SANITIZE_SPECIAL_CHARS
  • FILTER_SANITIZE_FULL_SPECIAL_CHARS
  • FILTER_SANITIZE_URL
  • Importante en PHP 8:
  • FILTER_SANITIZE_MAGIC_QUOTES foi eliminado en PHP 8.0 e non debe empregarse.
  • FILTER_SANITIZE_STRING e FILTER_SANITIZE_STRIPPED están obsoletos dende PHP 8.1. Para escapar texto que vai ser mostrado en HTML empregaremos directamente htmlspecialchars().
  • Un filtro de saneamento non substitúe unha validación. Por exemplo, se esperamos un enteiro é preferible rexeitar 12abc como inválido en lugar de transformalo silenciosamente en 12.

Exemplos

Validando

  • Validando un número enteiro:
1 $valor = '123';
2 
3 $enteiro = filter_var($valor, FILTER_VALIDATE_INT);
4 
5 if ($enteiro === false) {
6     echo 'Valor incorrecto';
7 } else {
8     echo "Valor correcto: $enteiro";
9 }
  • Validando un número flotante:
1 $valor = '-1.45';
2 
3 $decimal = filter_var($valor, FILTER_VALIDATE_FLOAT);
4 
5 if ($decimal === false) {
6     echo 'Valor incorrecto';
7 } else {
8     echo "Valor correcto: $decimal";
9 }
  • Validando un correo electrónico:
1 $correo = 'usuario@example.com';
2 
3 if (filter_var($correo, FILTER_VALIDATE_EMAIL) === false) {
4     echo 'Correo incorrecto';
5 } else {
6     echo 'Correo correcto';
7 }

Escapando texto para HTML

  • Se queremos mostrar nunha páxina un texto recibido do usuario, escapámolo no momento da saída:
1 $texto = '<script>alert("ola")</script>';
2 
3 echo htmlspecialchars(
4     $texto,
5     ENT_QUOTES | ENT_SUBSTITUTE,
6     'UTF-8'
7 );
O navegador mostrará o texto recibido, pero non interpretará as etiquetas como código HTML.
  • Importante: strip_tags() e htmlspecialchars() non fan o mesmo. strip_tags() elimina etiquetas; htmlspecialchars() codifica os caracteres especiais para que se mostren como texto.

Saneando un número flotante

1 $numero = '53.3pp';
2 
3 $resultado = filter_var(
4     $numero,
5     FILTER_SANITIZE_NUMBER_FLOAT,
6     FILTER_FLAG_ALLOW_FRACTION
7 );
8 
9 var_dump($resultado);
Neste caso o resultado será a cadea 53.3. Esta operación transforma o dato; non é equivalente a comprobar que o dato orixinal fose un decimal válido.

Establecendo criterios

  • Podemos validar un enteiro dentro dun rango:
 1 $valor = 25;
 2 
 3 $opcion = [
 4     'options' => [
 5         'min_range' => 10,
 6         'max_range' => 100,
 7     ],
 8 ];
 9 
10 $resultado = filter_var($valor, FILTER_VALIDATE_INT, $opcion);
11 
12 if ($resultado === false) {
13     echo 'Valor incorrecto';
14 } else {
15     echo "Valor correcto: $resultado";
16 }
  • Tamén podemos indicar un valor por defecto:
 1 $valor = -200;
 2 
 3 $opcion = [
 4     'options' => [
 5         'default' => 3,
 6         'min_range' => 10,
 7         'max_range' => 100,
 8     ],
 9 ];
10 
11 $resultado = filter_var($valor, FILTER_VALIDATE_INT, $opcion);
12 echo $resultado;
Neste caso obteremos 3 se o valor non supera a validación.
  • Para un número decimal podemos indicar outro separador decimal mediante a opción decimal:
 1 $valor = '1,23';
 2 
 3 $opcion = [
 4     'options' => [
 5         'decimal' => ',',
 6     ],
 7 ];
 8 
 9 $resultado = filter_var($valor, FILTER_VALIDATE_FLOAT, $opcion);
10 
11 if ($resultado === false) {
12     echo 'Valor incorrecto';
13 } else {
14     echo "Valor correcto: $resultado";
15 }


Seguridade no uso dos formularios

  • Os datos dun formulario están controlados polo cliente e poden ser modificados. A aplicación debe validar a entrada e aplicar a protección apropiada no contexto no que vai empregar cada dato.

Inxección SQL

  • Unha inxección SQL pode producirse cando unha aplicación constrúe unha consulta concatenando directamente datos controlados polo usuario.
Exemplo incorrecto:
1 $nome = $_POST['usuario'] ?? '';
2 $password = $_POST['password'] ?? '';
3 
4 $sql = "SELECT count(*) FROM usuarios
5         WHERE nome='$nome' AND password='$password'";
  • O problema non se resolve eliminando comiñas mediante filtros HTML nin empregando htmlspecialchars(). HTML e SQL son contextos diferentes.

Solución á inxección SQL

  • A solución habitual é empregar consultas preparadas con parámetros. Os datos e o código SQL envíanse ao xestor de base de datos de forma separada.
Exemplo con MySQLi:
 1 $stmt = $pdo->prepare(
 2     'SELECT id, nome, password_hash
 3      FROM usuarios
 4      WHERE nome = :nome'
 5 );
 6 
 7 $stmt->execute([
 8     'nome' => $nome
 9 ]);
10 
11 $usuario = $stmt->fetch(PDO::FETCH_ASSOC);
  • Cando traballemos con contrasinais reais non debemos gardalos nin comparalos en texto claro. Empregaremos password_hash() para gardar o hash e password_verify() para comprobar o contrasinal.
  • mysqli_real_escape_string() pode ser útil nalgúns contextos específicos, pero non é a técnica principal que debemos ensinar para construír consultas con datos externos. A opción preferente son as consultas preparadas.
  • Un procedemento almacenado tampouco evita por si só unha inxección SQL: dependerá de como estea implementado e de se constrúe SQL dinámico internamente.

Cross-Site Scripting (XSS)

  • Un ataque XSS pode producirse cando datos controlados por un usuario se incorporan a unha páxina e o navegador os interpreta como HTML ou JavaScript.
Por exemplo, imaxinemos que recibimos:
1 <script>alert('Exemplo XSS')</script>
Se facemos directamente:
echo $_GET['texto'];
o navegador podería interpretar o contido como marcado ou código.

Solucións ao ataque Cross-Site Scripting (XSS)

  • Para mostrar texto dentro de HTML empregaremos normalmente:
$texto = $_GET['texto'] ?? '';

echo htmlspecialchars(
    $texto,
    ENT_QUOTES | ENT_SUBSTITUTE,
    'UTF-8'
);
  • htmlspecialchars() non elimina o texto: codifica os caracteres con significado especial en HTML para que se mostren como texto.
Os parámetros empregados teñen o seguinte significado:
  • $texto: cadea que queremos converter para poder amosala de forma segura dentro de HTML.
  • ENT_QUOTES: indica que, ademais dos caracteres especiais de HTML, se deben converter tanto as comiñas dobres (") como as comiñas simples (').
  • ENT_SUBSTITUTE: emprégase para que en caso de enviar caracteres non válidos UTF-8 os sustitúa polo carácter �.
Isto é especialmente importante cando o dato vai ser empregado dentro dun atributo HTML.
Visualmente non notaredes nada no navegador, pero se mirades o código fonte de páxina podedes comprobar como cambia as comillas e outros caracteres por códigos.


  • O escapado debe realizarse no momento da saída e segundo o contexto. Un valor preparado para HTML non queda automaticamente preparado para SQL, JavaScript, CSS ou unha URL.
  • strip_tags() pode eliminar etiquetas HTML, pero non debe considerarse unha alternativa xeral ao escapado contextual.
  • Se queremos utilizar $_SERVER['PHP_SELF'] nun atributo HTML, debemos escapar o valor:
<form method="post"
      action="<?php echo htmlspecialchars($_SERVER['PHP_SELF'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?>">
</form>
Para un formulario que se procesa na mesma páxina tamén podemos empregar simplemente:
<form method="post" action="">
</form>

Ataques CSRF (Cross-Site Request Forgery)

Normas xerais

  • Como normas xerais:
  • Validaremos no servidor todos os datos procedentes do cliente.
  • Nos formularios que modifican o estado da aplicación empregaremos protección contra CSRF.
  • Para mostrar texto dentro de HTML empregaremos htmlspecialchars(..., ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8').
  • Para SQL empregaremos consultas preparadas con parámetros.
  • Para contrasinais empregaremos password_hash() e password_verify().
  • Non empregaremos filtros HTML para intentar evitar inxección SQL.
  • Sanearemos ou normalizaremos datos só cando as regras da aplicación o requiran; non substituiremos a validación por unha transformación silenciosa.

Obtendo variables de formularios (POST/GET) e filtrando ao mesmo tempo

  • PHP dispón da función filter_input(), que permite obter unha variable externa e aplicarlle opcionalmente un filtro.
  • Sintaxe actual:
filter_input(
    int $type,
    string $var_name,
    int $filter = FILTER_DEFAULT,
    array|int $options = 0
): mixed
  • Parámetros:
  • type: INPUT_GET, INPUT_POST, INPUT_COOKIE, INPUT_SERVER ou INPUT_ENV.
  • var_name: nome da variable.
  • filter: filtro a aplicar.
  • options: opcións ou flags.
  • Importante: FILTER_DEFAULT é un alias de FILTER_UNSAFE_RAW; é dicir, por defecto non se aplica unha limpeza automática.
  • filter_input() traballa sobre o valor orixinal proporcionado pola SAPI. Se modificamos previamente $_GET ou $_POST dentro do programa e queremos filtrar ese novo valor, empregaremos filter_var().
  • Exemplo validando un identificador enteiro recibido por GET:
 1 $id = filter_input(
 2     INPUT_GET,
 3     'id',
 4     FILTER_VALIDATE_INT,
 5     [
 6         'options' => [
 7             'min_range' => 1,
 8         ],
 9     ]
10 );
11 
12 if ($id === null) {
13     echo 'Non se enviou o parámetro id.';
14 } elseif ($id === false) {
15     echo 'O identificador non é válido.';
16 } else {
17     echo 'Identificador válido: ' . $id;
18 }
  • Exemplo para obter texto e mostralo en HTML:
 1 $buscar = filter_input(INPUT_GET, 'buscar', FILTER_UNSAFE_RAW);   // Por defecto todos os datos que veñen en post e get son cadeas. Non ten sentido filtrar por 'cadeas'
 2 
 3 if (!is_string($buscar)) {   // Con isto aseguramos que sexa unha cadea e non un array, por exemplo....
 4     $buscar = '';
 5 }
 6 
 7 $buscarHtml = htmlspecialchars(
 8     $buscar,
 9     ENT_QUOTES | ENT_SUBSTITUTE,
10     'UTF-8'
11 );
12 
13 echo "Buscaches: $buscarHtml";

Redireccionamento de páxinas

  • Despois de procesar un formulario pode ser necesario redireccionar ao usuario a outra páxina.
1 header('Location: /nova_paxina.php');
2 exit;
Importante:
  • A cabeceira debe enviarse antes de producir saída cara ao cliente. Non debe haber HTML, echo, printf nin espazos enviados antes da chamada a header().
  • Despois dun redireccionamento é recomendable executar exit; para impedir que continúe o resto do script.
  • Se queremos enviar parámetros GET nunha redirección temos dúas opcións:
  • Construír a cadea mediante http_build_query():
1 $parametros = http_build_query([
2     'parametro1' => 'valor 1',
3     'parametro2' => 'valor 2',
4 ]);
5 
6 header('Location: /nova_paxina.php?' . $parametros);
7 exit;
Esta é a opción recomendable se temos varios parámetros a enviar. Automaticamente xa fai o url_encode.


  • Empregar urlencode:
1  header(
2             'Location: /nova_paxina.php?parametro=' .
3                 urlencode('Valor a enviar')
4         );
5 exit;


IMPORTANTE: Lembrar poñer exit xa que o script continúa a execución se non está posto.


  • Na páxina de destino poderemos obter os datos mediante $_GET e validalos.
  • Para inspeccionar as cabeceiras HTTP xa non é necesario instalar complementos específicos. Os navegadores actuais dispoñen das ferramentas de desenvolvemento: normalmente podemos abrir a lapela Rede/Network, seleccionar unha petición e revisar as súas cabeceiras.




Gardando imaxes no servidor

  • NOTA: Este punto non vai entrar no exame, pero é convinte que o coñezades para poder subir un arquivo ao servidor web...
Polo tanto facede o exemplo que vai a continuación.


  • Os formularios permiten subir arquivos ao servidor. Un caso típico é asociar unha imaxe a un produto.
  • A subida de arquivos require especial atención á seguridade:
  • Non debemos confiar no nome orixinal enviado polo cliente.
  • Non debemos confiar en $_FILES['...']['type'], xa que esa información é proporcionada polo cliente e PHP non a verifica.
  • Debemos comprobar o código de erro, o tamaño e o tipo real do arquivo.
  • É recomendable xerar un nome novo no servidor.
  • Para arquivos que non necesiten ser servidos directamente polo servidor web é preferible almacenalos fóra do DocumentRoot.
  • Nunca debemos permitir a execución de código nun cartafol de subidas.

Cartafol de destino

  • Nun exemplo sinxelo no que Apache serve directamente as imaxes e o DocumentRoot é /var/www/html, poderiamos crear un cartafol específico:
1 cd /var/www/html
2 mkdir IMAXES
3 chown www-data:www-data IMAXES
4 chmod 750 IMAXES
  • O usuario e grupo reais dependen da instalación. En Debian/Ubuntu é habitual www-data; noutras instalacións pode ser diferente.
  • Evitaremos permisos 777. O proceso que executa PHP só necesita os permisos necesarios para escribir no directorio.

Páxina do formulario

  • Para subir arquivos é obrigatorio utilizar enctype="multipart/form-data".
 1 <!DOCTYPE html>
 2 <html lang="gl">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>Subida de imaxes</title>
 6 </head>
 7 <body>
 8 
 9 <form action="GardaCampos.php" method="post" enctype="multipart/form-data">
10     <label for="fileToUpload">Selecciona unha imaxe:</label>
11     <input
12         type="file"
13         name="fileToUpload"
14         id="fileToUpload"
15         accept=".jpg,.jpeg,.png,.gif"
16         required
17     >
18 
19     <button type="submit">Subir imaxe</button>
20 </form>
21 
22 </body>
23 </html>
  • O atributo accept axuda ao usuario a seleccionar determinados ficheiros, pero non é unha medida de seguridade. O servidor debe comprobar igualmente o arquivo recibido.

Páxina que procesa os datos

  • O seguinte exemplo:
  • comproba o método da petición;
  • comproba o código de erro;
  • limita o tamaño;
  • verifica que o arquivo procede dunha subida HTTP;
  • detecta o MIME real mediante finfo;
  • só permite determinados tipos de imaxe;
  • xera un nome aleatorio;
  • move o arquivo ao directorio definitivo.
 1 <?php
 2 
 3 if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
 4     exit('Método non permitido.');
 5 }
 6 
 7 if (!isset($_FILES['fileToUpload'])) {
 8     exit('Non se recibiu ningún arquivo.');
 9 }
10 
11 $arquivo = $_FILES['fileToUpload'];
12 
13 if ($arquivo['error'] !== UPLOAD_ERR_OK) {
14     exit('Erro na subida. Código: ' . $arquivo['error']);
15 }
16 
17 // Límite propio da aplicación: 2 MiB.
18 $tamanoMaximo = 2 * 1024 * 1024;
19 
20 if ($arquivo['size'] > $tamanoMaximo) {
21     exit('O arquivo supera o tamaño máximo permitido.');
22 }
23 
24 if (!is_uploaded_file($arquivo['tmp_name'])) {
25     exit('O arquivo non procede dunha subida HTTP válida.');
26 }
27 
28 // Detectamos o MIME a partir do contido real do arquivo.
29 $finfo = new finfo(FILEINFO_MIME_TYPE);
30 $mime = $finfo->file($arquivo['tmp_name']);
31 
32 $tiposPermitidos = [
33     'image/jpeg' => 'jpg',
34     'image/png'  => 'png',
35     'image/gif'  => 'gif',
36 ];
37 
38 if ($mime === false || !isset($tiposPermitidos[$mime])) {
39     exit('Tipo de arquivo non permitido.');
40 }
41 
42 $extension = $tiposPermitidos[$mime];
43 
44 // Non empregamos o nome enviado polo cliente como nome definitivo.
45 $nomeSeguro = bin2hex(random_bytes(16)) . '.' . $extension;
46 
47 $directorioDestino = __DIR__ . '/IMAXES';
48 
49 if (!is_dir($directorioDestino) || !is_writable($directorioDestino)) {
50     exit('O directorio de destino non existe ou non permite escritura.');
51 }
52 
53 $destino = $directorioDestino . DIRECTORY_SEPARATOR . $nomeSeguro;
54 
55 if (!move_uploaded_file($arquivo['tmp_name'], $destino)) {
56     exit('Non foi posible gardar o arquivo.');
57 }
58 
59 echo 'Imaxe gardada correctamente co nome: '
60     . htmlspecialchars($nomeSeguro, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
  • Neste exemplo empregamos a clase finfo coa constante FILEINFO_MIME_TYPE. Non é necesario chamar a finfo_close(); en PHP 8.5 esa función está obsoleta porque o obxecto se libera automaticamente.

Códigos de erro de subida

  • O campo $_FILES['fileToUpload']['error'] pode ter, entre outros, os seguintes valores:
  • UPLOAD_ERR_OK (0): subida correcta.
  • UPLOAD_ERR_INI_SIZE (1): o arquivo supera upload_max_filesize.
  • UPLOAD_ERR_FORM_SIZE (2): supera MAX_FILE_SIZE, se o formulario enviou ese campo.
  • UPLOAD_ERR_PARTIAL (3): subida parcial.
  • UPLOAD_ERR_NO_FILE (4): non se seleccionou ningún arquivo.
  • UPLOAD_ERR_NO_TMP_DIR (6): non existe o directorio temporal.
  • UPLOAD_ERR_CANT_WRITE (7): PHP non puido escribir no disco.
  • UPLOAD_ERR_EXTENSION (8): unha extensión de PHP detivo a subida.

Funcións útiles

  • basename(): devolve a última parte dunha ruta. Pode ser útil para mostrar o nome orixinal, pero non debemos confiar nel como única medida de seguridade nin utilizalo necesariamente como nome final.
  • pathinfo(): permite obter partes dun nome de ficheiro. A extensión enviada polo usuario non demostra o tipo real do arquivo.
  • getimagesize(): pode obter información dunha imaxe e pode empregarse como comprobación adicional cando a aplicación espera imaxes.
  • is_uploaded_file(): comproba que o ficheiro temporal procede dunha subida HTTP.
  • move_uploaded_file(): move de forma segura un ficheiro subido ao seu destino.

Limitar o tamaño

  • Ademais dos límites de PHP, a aplicación debería establecer o seu propio límite:
$tamanoMaximo = 2 * 1024 * 1024;

if ($_FILES['fileToUpload']['size'] > $tamanoMaximo) {
    exit('O arquivo é demasiado grande.');
}
  • HTML permite enviar un campo oculto MAX_FILE_SIZE:
<form action="GardaCampos.php" method="post" enctype="multipart/form-data">
    <input type="hidden" name="MAX_FILE_SIZE" value="2097152">
    <input type="file" name="fileToUpload">
    <button type="submit">Subir</button>
</form>
  • Importante: este campo está controlado polo cliente e pode ser modificado. Non substitúe os límites configurados en PHP nin a comprobación do tamaño no servidor.

Configuración en php.ini

  • As directivas principais son:
upload_max_filesize = 2M
post_max_size = 8M
  • Estes son os valores por defecto de PHP 8.5, pero un servidor real pode ter outros valores.
  • post_max_size debe ser maior que upload_max_filesize se queremos permitir a subida dun ficheiro dese tamaño.
  • Para saber que arquivo de configuración está cargando PHP en liña de comandos podemos executar:
php --ini
  • Nunha instalación Debian/Ubuntu poden existir arquivos diferentes segundo a SAPI empregada, por exemplo:
/etc/php/8.5/apache2/php.ini
/etc/php/8.5/fpm/php.ini
/etc/php/8.5/cli/php.ini
  • Nota: a ruta exacta depende da instalación e da versión instalada.
  • Se PHP funciona como módulo de Apache e o servidor o permite, algunhas directivas poden configurarse mediante .htaccess:
php_value upload_max_filesize 64M
php_value post_max_size 70M
php_value max_execution_time 300
php_value max_input_time 300
  • Se se utiliza CGI/FastCGI ou PHP-FPM, php_value en .htaccess pode non funcionar. Dependendo do servidor poden empregarse .user.ini, a configuración do pool de PHP-FPM ou a configuración proporcionada polo servizo de aloxamento.

Verificando os datos dun formulario

  • Podemos comprobar varios campos obrigatorios percorrendo un array cos seus nomes.
 1 $camposObrigatorios = ['txtNome', 'txtEmail'];
 2 
 3 $erros = [];
 4 
 5 foreach ($camposObrigatorios as $campo) {
 6     if (!isset($_POST[$campo]) || !is_string($_POST[$campo]) || trim($_POST[$campo]) === '') {
 7         $erros[] = "O campo $campo é obrigatorio.";
 8     }
 9 }
10 
11 if ($erros !== []) {
12     foreach ($erros as $erro) {
13         echo htmlspecialchars($erro, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '<br>';
14     }
15 }
  • Empregamos trim() porque unha cadea formada só por espazos tampouco debería considerarse un valor válido para estes campos.
  • Nota: Non sempre debemos empregar empty() para validar campos obrigatorios, xa que a cadea "0" considérase baleira por empty() e nalgúns formularios pode ser un valor válido.


Creando arrays cos datos dun formulario

  • Nos formularios xerados dinamicamente pode haber un número variable de campos. HTML permite empregar corchetes no atributo name para que PHP reciba os valores nun array.
  • Imaxinemos que queremos introducir o estado de varios exemplares dun libro e que o número de exemplares non se coñece de antemán.
En lugar de crear nomes diferentes:
<input type="text" name="txtEstado1">
<input type="text" name="txtEstado2">
<input type="text" name="txtEstado3">
podemos empregar:
<?php for ($cont = 0; $cont < $numExemplares; $cont++): ?>
    <input type="text" name="txtEstado[]">
<?php endfor; ?>
  • Ao recibir o formulario, $_POST['txtEstado'] será un array.
  • Debemos comprobar que realmente recibimos un array antes de percorrelo:
$estados = $_POST['txtEstado'] ?? [];

if (!is_array($estados)) {
    $estados = [];
}

foreach ($estados as $estado) {
    if (!is_string($estado)) {
        continue;
    }

    $estado = trim($estado);

    // Validar $estado segundo as regras da aplicación.
}


Exercicios propostos

  • Exercicio 1: Crea unha páxina cun formulario e dous campos, nome e idade. O método de envío será POST e o destino será a mesma páxina, sen escribir manualmente o seu nome na URL. O nome será obrigatorio e terá unha lonxitude máxima de 20 caracteres. A idade deberá ser un número enteiro de dous díxitos. Todos os datos recibidos deberán validarse no servidor. Ao amosar datos introducidos polo usuario na páxina HTML deberán escaparse correctamente para evitar ataques XSS. O formulario terá un botón "Enviar datos". Se algún dato non é válido, amosarase unha mensaxe de erro e volverase amosar o formulario conservando os valores correctos introducidos. Se todos os datos son válidos, amosaranse nunha lista e non se volverá amosar o formulario. Debes diferenciar entre validación da entrada e escapado da saída HTML.
  • Exercicio 2: Fai o necesario para xestionar a alta, baixa e baleirado dunha lista de froitas. O nome dunha froita debe validarse no servidor e todos os valores deben escaparse ao seren mostrados en HTML. Se o campo está baleiro ou non cumpre o formato permitido, amosarase un erro. A lista amosarase nunha táboa e ao carón de cada froita haberá un botón para eliminala. Se non hai elementos amosarase "Cesta baleira". Nun arquivo separado /utilidades/operacionsFroitas.php gardaranse as funcións necesarias. O arquivo é imprescindible e só debe cargarse unha vez, polo que se empregará require_once. Para manter a lista entre peticións neste exercicio pódense empregar campos ocultos, lembrando que os seus valores poden ser modificados polo cliente e deben validarse de novo. Máis adiante substituiremos este mecanismo por sesións.
  • Exercicio 3: Crea nun arquivo separado /utilidades/usuarios.php un array de usuarios. Cada usuario terá: id, nome, teléfono e correo electrónico. Nese mesmo arquivo define unha función obterUsuarioPorID() que reciba un enteiro e devolva o usuario correspondente ou null se non existe. Crea unha páxina listaUsuarios.php que amose os nomes como enlaces a detalleUsuario.php. O identificador enviarase mediante GET. Na páxina de detalle debes validar que o ID recibido é un enteiro positivo e escapar os datos antes de amosalos. Proba a modificar manualmente o parámetro id da URL.


Solución Exercicios propostos

Solución Exercicio 1

<?php

$nome = '';
$idadeTexto = '';
$idade = null;
$erros = [];
$formularioEnviado = $_SERVER['REQUEST_METHOD'] === 'POST';

if ($formularioEnviado) {
    $nomeRecibido = $_POST['nome'] ?? '';
    $idadeRecibida = $_POST['idade'] ?? '';

    // Comprobamos que os datos recibidos sexan cadeas.
    // Un usuario podería manipular a petición e enviar un array.
    if (!is_string($nomeRecibido)) {
        $nomeRecibido = '';
    }

    if (!is_string($idadeRecibida)) {
        $idadeRecibida = '';
    }

    $nome = trim($nomeRecibido);
    $idadeTexto = trim($idadeRecibida);

    /*
     * Validación do nome.
     *
     * PHP non dispón dun FILTER_VALIDATE específico para nomes de persoa.
     * Neste exemplo comprobamos unicamente as regras da nosa aplicación:
     * que exista e que non supere os 20 caracteres.
     */
    if ($nome === '') {
        $erros[] = 'O campo nome é obrigatorio.';
    } elseif (mb_strlen($nome) > 20) {
        $erros[] = 'O campo nome non pode ter máis de 20 caracteres.';
    }

    /*
     * Validación da idade.
     *
     * Empregamos FILTER_VALIDATE_INT para comprobar que realmente
     * representa un número enteiro.
     *
     * Ademais, limitamos o valor entre 10 e 99 porque o exercicio
     * indica que a idade debe ter dous díxitos.
     */
    $idadeValidada = filter_var(
        $idadeTexto,
        FILTER_VALIDATE_INT,
        [
            'options' => [
                'min_range' => 10,
                'max_range' => 99
            ]
        ]
    );

    if ($idadeTexto === '') {
        $erros[] = 'O campo idade é obrigatorio.';
    } elseif ($idadeValidada === false) {
        $erros[] = 'A idade debe ser un número enteiro de dous díxitos.';
    } else {
        $idade = $idadeValidada;
    }
}

function escaparHtml(string $valor): string
{
    return htmlspecialchars(
        $valor,
        ENT_QUOTES | ENT_SUBSTITUTE,
        'UTF-8'
    );
}

$datosValidos = $formularioEnviado && $erros === [];
?>
<!DOCTYPE html>
<html lang="gl">
<head>
    <meta charset="UTF-8">
    <title>Formulario</title>
</head>
<body>

<?php if (!$datosValidos): ?>

    <h1>Formulario</h1>

    <?php if ($formularioEnviado && $erros !== []): ?>
        <h2>Erros</h2>
        <ul>
            <?php foreach ($erros as $erro): ?>
                <li><?php echo escaparHtml($erro); ?></li>
            <?php endforeach; ?>
        </ul>
    <?php endif; ?>

    <form method="post" action="">
        <div>
            <label for="nome">Nome:</label>
            <input
                type="text"
                id="nome"
                name="nome"
                maxlength="20"
                required
                value="<?php echo escaparHtml($nome); ?>"
            >
        </div>

        <div>
            <label for="idade">Idade:</label>
            <input
                type="text"
                id="idade"
                name="idade"
                inputmode="numeric"
                maxlength="2"
                size="3"
                required
                value="<?php echo escaparHtml($idadeTexto); ?>"
            >
        </div>

        <button type="submit">Enviar datos</button>
    </form>

<?php else: ?>

    <h1>Datos enviados</h1>
    <ul>
        <li>Nome: <?php echo escaparHtml($nome); ?></li>
        <li>Idade: <?php echo $idade; ?></li>
    </ul>

<?php endif; ?>

</body>
</html>
  • Fixarse en que o nome non se transforma con filtros HTML antes de validalo. Gardamos o valor lóxico e aplicamos htmlspecialchars() unicamente cando o inserimos na saída HTML.


Solución Exercicio 2

Arquivo /utilidades/operacionsFroitas.php
<?php

/**
 * Valida e normaliza o nome dunha froita.
 *
 * @param string $nome Nome recibido.
 * @return string|false Nome normalizado ou false se non é válido.
 */
function verificarNomeFroita(string $nome): string|false
{
    $nome = trim($nome);

    if ($nome === '') {
        return false;
    }

    if (mb_strlen($nome) > 40) {
        return false;
    }

    // Permitimos letras Unicode, espazos, apóstrofos e guións.
    if (!preg_match("/^[\p{L}\p{M} '-]+$/u", $nome)) {  // => Non tedes por que saber expresións regulares que se ven noutro módulo.
        return false;
    }

    return $nome;
}

/**
 * Engade unha froita se aínda non existe.
 *
 * @param array $lista Lista actual.
 * @param string $froita Froita a engadir.
 * @return array Nova lista.
 */
function engadirFroita(array $lista, string $froita): array
{
    if (!in_array($froita, $lista, true)) {
        $lista[] = $froita;
    }

    return $lista;
}

/**
 * Elimina unha froita da lista.
 *
 * @param array $lista Lista actual.
 * @param string $froita Froita a eliminar.
 * @return array Nova lista.
 */
function eliminarFroita(array $lista, string $froita): array
{
    $indice = array_search($froita, $lista, true);

    if ($indice !== false) {
        unset($lista[$indice]);
        $lista = array_values($lista);
    }

    return $lista;
}
Arquivo xestionFroitas.php
<?php

require_once __DIR__ . '/utilidades/operacionsFroitas.php';

function escaparHtml(string $valor): string
{
    return htmlspecialchars(
        $valor,
        ENT_QUOTES | ENT_SUBSTITUTE,
        'UTF-8'
    );
}

$listaFroitas = [];
$erro = null;

// Recuperamos a lista dos campos ocultos.
// Estes valores veñen do cliente, polo que os validamos novamente.
$listaRecibida = $_POST['listaFroitas'] ?? [];

if (is_array($listaRecibida)) {
    foreach ($listaRecibida as $froitaRecibida) {
        if (!is_string($froitaRecibida)) {
            continue;
        }

        $froitaValida = verificarNomeFroita($froitaRecibida);

        if ($froitaValida !== false) {
            $listaFroitas = engadirFroita($listaFroitas, $froitaValida);
        }
    }
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (isset($_POST['btnBaleirar'])) {
        $listaFroitas = [];
    }

    if (isset($_POST['btnEngadir'])) {
        $novaFroita = $_POST['novaFroita'] ?? '';

        if (!is_string($novaFroita)) {
            $novaFroita = '';
        }

        $novaFroita = verificarNomeFroita($novaFroita);

        if ($novaFroita === false) {
            $erro = 'O nome da froita non é válido.';
        } else {
            $listaFroitas = engadirFroita($listaFroitas, $novaFroita);
        }
    }

    if (isset($_POST['btnBorrar']) && is_string($_POST['btnBorrar'])) {
        $froitaAEliminar = verificarNomeFroita($_POST['btnBorrar']);

        if ($froitaAEliminar !== false) {
            $listaFroitas = eliminarFroita($listaFroitas, $froitaAEliminar);
        }
    }
}
?>
<!DOCTYPE html>
<html lang="gl">
<head>
    <meta charset="UTF-8">
    <title>Xestión de froitas</title>
</head>
<body>

<h1>Xestión de froitas</h1>

<?php if ($erro !== null): ?>
    <p><?php echo escaparHtml($erro); ?></p>
<?php endif; ?>

<form method="post" action="">

    <?php foreach ($listaFroitas as $clave => $froita): ?>
        <input
            type="hidden"
            name="listaFroitas[<?php echo $clave; ?>]"
            value="<?php echo escaparHtml($froita); ?>"
        >
    <?php endforeach; ?>

    <?php if ($listaFroitas === []): ?>
        <p>Cesta baleira.</p>
    <?php else: ?>
        <table>
            <thead>
            <tr>
                <th>Froita</th>
                <th>Acción</th>
            </tr>
            </thead>
            <tbody>

            <?php foreach ($listaFroitas as $froita): ?>
                <tr>
                    <td><?php echo escaparHtml($froita); ?></td>
                    <td>
                        <button
                            type="submit"
                            name="btnBorrar"
                            value="<?php echo escaparHtml($froita); ?>"
                        >
                            Eliminar
                        </button>
                    </td>
                </tr>
            <?php endforeach; ?>

            </tbody>
        </table>
    <?php endif; ?>

    <div>
        <label for="novaFroita">Nome da froita:</label>
        <input type="text" id="novaFroita" name="novaFroita" maxlength="40">
        <button type="submit" name="btnEngadir" value="1">Engadir froita</button>
    </div>

    <?php if ($listaFroitas !== []): ?>
        <button type="submit" name="btnBaleirar" value="1">Baleirar cesta</button>
    <?php endif; ?>

</form>

</body>
</html>
  • Nota: Este exercicio conserva o estado mediante campos ocultos porque as sesións aínda non se estudaron. Os campos ocultos poden ser manipulados polo cliente; por iso a solución volve validar todas as froitas recibidas. Nunha aplicación real, para manter estado interno empregaremos normalmente sesións ou persistencia no servidor.


Solución Exercicio 3

Arquivo utilidades/usuarios.php
<?php

$usuarios = [
    [
        'id' => 1,
        'nome' => 'Usuario 1',
        'tfno' => '123456789',
        'email' => 'usuario1@example.com',
    ],
    [
        'id' => 2,
        'nome' => 'Usuario 2',
        'tfno' => '987654321',
        'email' => 'usuario2@example.com',
    ],
    [
        'id' => 3,
        'nome' => 'Usuario 3',
        'tfno' => '555555555',
        'email' => 'usuario3@example.com',
    ],
];

/**
 * Obtén os datos dun usuario a partir do seu ID.
 */
function obterUsuarioPorID(int $id): ?array
{
    global $usuarios;

    foreach ($usuarios as $usuario) {
        if ($usuario['id'] === $id) {
            return $usuario;
        }
    }

    return null;
}
Arquivo listaUsuarios.php
<?php

require_once __DIR__ . '/utilidades/usuarios.php';

function escaparHtml(string $valor): string
{
    return htmlspecialchars(
        $valor,
        ENT_QUOTES | ENT_SUBSTITUTE,
        'UTF-8'
    );
}
?>
<!DOCTYPE html>
<html lang="gl">
<head>
    <meta charset="UTF-8">
    <title>Listado de usuarios</title>
</head>
<body>

<h1>Listado de usuarios</h1>

<ul>
<?php foreach ($usuarios as $usuario): ?>
    <?php
    $url = 'detalleUsuario.php?' . http_build_query([
        'id' => $usuario['id'],
    ]);
    ?>
    <li>
        <a href="<?php echo escaparHtml($url); ?>">
            <?php echo escaparHtml($usuario['nome']); ?>
        </a>
    </li>
<?php endforeach; ?>
</ul>

</body>
</html>
Arquivo detalleUsuario.php
<?php

require_once __DIR__ . '/utilidades/usuarios.php';

function escaparHtml(string $valor): string
{
    return htmlspecialchars(
        $valor,
        ENT_QUOTES | ENT_SUBSTITUTE,
        'UTF-8'
    );
}

$idRecibido = $_GET['id'] ?? null;

$usuarioSeleccionado = null;
$erro = null;

if ($idRecibido === null) {
    $erro = 'Non se indicou ningún identificador.';
} elseif (!is_string($idRecibido)) {
    $erro = 'O identificador non é válido.';
} else {
    $idUsuario = filter_var(
        $idRecibido,
        FILTER_VALIDATE_INT,
        [
            'options' => [
                'min_range' => 1,
            ],
        ]
    );

    if ($idUsuario === false) {
        $erro = 'O identificador non é válido.';
    } else {
        $usuarioSeleccionado = obterUsuarioPorID($idUsuario);

        if ($usuarioSeleccionado === null) {
            $erro = 'Non existe ningún usuario con ese identificador.';
        }
    }
}
?>
<!DOCTYPE html>
<html lang="gl">
<head>
    <meta charset="UTF-8">
    <title>Detalles do usuario</title>
</head>
<body>

<h1>Detalles do usuario</h1>

<?php if ($erro !== null): ?>

    <p><?php echo escaparHtml($erro); ?></p>

<?php else: ?>

    <dl>
        <dt>Nome</dt>
        <dd><?php echo escaparHtml($usuarioSeleccionado['nome']); ?></dd>

        <dt>Teléfono</dt>
        <dd><?php echo escaparHtml($usuarioSeleccionado['tfno']); ?></dd>

        <dt>Correo</dt>
        <dd><?php echo escaparHtml($usuarioSeleccionado['email']); ?></dd>
    </dl>

<?php endif; ?>

<p><a href="listaUsuarios.php">Volver á lista de usuarios</a></p>

</body>
</html>


-- Ángel D. Fernández González -- (2026).