Hace dos semanas realizamos un formulario en PHP para solicitar información en la web. Con el contenido de dicho formulario enviábamos un correo electrónico con los datos y presentábamos por pantalla los datos enviados. Vamos a implementar la gestión de usuarios en PHP, donde realizaremos un formulario de registro en el que podremos añadir una foto y guardaremos los datos en una base de datos mySQL.

Gestión de usuarios en PHP
Foto de George Milton en Pexels

¿Para qué un formulario de registro?

Si queremos hacer que la web sea algo más que un escaparate podemos facilitar herramientas para que los visitantes participen, como habilitar zonas con contenido exclusivo para usuarios registrados. Este registro también nos permitirá que las entradas del blog sean escritas por usuarios diferentes por lo que será conveniente crear roles según lo que se le permita hacer a cada usuario.

La creación del formulario de registro será muy similar al de contacto. En este caso añadiremos un campo contraseña de tipo password que hará que cuando se teclee la misma solamente se vean por la pantalla asteriscos.

<div class="single-form">
    <input type="password" class="form-input" id="password" name="password"
           placeholder="Contraseña*" required>
</div>

Como queremos que el usuario suba su fotografía también añadimos el campo correspondiente que en este caso es de tipo file.

<fieldset>
    <p>Sube tu foto</p>
    <input type="file" name="foto" id="foto">
</fieldset>

Vamos a aprovechar también para dar la opción de que se suscriban a nuestra newsletter segmentando por temas de interés:

<div class="custom-control custom-checkbox pt-10">
<p class="pb-10">¿Quieres recibir nuestro boletín? Escoge los temas que te
interesan:</p>
<label class=checkbox"><input type="checkbox" class="form-input" id="bigData"
name="bigData"> Big Data
</label>
<label>&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp</label>
<label class=checkbox"><input type="checkbox" class="form-input" id="inteligenciaArtificial"
name="inteligenciaArtificial"> Inteligencia
Artificial</label>
</div>

El resultado final quedaría así:

Registro web de usuarios

Creación de la base de datos para la gestión de usuarios

Antes de procesar los datos introducidos en el formulario vamos a crear una base de datos donde tendremos una tabla con los usuarios. Para ello nos iremos al pane de control de nuestro hosting, en nuestro caso Raiola Networks.

En la pestaña Base de Datos pulsamos en Bases de datos MySQL®.

Le ponemos un nombre a nuestra base de datos en nuestro caso aprendizajeprofundo aunque el hosting añadirá un prefijo diferente para cada cliente. Creamos un usuario con su contraseña y lo añadimos a la base de datos. Cuando añadimos un usuario a la base de datos elegimos los privilegios que le otorgamos.

A continuación entramos en phpMyAdmin elegimos nuestra base de datos, le damos nombre a una nueva tabla, en nuestro caso usuarios, indicamos el número de campos y pulsamos en continuar.

Hemos elegido 11 campos, uno para que nos sirva de clave primaria que llamaremos id y se autoincrementará con cada registro que añadamos. Los otros nueve serán:

  • nombre
  • email
  • usuario
  • password (guardaremos un hash)
  • foto (guardaremos el nombre de la foto)
  • bigdata (marcaremos si quiere recibir la newsletter de Big Data)
  • inteligenciaartificial (marcaremos si quiere recibir la newsletter de Inteligencia Artificial)
  • politicaprivacidad (marcaremos si ha aceptado la política de privacidad, aunque es obligatorío que la hayan aceptado)
  • rol (por defecto le daremos a todos los usuarios el rol más básico invitado, lo pondremos como valor por defecto)
  • date (para que se guarde la fecha y hora de registro, la actualización será automática)
Base de datos MySQL

Conexión a la base de datos

Vamos a probar la conexión a la base de datos en PHP, particularizando el nombre de la base de datos, del usuario y del password, mediante el siguiente script:

<?php

$dbhost = "localhost";
$dbuser = "";
$dbpass = "";
$dbname = "";
try {
$db = new PDO("mysql:host=$dbhost;dbname=$dbname", $dbuser, $dbpass,
array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'UTF8'"));
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}catch(Exception $error) {
die("Error conexión BBDD " . $error->getMessage());
}

Ejecutamos el script y obtenemos la siguiente pantalla de error:

Error conexión BBDD SQLSTATE[28000] [1045] Access denied for user 'user_name'@'localhost' (using password: YES)

Luego tengo que revisar los datos de la conexión (he cambiado por seguridad el nombre del usuario de la base de datos). Una vez encontrado el error, ejecuto de nuevo el script y en este caso la pantalla se queda en blanco, lo que significa que la conexión se ha realizado correctamente.

¿Cuál era el error que había cometido?

$dbpass es un string y resulta que mi contraseña contenía el carácter ‘$’ reservado de PHP. Simplemente he tenido que anteponer el carácter de escape ‘\’ para que se interprete la contraseña correctamente.

Inserción de un registro

Antes de recoger los datos enviados por el formulario de registro vamos a crear el script correspondiente a la inserción de los datos en la base de datos. Vamos a insertar unos datos de prueba para asegurarnos que lo hacemos correctamente.

<?php
require_once 'conectarBD.php';
$sql_insert = "INSERT INTO `usuarios` (nombre, email, usuario, password, foto, bigdata, inteligenciaartificial, politicaprivacidad)" . "
VALUES (:nombre, :email, :usuario, :password, :foto, :bigdata, :inteligenciaartificial, :politicaprivacidad)";
try {
$sentencia = $db->prepare($sql_insert);
$sentencia->execute(array(
'nombre' => 'José María Jiménez Shaw',
'email' => '[email protected]',
'usuario' => 'josem',
'password' => 'passwordeprueba',
'foto' => 'mifoto.jpg',
'bigdata' => true,
'inteligenciaartificial' => false,
'politicaprivacidad' => true)

);
echo "Insertado";
} catch (PDOException $error) {
die("Error a insertar " . $error->getMessage());
}
?>

Obtenemos el siguiente mensaje de error:

Error a insertar SQLSTATE[22007]: Invalid datetime format: 1366 Incorrect integer value: '' for column `prefijo_aprendizajeprofundo`.`usuarios`.`inteligenciaartificial` at row 1

Revisamos la estructura de la base de datos:

Estructura de base de datos
Vemos que los boolean se almacenan como un entero de tamaño 1 por lo que vamos a sustituir true por 1 y false por 0.
<?php
require_once 'conectarBD.php';
$sql_insert = "INSERT INTO `usuarios` (nombre, email, usuario, password, foto, bigdata, inteligenciaartificial, politicaprivacidad)" . "
VALUES (:nombre, :email, :usuario, :password, :foto, :bigdata, :inteligenciaartificial, :politicaprivacidad)";
try {
$sentencia = $db->prepare($sql_insert);
$sentencia->execute(array(
'nombre' => 'José María Jiménez Shaw',
'email' => '[email protected]',
'usuario' => 'josem',
'password' => 'passwordeprueba',
'foto' => 'mifoto.jpg',
'bigdata' => 1,
'inteligenciaartificial' => 0,
'politicaprivacidad' => 1)

);
echo "Insertado";
} catch (PDOException $error) {
die("Error a insertar " . $error->getMessage());
}
?>

Ahora tenemos como resultado ‘insertado’, por lo que para asegurarnos del funcionamiento correcto solamente nos quedaría comprobar que se ha almacenado en la tabla.

Registro de base de datos
Podemos comprobar que los datos se han insertado correctamente, y en los campos id, rol y date se han completado con el autoincremento y con los valores por defecto respectivamente.

Recogida datos del formulario de registro en PHP

La novedad en el procesamiento del formulario de registro en PHP respecto al formulario en PHP de la página de contacto, está en el almacenamiento del fichero recibido que haremos en un directorio y que almacenaremos los datos recibidos en la base de datos.

Para el caso en el que el usuario no añada una foto en su registro vamos a asignar una foto de perfil de una silueta por defecto, así siempre tendremos una imagen para cuando presentemos la ficha del usuario.

$fichero_movido = "perfil_vacio.jpg"; // para cuando no hay foto

También vamos a prever que el usuario intente cargar la página de procesamiento del registro sin haberse registrado previamente, lo haremos comprobando si se ha recibido el campo email (campo obligatorio). Así nos aseguramos que al usuario no le salen errores de PHP que no entendería.

if (!isset($_REQUEST['email'])) { //Si se accede a la página directamente sin haber recibido los datos de registro
    $error = true;

    // creamos las variables con valor vacío
    $name = "";
    $email = "";
    $user = "";
    $password = "";
    $bigData = "";
    $ia = "";
    $privacyPolicy = "";

} else {

    $error = false;

    // Recupera valores
    $name = $_REQUEST['name'];
    $email = $_REQUEST['email'];
    $user = $_REQUEST['usuario'];
    $password = $_REQUEST['password'];


    if (isset($_REQUEST['bigData'])) {
        $bigData = "Sí"; // Para presentar en pantalla
        $bdata =1; // Para almacenar en base de datos
    } else {
        $bigData = "No"; // Para presentar en pantalla
        $bdata =0; // Para almacenar en base de datos
    }
    if (isset($_REQUEST['inteligenciaArtificial'])) {
        $ia = "Sí"; // Para presentar en pantalla
        $iartificial = 1; // Para almacenar en base de datos
    } else {
        $ia = "No"; // Para presentar en pantalla
        $iartificial = 0; // Para almacenar en base de datos
    }

    if (isset($_REQUEST['privacyPolicy'])) {
        $privacyPolicy = "Sí"; // Para presentar en pantalla
        $ppolicy = 1; // Para almacenar en base de datos
    } else {
        $privacyPolicy = "No"; // Para presentar en pantalla
        $ppolicy = 0; // Para almacenar en base de datos
    }

}

Vamos a trabajar con una serie de funciones para almacenar la foto recibida por el formulario de registro. Empecemos comprobando que el archivo es válido, es decir que tiene la extensión, el contenido y el tamaño permitido.

function is_valido($fichero)
{
$extValidas = array("gif", "jpeg", "jpg", "png", "webp");
$temp = explode(".", $_FILES[$fichero]["name"]);
$extension = end($temp);
$tipo = $_FILES[$fichero]["type"];
$tiposValidos = array("image/jpeg", "image/jpg", "image/pjpeg", "image/x-png", "image/png", "image/webp");
$maxTamano = 100000;
if (in_array($extension, $extValidas)) {
echo "<p>Extensión válida </p>";
} else {
echo "<p>Extensión inválida </p>";
}
if (in_array($tipo, $tiposValidos)) {
echo "<p>Tipo válido </p>";
} else {
echo "<p>Tipo inválido </p>";
}
echo "<p>Tamaño " . $_FILES[$fichero]["size"] . "</p>";
if ($_FILES[$fichero]["size"] < $maxTamano) {
echo "<p>Tamaño permitido</p>";
} else {
echo "<p>Tamaño no permitido</p>";
}
return (in_array($extension, $extValidas) && in_array($tipo, $tiposValidos) && ($_FILES[$fichero]['size'] < $maxTamano));
}

Podemos mostrar los datos del archivo subido, sobre todo a efectos de depuración, seguramente no lo dejemos para la web final:

function muestraFichero($fichero)
{
    echo "<p>Subido: " . $_FILES[$fichero]['name'] . "</p>";
    echo "<p>Tipo: " . $_FILES[$fichero]['type'] . "</p>";
    echo "<p>Tamaño: " . $_FILES[$fichero]['size'] . "</p>";
    echo "<p>Fichero temporal: " . $_FILES[$fichero]['tmp_name'] . "</p>";
}

Hemos estado realizando las comprobaciones en una web temporal por lo deberemos moverlo al directorio donde finalmente lo guardaremos, aunque tendremos que comprobar previamente si el directorio existe.

function mueveFichero($origen, $destino)
{
    move_uploaded_file($origen, $destino);
    echo "Almacenado en " . $destino;
}

function existe_directorio($destino)
{
    return file_exists($destino) && is_dir($destino);
}

Dentro del <body> del html de la página para comprobar si se ha cargado bien el fichero podríamos incluir:

<div class="section-title mb-60 pt-50">
    <?php

    if ($error) {
        echo "<h5>No se han recibido datos</h5><br>";
    } else {
        

        echo "<div>";
        echo "<h5><strong>Resultado Subir Fichero</strong></h5><br>";

        $f = 'foto'; //el nombre del input del formulario
        $d = "img/foto_perfil/";
        if (!is_valido($f)) {
            echo "<p>Fichero inválido</p>";
        } elseif ($_FILES[$f]['error'] > 0) {
            echo "<p>Error: " . $_FILES[$f]['error'] . "</p>";
        } else {
            muestraFichero($f);
            $fichero_movido = $d . $_FILES[$f]['name'];
            if (!existe_directorio($d)) {
                echo "<p>Error: no existe el directorio destino $d </p>";
                // mkdir ($d);
                // echo "<p>Directorio creado $d</p>";
            } elseif (file_exists($fichero_movido)) {
                echo "<p>" . $fichero_movido . " ya existe. </p>";
            } else {
                mueveFichero($_FILES[$f]['tmp_name'], $fichero_movido);
            }
        }
        echo "</div>";
    }
    ?>

Toda esta información nos viene muy bien para depurar. Habrá que ver más adelante si es información relevante para el usuario o no, y si habría que eliminar información que se muestra.

Almacenamiento de datos en la base de datos

Ahora ya podemos almacenar los datos en la base de datos mediante el siguiente código:

        require_once 'conectarBD.php';
        $sql_insert = "INSERT INTO `usuarios` (nombre, email, usuario, password, foto, bigdata, inteligenciaartificial, politicaprivacidad)" . " 
VALUES (:nombre, :email, :usuario, :password, :foto, :bigdata, :inteligenciaartificial, :politicaprivacidad)";
        try {
            $sentencia = $db->prepare($sql_insert);
            $sentencia->execute(array(
                    'nombre' => $name,
                    'email' => $email,
                    'usuario' => $user,
                    'password' => password_hash($password, PASSWORD_DEFAULT),
                    'foto' => $_FILES[$f]['name'],
                    'bigdata' => $bdata,
                    'inteligenciaartificial' => $iartificial,
                    'politicaprivacidad' => $ppolicy)

            );
            echo "Insertado";
        } catch (PDOException $error) {
            die("Error a insertar " . $error->getMessage());
        }

Hemos utilizado la función password_hash() para almacenar un hash del password en vez de éste. No es aconsejable por motivos de seguridad almacenar una contraseña en una base de datos. Cuando queramos comprobar el password lo haremos con la función password_verify (string $password, string $hash): bool. PASSWORD_DEFAULT es una constante diseñada para cambiar siempre que se añada un algoritmo nuevo y más fuerte a PHP.

Tendremos que volver más adelante para impedir que se creen usuarios repetidos.

Acceso de usuarios registrados

Ya tenemos los primeros usuarios registrados. Estos usuarios podrán realizar ciertas funciones que iremos incorporando a la web; pero al menos deberían poder acceder a sus datos, modificarlos y cancelarlos, por lo que implementaremos a continuación el acceso junto con la página de perfil.

Para el acceso podemos crear una página con un formulario sencillo basado en el de registro:

<form id="formulario" action="perfil.php" method="POST" enctype="multipart/form-data" id="contact-form"
      class="contact-form">
    <div class="row">
        <div class="col-md-6">
            <div class="single-form">
                <input type="text" class="form-input" id="usuario" name="usuario"
                       placeholder="Usuario" required>
            </div>
        </div>
        <div class="col-md-6">
            <div class="single-form">
                <input type="password" class="form-input" id="password" name="password"
                       placeholder="Contraseña" required>
            </div>
        </div>
    </div>

    <div class="col-12 pt-40">
        <div class="submit-btn">
            <button class="main-btn btn-hover" type="submit">Enviar</button>
        </div>
    </div>


</form>
Acceso de usuarios en PHP

Crearemos la página de perfil. En la cual haremos una búsqueda en la tabla usuarios por el usuario y comprobaremos que el hash de la contraseña introducida coincide con el que tenemos almacenado. Si no coincide volveremos a la página de acceso mediante la función header(«Location: acceso.php»), en caso contrario recuperar los valores para mostrarlos en pantalla.

$password = $_POST['password'];

$sql = "SELECT * FROM usuarios WHERE usuario ='" . $_POST['usuario'] . "'";


$statement = $db->prepare($sql);
$statement->execute();
$fila = $statement->fetch();

if (!password_verify($password, $fila['password'])) {
header("Location: acceso.php");
exit();
} else {


$error = false;

// Recupera valores
$name = $fila['nombre'];
$email = $fila['email'];
$user = $fila['usuario'];
$usuario = $user;

if ($fila['bigdata'] = 1) {
$bigData = "Sí";
} else {
$bigData = "No";
}
if ($fila['inteligenciaartificial'] = 1) {
$ia = "Sí";
} else {
$ia = "No";
}

if ($fila['politicaprivacidad'] = 1) {
$privacyPolicy = "Sí";
} else {
$privacyPolicy = "No";
}

$d = "assets/img/foto_perfil/";
if ($fila['foto']==''){
$fichero_foto = $d . "perfil_vacio.jpg";
} else {
$fichero_foto = $d . $fila['foto'];
}

}


?>

Tras la presentación de datos he añadido dos botones , borrar y modificar, que más tarde implementaré, ya que damos la opción a los usuarios a borrar y modificar sus datos.

Página de perfil

El código sería el siguiente

    <div class="card">
<div class="card-body">
<p><strong>Nombre: </strong> <?php echo $name; ?></p>
<p><strong>Email: </strong><?php echo $email; ?></p>
<p><strong>Usuario: </strong><?php echo $user; ?></p>
<p><strong>Recibir boletín de Big Data: </strong><?php echo $bigData ?></p>
<p><strong>Recibir boletín de Inteligencia Artificial: </strong><?php echo $ia ?></p>
<br>
<p><strong>Aceptación de Política de Privacidad: </strong><?php echo $privacyPolicy ?></p>
</div>

</div>
<br>
<a href="#" class="main-btn btn-hover">Borrar</a>
&nbsp
&nbsp
<a href="#" class="main-btn btn-hover">Modificar</a>
</div>

Mantener la sesión del usuario

Tal como está la web ahora si volviéramos a la página de inicio y quisiéramos regresar al perfil tendríamos que volver a introducir el usuario y contraseña. Tenemos que mantener el usuario por todas las páginas. Para hacerlo vamos a crear una sesión mediante la función de PHP session_start().

Introducimos en head.php la llamada a la función para poder mantenerla en todas las páginas:

<?php session_start();?>

Y cuando hemos verificado el acceso de usuario correctamente en perfil.php añadimos:

$_SESSION["usuario"] = $user;
$_SESSION['foto'] = $fila['foto'];

Y compruebo que no funciona. Tras múltiples pruebas descubro que no es suficiente con la llamada desde el head. Tiene que estar al principio del todo del archivo PHP, es decir en index.php se cargaba antes:

<!DOCTYPE html>
<html class="no-js" lang="es">
<head>

Suficiente para no poder pasar la variable de sesión. Por lo que lo quito del head y lo añado manualmente a todas las páginas.

Cambio el menú

Modifico el menú añadiendo las opciones de Registro y Acceso que cambiarán cuando un usuario esté conectado, a su foto y usuario y tendrá un menú desplegable con acceso al perfil y a cerrar sesión.

        <?php
        if (!isset($_SESSION["usuario"]) || $_SESSION["usuario"] == ''):
            ?>
            <li class="nav-item id=" tab-registro">
            <a class=\"page-item\" href=registro.php>Registro</a>
            </li>
            <li class="nav-item id=" tab-registro">
            <a class=\"page-item\" href=acceso.php>Acceso</a>
            </li>
        <?php
        else:
            ?>
            <li class="nav-item dropdown">
                <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown"
                   aria-haspopup="true" aria-expanded="false">
                    <img width="20px" src="<?php echo "assets/img/foto_perfil/".$_SESSION["foto"]." "; ?>" alt="Foto de perfil">
                    <?php echo $_SESSION["usuario"] ?>
                </a>
                <div class="dropdown-menu" aria-labelledby="navbarDropdown">
                    <a class="dropdown-item" href="perfil.php">Perfil</a>
                    <a class="dropdown-item" href="cerrar.php">Cerrar Sesión</a>
                </div>
            </li>
        <?php
        endif
        ?>

El desplegable no funcionaba. He tenido que añadir a common.css las siguientes reglas.

.dropdown:hover .dropdown-menu {
display: block;
}

para que se abra el menú al pasar el ratón por encima y

@media only screen and (min-width: 992px) {

  .dropdown-menu {
    background: #19184F;
  }
}

para que se vea un color azul de fondo cuando no estás en móvil ya que las letras son de color blanco.

Cerrar sesión

En el menú llamo a cerrar.php; pero todavía no lo he creado. Es muy sencillo, solamente tiene tres instrucciones. Quedaría así:

<?php
session_start();
session_destroy(); // destruyo la sesión
session_regenerate_id(); // regenera el identificador de sesión, para que no se use el anterior en una nueva sesión
header("Location: index.php");
?>

Después de destruir la sesión redirijo al usuario a la página principal.

Cambiando el procesamiento del registro

La página a la que accedemos para procesar el registro mantiene el menú anterior al registro por lo que el usuario tendría que volver a introducir sus credenciales. Aunque almacenemos los datos de acceso en las variables de sesión no serviría para la página porque el menú se procesa antes. La mejor solución que he encontrado es eliminar esta página. Dejar solamente el proceso PHP, guardar los datos de registro en las variables de sesión y redirigir la página al perfil. Por razones de claridad he guardado las funciones de manejo de ficheros en un archivo aparte. El código sería:

session_start();

$f = 'foto'; // archivo de la foto del formulario
$d = "assets/img/foto_perfil/";
$fichero_movido = $d . "perfil_vacio.jpg"; // para cuando no hay foto

if (!isset($_REQUEST['email'])) { //Si se accede a la página directamente sin haber recibido los datos de registro

    header("Location: index.php");
    exit();

} else {


// Recupera valores
    $name = $_REQUEST['name'];
    $email = $_REQUEST['email'];
    $user = $_REQUEST['usuario'];
    $password = $_REQUEST['password'];
    $usuario = $user;

    if (isset($_REQUEST['bigData'])) {

        $bdata = 1; // Para almacenar en base de datos
    } else {

        $bdata = 0; // Para almacenar en base de datos
    }
    if (isset($_REQUEST['inteligenciaArtificial'])) {

        $iartificial = 1; // Para almacenar en base de datos
    } else {

        $iartificial = 0; // Para almacenar en base de datos
    }

    if (isset($_REQUEST['privacyPolicy'])) {

        $ppolicy = 1; // Para almacenar en base de datos
    } else {

        $ppolicy = 0; // Para almacenar en base de datos
    }

    ini_set('display_errors', 'On'); // Muestra mensajes de error al depurar
    error_reporting(E_ALL | E_STRICT); // Nos indica niveles de error (todos)
    // Mirar el log de apache error.log en /var/log/apache2/error.log

    require_once ("assets/templates/funciones.php"); /inserto funciones de gestión de ficheros
    

    if (!is_valido($f)) {
        echo "<p>Fichero inválido</p>";
    } elseif ($_FILES[$f]['error'] > 0) {
        echo "<p>Error: " . $_FILES[$f]['error'] . "</p>";
    } else {
        muestraFichero($f);
        $fichero_movido = $d . $_FILES[$f]['name'];
        if (!existe_directorio($d)) {
            echo "<p>Error: no existe el directorio destino $d </p>";
            // mkdir ($d);
            // echo "<p>Directorio creado $d</p>";
        } elseif (file_exists($fichero_movido)) {
            echo "<p>" . $fichero_movido . " ya existe. </p>";
        } else {
            mueveFichero($_FILES[$f]['tmp_name'], $fichero_movido);
        }
    }


    require_once 'conectar.php'; //conexión a la base de datos para insertar el usuario

    $sql_insert = "INSERT INTO `usuarios` (nombre, email, usuario, password, foto, bigdata, inteligenciaartificial, politicaprivacidad)" . " 
                VALUES (:nombre, :email, :usuario, :password, :foto, :bigdata, :inteligenciaartificial, :politicaprivacidad)";
    try {
        $sentencia = $db->prepare($sql_insert);
        $sentencia->execute(array(
                'nombre' => $name,
                'email' => $email,
                'usuario' => $user,
                'password' => password_hash($password, PASSWORD_DEFAULT),
                'foto' => $_FILES[$f]['name'],
                'bigdata' => $bdata,
                'inteligenciaartificial' => $iartificial,
                'politicaprivacidad' => $ppolicy)

        );
        echo "Insertado";
    } catch (PDOException $error) {
        die("Error a insertar " . $error->getMessage());
    }
    $_SESSION["usuario"]=$user;
    $_SESSION["foto"]=$_FILES[$f]['name'];

    header("Location: perfil.php");
    exit();


}

Cambiando el código de perfil

Hago cambios en el perfil para distinguir cuando se procede del registro, del acceso, habiendo ya accedido o cargándose directamente la página sin haber accedido. Tras pruebas y corrección de distintos detalles el código PHP quedaría:

session_start();

require_once 'conectar.php';

$usuario = $_POST['usuario'];
$password = $_POST['password'];


if ($usuario == '') $usuario = $_SESSION['usuario'];


if ($password == '' && $usuario == '') // si se cumple es que la página se ha tratado de acceder directamente sin tener acceso
{
header("Location: acceso.php");
exit();
}

$sql = "SELECT * FROM usuarios WHERE usuario ='" . $usuario . "'";

$statement = $db->prepare($sql);
$statement->execute();
$fila = $statement->fetch();


if ($password != '') // si viene del fichero de acceso compruebo el password
if (!password_verify($password, $fila['password'])) {
header("Location: acceso.php");
exit();
}

$error = false;

// Recupera valores
$name = $fila['nombre'];
$email = $fila['email'];
$user = $fila['usuario'];

$_SESSION["usuario"] = $user;
$_SESSION['rol'] = $fila['rol'];

if ($fila['bigdata'] == 1) {
$bigData = "Sí";
} else {
$bigData = "No";
}
if ($fila['inteligenciaartificial'] == 1) {
$ia = "Sí";
} else {
$ia = "No";
}

if ($fila['politicaprivacidad'] == 1) {
$privacyPolicy = "Sí";
} else {
$privacyPolicy = "No";
}

$d = "assets/img/foto_perfil/";
if ($fila['foto'] == '') {
$fichero_foto = $d . "perfil_vacio.jpg";
$_SESSION['foto'] = "perfil_vacio.jpg";
} else {
$fichero_foto = $d . $fila['foto'];
$_SESSION['foto'] = $fila['foto'];
}

Borrar registro

Un usuario tiene derecho a borrar sus datos y lo puede hacer fácilmente desde su perfil pulsando el botón Borrar.

He creado un borrarRegistro.php con el siguiente contenido:

<?php
session_start();
$usuario = $_SESSION["usuario"];

$d = "assets/img/foto_perfil/";

if ($_SESSION["foto"]!=""){
    $foto = $d . $_SESSION["foto"];
    unlink($foto);
}

    If (unlink($foto)) {
        // file was successfully deleted
    } else {
        // there was a problem deleting the file
    }

require_once 'conectar.php'; //conexión a la base de datos para insertar el usuario

$sql = "DELETE FROM usuarios WHERE usuario ='" . $usuario . "'";

try {
    $sentencia = $db->prepare($sql);
    $sentencia->execute();

} catch (PDOException $error) {
    die("Error al borrar " . $error->getMessage());
}
session_destroy(); // destruyo la sesión
session_regenerate_id(); // regenera el identificador de sesión, para que no se use el anterior en una nueva sesión
header("Location: index.php");

Utilizo la función unlink() de PHP para borrar la foto del perfil si es que existía.

Pruebo el script desde el botón borrar y funciona perfectamente. Ahora debería dar la posibilidad al usuario de que confirme si realmente quiere borrar el perfil y que no lo borre de forma accidental. Podría hacerlo llamando a una ventana modal en JavaScript; pero prefiero hacerlo utilizando una página intermedia propia de manera que mantengo en todo momento el mismo diseño de la web. Tendría dos botones, uno para borrar definitivamente y otro para cancelar la operación. Como la página intermedia tiene el mismo menú y el pie de página que el resto de las páginas se podría acceder a otras páginas y se cancelaría la operación de borrado.

Confirmar borrado registro PHP

El código sería el siguiente:

<div class="container">
<div class="row">
<div class="col-lg-12">
<div class="section-title mb-60 pt-50">
<div class="card w-100 text-center">
<div class="card-body">
<h5 class="card-title">¿Está seguro de que quiere borrar su perfil?</h5>
<p class="card-text">Esta operación no se puede deshacer</p>
<span class="pt-20"><a href="borrar.php" class="main-btn btn-hover">Sí, quiero borrarlo</a></span>
<span class="pt-20"><a href="perfil.php" class="main-btn btn-hover">Cancelar</a></span>
</div>

</div>

</div>
</div>
</div>
</div>

Modificar Perfil

Vamos a permitir que un usuario registrado cambien de su perfil los siguientes datos. El nombre, el email, su foto de perfil y los boletines a los que está suscrito. Para ello creamos un formulario donde recogemos los datos actuales pero con campos modificables. La foto se puede cargar o no, si no se selecciona una foto nueva se mantendrá la que había, el resto de los datos se volverá a enviar y si por ejemplo hemos borrado el nombre, éste desaparecerá de la base de datos.

Modificar usuario

La página de modificar perfil solamente ha de estar disponible para un usuario que se ha identificado. Si se trata de entrar en la página si haber introducido el usuario y contraseña se redirige al usuario a la página de acceso:

$usuario = $_SESSION['usuario'];


if ($usuario == '') // si se cumple es que la página se ha tratado de acceder directamente sin tener acceso
{
    $errorAcceso = "Acceso incorrecto. Introduzca su usuario y contraseña";
    header("Location: acceso.php?errorAcceso=$errorAcceso");
    exit();

Utilizamos la propiedad value para mostrar el valor que tiene actualmente un campo del formulario:

<div class="single-form">
    <input type="text" class="form-input" id="name" name="name" placeholder="Nombre"
           value= "<?php echo $name ?>">
</div>

El formulario de modificación del perfil lo vamos a procesar realizando algunos cambios en el código que creamos para procesar el registro de un usuario. El archivo PHP sabrá distinguir que venimos del formulario de modificación porque vamos a enviar en el POST una variable oculta de nombre modificar con valor 1:

<input type="hidden" name="modificar" value="1">

La diferencia fundamental va a ser que si

isset($_REQUEST['modificar'])

vamos a actualizar un registro existente y en caso contrario vamos insertar un nuevo registro.

Hasta ahora no habíamos considerado que no puede haber dos registros con el mismo usuario ni con el mismo email. He realizado cambios para que si se quiere registrar alguien con un usuario que ya están el base de datos se le devuelve un mensaje de error enviándole de nuevo al formulario de registro.

Error en el registro

El código sería

// Comprobamos si el usuario existe
$sql_user = "SELECT COUNT(*) as total FROM usuarios WHERE usuario ='" . $usuario . "'";

$sentencia = $db->prepare($sql_user);
$result = $sentencia->execute();
$num = $sentencia->fetchColumn();

if ($num > 0) {
$errorRegister = "El usuario ya existe, elija otro usuario";
header("Location: registro.php?errorRegister=$errorRegister");
exit();
}

Algo similar realizo para comprobar que el email no está en la base de datos.

Si es un registro nuevo necesitaré guardar el usuario en una variable de sesión y la foto para poder mostrarla en el menú, tanto si era un usuario nuevo como si se ha modificado la foto de perfil. Termino el el procesamiento de ambos formularios con una redirección al perfil.

$_SESSION["usuario"] = $user;
$_SESSION["foto"] = $_FILES[$f]['name'];

header("Location: perfil.php");
exit();

Cambiar Contraseña

Lo único que no dejamos modificar en el perfil es el nombre de usuario. Es habitual en toda gestión de usuarios dejar ese campo inmutable, no obstante, si alguien quisiera cambiar el nombre de usuario podría borrar su usuario y crear uno nuevo,

Nos queda dar la opción de cambiar la contraseña y por su relevancia he preferido ponerla directamente en el menú en vez de el perfil de usuario. El nuevo menú quedaría así:

<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
<img width="20px" src="<?php echo "assets/img/foto_perfil/" . $_SESSION["foto"] . " "; ?>"
alt="Foto de perfil">
<?php echo $_SESSION["usuario"] ?>
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" href="perfil.php">Perfil</a></li>
<li><a class="dropdown-item" href="password.php">Cambiar contraseña</a></li>
<li><a class="dropdown-item" href="cerrar.php">Cerrar Sesión</a></li>
<?php

Para cambiar la contraseña hemos creado un formulario donde se pide la contraseña dos veces.

El formulario es muy sencillo. Las dos contraseñas las envío a un fichero php que comprueba si ambos valores son iguales. En caso contrario devuelve un mensaje de error:

$password = $_POST['password'];

if ($password != $_POST['rePassword']) {
$errorPassword = "Las contraseñas no coinciden. Inténtelo de nuevo";
header("Location: password.php?errorPassword=$errorPassword");

exit();
}

Como cuando implementamos el registro, no guardamos la contraseña sino el hash:

$hash = password_hash($password, PASSWORD_DEFAULT);

Y para guardarlo ejecutamos la siguiente sentencia SQL:

$sql_update = "UPDATE usuarios SET password='" . $hash . "' WHERE usuario ='" . $usuario . "'";

Si no hay ningún problema realizo a una redirección a la página de perfil con un mensaje de éxito:

Contraseña cambiada

El código sería:

$mensaje = "Contraseña cambiada";
header("Location: perfil.php?mensaje=$mensaje");

exit();

Rol de administrador

Hasta hora todos los usuarios tienen el rol «invitado», es el rol que se asigna por defecto cuando se crea un registro, Es algo que se produce de forma automática porque así lo he definido al crear la base de datos.

Ahora vamos a asignar a algunos usuarios el rol «admin» y crearemos una nueva opción en el menú que solamente verán ellos y que les llevará a una página privada donde podrán ver todos los usuarios registrados. Más adelante crearemos otros roles como el del editor para permitir publicar posts en el blog.

<?php
if ($_SESSION["rol"] == 'admin'): ?>
<li>
<hr class="dropdown-divider">
</li>
<li><a class="dropdown-item" href="usuarios.php">Usuarios</a></li>
<?php endif ?>

La página de usuarios es muy importante que esté protegida y solamente puedan entrar administradores. Si alguien intenta entrar sin autorización lo redirigimos a la página de inicio:

// Página solamente accesible con el rol admin
if (!isset($_SESSION['rol'])||$_SESSION['rol']!='admin'){
    header("Location: index.php");
    exit();
}

Los datos los voy a recuperar mediante un bucle que recorra la base de datos y los almaceno en un array:

require_once 'conectar.php';

$sql = "SELECT nombre, email, usuario, foto, bigdata, inteligenciaartificial, politicaprivacidad, rol FROM usuarios";
$statement = $db->prepare($sql);
$statement->execute();

while ($fila = $statement->fetch()) {
$filas[] = array(
"nombre" => $fila['nombre'],
"email" => $fila['email'],
"usuario" => $fila['usuario'],
"foto" => $fila['foto'],
"bigdata" => $fila['bigdata'],
"inteligenciaartificial" => $fila['inteligenciaartificial'],
"politicaprivacidad" => $fila['politicaprivacidad'],
"rol" => $fila['rol']);
}

Para mostrarlos en página usamos un bucle for:

<div class="container">
<div class="row mt-20">
<?php for($i = 0; $i < sizeof($filas); $i++) {

$d = "assets/img/foto_perfil/";
if ( $filas[$i]['foto'] == '') {
$fichero_foto = $d . "perfil_vacio.jpg";
} else {
$fichero_foto = $d . $filas[$i]['foto'];
}

?>
<div class="col-12 col-lg-4 col-xl-4">
<div class="section-title">
<div class="card">
<div class="card-body">
<img width="100px" src="<?php echo $fichero_foto; ?>" alt="Foto de perfil">
<p><strong>Nombre: </strong> <?php echo $filas[$i]['nombre']; ?></p>
<p><strong>Email: </strong><?php echo $filas[$i]['email']; ?></p>
<p><strong>Usuario: </strong><?php echo $filas[$i]['usuario']; ?></p>
<p><strong>Recibir boletín de Big Data: </strong><?php echo $filas[$i]['bigdata']; ?></p>
<p><strong>Recibir boletín de Inteligencia Artificial: </strong><?php echo $filas[$i]['inteligenciaartificial']; ?></p>
<p><strong>Rol: </strong><?php echo $filas[$i]['rol']; ?></p>
<br>
<p><strong>Aceptación de Política de Privacidad: </strong><?php echo $filas[$i]['politicaprivacidad'] ?></p>
</div>
</div>
<br>
</div>
</div>
<?php } ?>
</div>
</div>

No he implementado paginación por lo que habría que desplazarse por la página para ver todos los usuarios si hubiera más de nueve.

Una posible mejora para el futuro de este apartado sería dar la posibilidad a un usuario administrador a cambiar el rol de un usuario. En estos momentos para hacerlo hay que entrar directamente en la base de datos con phpMyAdmiin.

Prueba la gestión de usuarios en PHP

Llevo un mes trabajando en la web, probando el código desarrollado, corrigiendo errores y añadiendo funcionalidades. Preveo que todavía estaré trabajando alguna semana más aportándola funcionalidad.

No obstante creo que he avanzado lo suficiente para darte la oportunidad de probarla y darme tu opinión si lo deseas, para ello voy a dejar aquí la contraseña de un usuario administrador para que puedas probar por ejemplo la funcionalidad de listar los usuarios. No te preocupes, de que aunque sea administrador no vas a poder hacer mucho ni romper nada, solamente ver los usuarios ficticios que he creado para pruebas. Te animo también a que crees otros usuarios y realices operaciones incorrectas con la web y que me dejes en los comentarios el resultado, así podré corregir los errores antes de entrar en producción.

Usuario: Lucia

Contraseña: 12345678

Si has llegado aquí porque tienes algún problema en la gestión de usuarios en PHP y mis explicaciones no son suficientes, déjame un comentario y te ayudaré lo antes posible.

También he creado en Optima WorkShop un estudio de navegación web en Aprendizaje Profundo, te invito a que participes y me des tu opinión: https://ows.io/os/r48w2h18

No te pierdas el próximo artículo donde mejoraremos el diseño del blog, guardaremos las entradas en una base de datos y accederemos a ellas con una plantilla del blog, inicialmente con una url con un parámetro y posteriormente una url amigable gracias a las reglas que añadiré al archivo .htaccess.

Shares
Share This