Cómo Crea un Foro Potenciado por PHP/MySQL desde Cero
Spanish (Español) translation by Jorge Montoya (you can also view the original English article)
En este tutorial, vamos a construir un foro con PHP / MySQL desde cero. Este tutorial es perfecto para acostumbrarse al uso básico de PHP y de bases de datos. Empecemos!
Paso 1: Crear las Tablas de la Base de Datos
Siempre es una buena idea comenzar con la creación de un buen modelo de datos al crear una aplicación. Vamos a describir nuestra aplicación en una frase: vamos a hacer un foro que tenga usuarios que creen temas en varias categorías. Otros usuarios pueden publicar respuestas. Como puede ver, destaqué un par de sustantivos que representan nuestros nombres de tabla.
Usuarios
- Categorías
- Temas
- Publicaciones
Estos tres objetos están relacionados entre sí, por lo que procesaremos eso en nuestro diseño de tabla. Eche un vistazo al esquema a continuación.



Se ve bastante bien, ¿verdad? Cada cuadrado es una tabla de base de datos. Todas las columnas se enumeran en ella y las líneas entre ellas representan las relaciones. Las explicaré más a fondo, así que está bien si no tiene mucho sentido para usted en este momento.
Discutiré cada tabla explicando el SQL, que creé usando el esquema anterior. Para sus propios scripts puede crear un esquema similar y SQL también. Algunos editores como MySQL Workbench (el que utilicé) también pueden generar archivos .sql, pero recomendaría aprender SQL porque es más divertido hacerlo usted mismo. Se puede encontrar una introducción a SQL en W3Schools.
Tabla de Usuarios
1 |
CREATE TABLE users ( |
2 |
user_id INT(8) NOT NULL AUTO_INCREMENT, |
3 |
user_name VARCHAR(30) NOT NULL, |
4 |
user_pass VARCHAR(255) NOT NULL, |
5 |
user_email VARCHAR(255) NOT NULL, |
6 |
user_date DATETIME NOT NULL, |
7 |
user_level INT(8) NOT NULL, |
8 |
UNIQUE INDEX user_name_unique (user_name), |
9 |
PRIMARY KEY (user_id) |
10 |
) TYPE=INNODB; |
La instrucción CREATE TABLE se usa para indicar que queremos crear una nueva tabla, por supuesto. La declaración es seguida por el nombre de la tabla y todas las columnas se enumeran entre los corchetes. Los nombres de todos los campos se explican por sí mismos, por lo que solo analizaremos los tipos de datos a continuación.
user_id
"Una clave principal se usa para identificar de manera única cada fila en una tabla".
El tipo de este campo es INT, lo que significa que este campo contiene un número entero. El campo no puede estar vacío (NOT NULL) y aumenta con cada registro insertado. En la parte inferior de la tabla, puede ver que el campo user_id se declara como clave principal. Una clave principal se usa para identificar de manera única cada fila en una tabla. No hay dos filas distintas en una tabla que puedan tener el mismo valor (o combinación de valores) en todas las columnas. Eso podría ser un poco confuso, así que aquí hay un pequeño ejemplo.
Hay un usuario llamado John Doe. Si otro usuario se registra con el mismo nombre, hay un problema, porque: ¿qué usuario es cuál? No se puede decir y la base de datos tampoco puede decir. Al usar una clave principal, este problema se resuelve porque ambos temas son únicos.
Todas las otras tablas también tienen teclas principales y funcionan de la misma manera.
user_name
Este es un campo de texto, llamado campo VARCHAR en MySQL. El número entre corchetes es la longitud máxima. Un usuario puede elegir un nombre de usuario de hasta 30 caracteres de largo. Este campo no puede ser NULL. En la parte inferior de la tabla, puede ver que este campo se declara UNIQUE, lo que significa que el mismo nombre de usuario no se puede registrar dos veces. La parte UNIQUE INDEX le dice a la base de datos que queremos agregar una clave única. Luego definimos el nombre de la clave única, user_name_unique en este caso. Entre paréntesis está el campo al que se aplica la clave única, que es user_name.
user_pass
Este campo es igual al campo user_name, excepto la longitud máxima. Dado que la contraseña del usuario, sin importar la longitud, tiene hash con sha1(), la contraseña siempre tendrá 40 caracteres de largo.
user_email
Este campo es igual al campo user_pass.
user_date
Este es un campo en el que almacenaremos la fecha en que el usuario se registró. Su tipo es DATETIME y el campo no puede ser NULL.
user_level
Este campo contiene el nivel del usuario, por ejemplo: '0' para un usuario regular y '1' para un administrador. Más sobre esto más tarde.
Tabla de Categorías
1 |
CREATE TABLE categories ( |
2 |
cat_id INT(8) NOT NULL AUTO_INCREMENT, |
3 |
cat_name VARCHAR(255) NOT NULL, |
4 |
cat_description VARCHAR(255) NOT NULL, |
5 |
UNIQUE INDEX cat_name_unique (cat_name), |
6 |
PRIMARY KEY (cat_id) |
7 |
) TYPE=INNODB; |
Estos tipos de datos básicamente funcionan de la misma manera que los de la tabla de usuarios. Esta tabla también tiene una clave principal y el nombre de la categoría debe ser único.
Tabla de Temas
1 |
CREATE TABLE topics ( |
2 |
topic_id INT(8) NOT NULL AUTO_INCREMENT, |
3 |
topic_subject VARCHAR(255) NOT NULL, |
4 |
topic_date DATETIME NOT NULL, |
5 |
topic_cat INT(8) NOT NULL, |
6 |
topic_by INT(8) NOT NULL, |
7 |
PRIMARY KEY (topic_id) |
8 |
) TYPE=INNODB; |
Esta tabla es casi la misma que las otras tablas, a excepción del campo topic_by. Ese campo se refiere al usuario que creó el tema. El campo topic_cat se refiere a la categoría a la que pertenece el tema. No podemos forzar estas relaciones simplemente declarando el campo. Tenemos que dejar que la base de datos sepa que este campo debe contener un user_id existente de la tabla de usuarios, o un cat_id válido de la tabla de categorías. Agregaremos algunas relaciones después de haber discutido la tabla de publicaciones.
Tabla de Publicaciones
1 |
CREATE TABLE posts ( |
2 |
post_id INT(8) NOT NULL AUTO_INCREMENT, |
3 |
post_content TEXT NOT NULL, |
4 |
post_date DATETIME NOT NULL, |
5 |
post_topic INT(8) NOT NULL, |
6 |
post_by INT(8) NOT NULL, |
7 |
PRIMARY KEY (post_id) |
8 |
) TYPE=INNODB; |
Esto es lo mismo que el resto de las tablas; también hay un campo que hace referencia a un user_id aquí: el campo post_by. El campo post_topic se refiere al tema al que pertenece la publicación.
"Una clave externa es una restricción referencial entre dos tablas. La clave externa identifica una columna o un conjunto de columnas en una tabla (de referencia) que hace referencia a una columna o conjunto de columnas en otra tabla (referenciada)".
Ahora que hemos ejecutado estas consultas, tenemos un modelo de datos bastante decente, pero las relaciones aún faltan. Comencemos con la definición de una relación. Vamos a usar algo llamado clave externa. Una clave externa es una restricción referencial entre dos tablas. La clave externa identifica una columna o un conjunto de columnas en una tabla (de referencia) que hace referencia a una columna o conjunto de columnas en otra tabla (referenciada). Algunas condiciones:
- La columna en la tabla de referencia a la que se refiere la clave externa debe ser una clave primaria
- Los valores a los que se hace referencia deben existir en la tabla a la que se hace referencia
Al agregar claves externas, la información se vincula entre sí, lo cual es muy importante para la normalización de la base de datos. Ahora sabe qué es una clave externa y por qué las usamos. Es hora de agregarlas a las tablas que ya hemos creado utilizando la instrucción ALTER, que se puede usar para cambiar una tabla ya existente.
Primero vincularemos los temas a las categorías:
1 |
ALTER TABLE topics ADD FOREIGN KEY(topic_cat) REFERENCES categories(cat_id) ON DELETE CASCADE ON UPDATE CASCADE; |
La última parte de la consulta ya dice lo que sucede. Cuando una categoría se elimina de la base de datos, también se eliminarán todos los temas. Si el cat_id de una categoría cambia, todos los temas se actualizarán también. Para eso es la parte de ON UPDATE CASCADE. Por supuesto, puede revertir esto para proteger sus datos, de modo que no pueda eliminar una categoría, siempre y cuando todavía tenga temas vinculados a ella. Si desea hacer eso, puede reemplazar la parte 'ON DELETE CASCADE' con 'ON DELETE RESTRICT'. También hay SET NULL y NO ACTION, que hablan por sí mismos.
Todos los temas están vinculados a una categoría ahora. Vamos a vincular los temas con el usuario que crea uno.
1 |
ALTER TABLE topics ADD FOREIGN KEY(topic_by) REFERENCES users(user_id) ON DELETE RESTRICT ON UPDATE CASCADE; |
Esta clave externa es la misma que la anterior, pero hay una diferencia: el usuario no puede eliminarse mientras haya temas con la identificación del usuario. No usamos CASCADE aquí porque podría haber información valiosa en nuestros temas. No deseamos que esa información se borre si alguien decide eliminar su cuenta. Para dar a los usuarios la oportunidad de eliminar su cuenta, puede crear alguna característica que anonimice todos sus temas y luego eliminar su cuenta. Desafortunadamente, eso está más allá del alcance de este tutorial.
Enlace las publicaciones a los temas:
1 |
ALTER TABLE posts ADD FOREIGN KEY(post_topic) REFERENCES topics(topic_id) ON DELETE CASCADE ON UPDATE CASCADE; |
Y finalmente, vincule cada publicación al usuario que la realizó:
1 |
ALTER TABLE posts ADD FOREIGN KEY(post_by) REFERENCES users(user_id) ON DELETE RESTRICT ON UPDATE CASCADE; |
¡Esa es la parte de la base de datos! Fue bastante trabajo, pero el resultado, un gran modelo de datos, definitivamente vale la pena.
Paso 2: Introducción al Sistema de Encabezado y Pie de Página
Cada página de nuestro foro necesita algunas cosas básicas, como un doctype y algo de marcado. Es por eso que incluiremos un archivo header.php en la parte superior de cada página y un footer.php en la parte inferior. El header.php contiene un doctype, un enlace a la hoja de estilo y cierta información importante sobre el foro, como la etiqueta del título y las metaetiquetas.
header.php
1 |
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
2 |
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
3 |
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="nl" lang="nl"> |
4 |
<head>
|
5 |
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> |
6 |
<meta name="description" content="A short description." /> |
7 |
<meta name="keywords" content="put, keywords, here" /> |
8 |
<title>PHP-MySQL forum</title> |
9 |
<link rel="stylesheet" href="style.css" type="text/css"> |
10 |
</head>
|
11 |
<body>
|
12 |
<h1>My forum</h1> |
13 |
<div id="wrapper"> |
14 |
<div id="menu"> |
15 |
<a class="item" href="/forum/index.php">Home</a> - |
16 |
<a class="item" href="/forum/create_topic.php">Create a topic</a> - |
17 |
<a class="item" href="/forum/create_cat.php">Create a category</a> |
18 |
|
19 |
<div id="userbar"> |
20 |
<div id="userbar">Hello Example. Not you? Log out.</div> |
21 |
</div>
|
22 |
<div id="content"> |
El div wrapper se usará para facilitar el estilo de toda la página. El div menu obviamente contiene un menú con enlaces a páginas que todavía tenemos que crear, pero ayuda un poco a ver hacia dónde vamos. El div userbar se usará para una pequeña barra superior que contiene cierta información como el nombre de usuario y un enlace a la página de cierre de sesión. La página de contenido abarca el contenido real de la página, obviamente.
El lector atento quizás ya haya notado que nos faltan algunas cosas. No hay etiqueta </body> o </html>. Están en la página footer.php, como puede ver a continuación.
1 |
</div><!-- content --> |
2 |
</div><!-- wrapper --> |
3 |
<div id="footer">Created for Nettuts+</div> |
4 |
</body>
|
5 |
</html>
|
Cuando incluimos un encabezado y un pie de página en cada página, el resto de la página queda incrustado entre el encabezado y el pie de página. Este método tiene algunas ventajas. En primer lugar, todo se diseñará correctamente. Un pequeño ejemplo:
1 |
<?php
|
2 |
$error = false; |
3 |
if($error = false) |
4 |
{
|
5 |
//the beautifully styled content, everything looks good
|
6 |
echo '<div id="content">some text</div>'; |
7 |
}
|
8 |
else
|
9 |
{
|
10 |
//bad looking, unstyled error :-(
|
11 |
}
|
12 |
?>
|
Como puede ver, una página sin errores dará como resultado una buena página con el contenido. Pero si hay un error, todo se ve realmente feo; así que es por eso que es mejor asegurarse de que no solo el contenido real tenga el estilo correcto, sino también los errores que podamos obtener.
Otra ventaja es la posibilidad de hacer cambios rápidos. Puede verlo usted mismo editando el texto en footer.php cuando haya terminado este tutorial; Notará que el pie de página cambia en cada página de inmediato. Finalmente, agregamos una hoja de estilo que nos proporciona un marcado básico, nada demasiado sofisticado.
1 |
body { |
2 |
background-color: #4E4E4E; |
3 |
text-align: center; /* make sure IE centers the page too */ |
4 |
}
|
5 |
|
6 |
#wrapper { |
7 |
width: 900px; |
8 |
margin: 0 auto; /* center the page */ |
9 |
}
|
10 |
|
11 |
#content { |
12 |
background-color: #fff; |
13 |
border: 1px solid #000; |
14 |
float: left; |
15 |
font-family: Arial; |
16 |
padding: 20px 30px; |
17 |
text-align: left; |
18 |
width: 100%; /* fill up the entire div */ |
19 |
}
|
20 |
|
21 |
#menu { |
22 |
float: left; |
23 |
border: 1px solid #000; |
24 |
border-bottom: none; /* avoid a double border */ |
25 |
clear: both; /* clear:both makes sure the content div doesn't float next to this one but stays under it */ |
26 |
width:100%; |
27 |
height:20px; |
28 |
padding: 0 30px; |
29 |
background-color: #FFF; |
30 |
text-align: left; |
31 |
font-size: 85%; |
32 |
}
|
33 |
|
34 |
#menu a:hover { |
35 |
background-color: #009FC1; |
36 |
}
|
37 |
|
38 |
#userbar { |
39 |
background-color: #fff; |
40 |
float: right; |
41 |
width: 250px; |
42 |
}
|
43 |
|
44 |
#footer { |
45 |
clear: both; |
46 |
}
|
47 |
|
48 |
/* begin table styles */
|
49 |
table { |
50 |
border-collapse: collapse; |
51 |
width: 100%; |
52 |
}
|
53 |
|
54 |
table a { |
55 |
color: #000; |
56 |
}
|
57 |
|
58 |
table a:hover { |
59 |
color:#373737; |
60 |
text-decoration: none; |
61 |
}
|
62 |
|
63 |
th { |
64 |
background-color: #B40E1F; |
65 |
color: #F0F0F0; |
66 |
}
|
67 |
|
68 |
td { |
69 |
padding: 5px; |
70 |
}
|
71 |
|
72 |
/* Begin font styles */
|
73 |
h1, #footer { |
74 |
font-family: Arial; |
75 |
color: #F1F3F1; |
76 |
}
|
77 |
|
78 |
h3 {margin: 0; padding: 0;} |
79 |
|
80 |
/* Menu styles */
|
81 |
.item { |
82 |
background-color: #00728B; |
83 |
border: 1px solid #032472; |
84 |
color: #FFF; |
85 |
font-family: Arial; |
86 |
padding: 3px; |
87 |
text-decoration: none; |
88 |
}
|
89 |
|
90 |
.leftpart { |
91 |
width: 70%; |
92 |
}
|
93 |
|
94 |
.rightpart { |
95 |
width: 30%; |
96 |
}
|
97 |
|
98 |
.small { |
99 |
font-size: 75%; |
100 |
color: #373737; |
101 |
}
|
102 |
#footer { |
103 |
font-size: 65%; |
104 |
padding: 3px 0 0 0; |
105 |
}
|
106 |
|
107 |
.topic-post { |
108 |
height: 100px; |
109 |
overflow: auto; |
110 |
}
|
111 |
|
112 |
.post-content { |
113 |
padding: 30px; |
114 |
}
|
115 |
|
116 |
textarea { |
117 |
width: 500px; |
118 |
height: 200px; |
119 |
}
|
Paso 3: Preparándose para la Acción
Antes de que podamos leer algo de nuestra base de datos, necesitamos una conexión. Para eso está connect.php. Lo incluiremos en cada archivo que vamos a crear.
1 |
<?php
|
2 |
//connect.php
|
3 |
$server = 'localhost'; |
4 |
$username = 'usernamehere'; |
5 |
$password = 'passwordhere'; |
6 |
$database = 'databasenamehere'; |
7 |
|
8 |
if(!mysql_connect($server, $username, $password)) |
9 |
{
|
10 |
exit('Error: could not establish database connection'); |
11 |
}
|
12 |
if(!mysql_select_db($database) |
13 |
{
|
14 |
exit('Error: could not select the database'); |
15 |
}
|
16 |
?>
|
Simplemente reemplace los valores predeterminados de las variables en la parte superior de la página con su propia fecha, guarde el archivo y ¡listo!
Paso 4: Visualización de la Descripción General del Foro
Como recién comenzamos con algunas técnicas básicas, por ahora vamos a hacer una versión simplificada de la descripción general del foro.
1 |
<?php
|
2 |
//create_cat.php
|
3 |
include 'connect.php'; |
4 |
include 'header.php'; |
5 |
|
6 |
echo '<tr>'; |
7 |
echo '<td class="leftpart">'; |
8 |
echo '<h3><a href="category.php?id=">Category name</a></h3> Category description goes here'; |
9 |
echo '</td>'; |
10 |
echo '<td class="rightpart">'; |
11 |
echo '<a href="topic.php?id=">Topic subject</a> at 10-10'; |
12 |
echo '</td>'; |
13 |
echo '</tr>'; |
14 |
include 'footer.php'; |
15 |
?>
|
Ahí lo tiene: una visión general agradable y limpia. Vamos a actualizar esta página a lo largo del tutorial para que se parezca más al resultado final, ¡paso a paso!
Paso 5: Registrándose como Usuario
Comencemos haciendo un formulario HTML simple para que un nuevo usuario pueda registrarse.



Se necesita una página de PHP para procesar el formulario. Vamos a usar una variable $_SERVER. La variable $_SERVER es un array con valores que se establecen automáticamente con cada solicitud. Uno de los valores del array $_SERVER es 'REQUEST_METHOD'. Cuando se solicita una página con GET, esta variable contendrá el valor 'GET'. Cuando se solicita una página a través de POST, mantendrá el valor 'POST'. Podemos usar este valor para verificar si un formulario ha sido publicado. Vea la página signup.php a continuación.
1 |
<?php
|
2 |
//signup.php
|
3 |
include 'connect.php'; |
4 |
include 'header.php'; |
5 |
|
6 |
echo '<h3>Sign up</h3>'; |
7 |
|
8 |
if($_SERVER['REQUEST_METHOD'] != 'POST') |
9 |
{
|
10 |
/*the form hasn't been posted yet, display it
|
11 |
note that the action="" will cause the form to post to the same page it is on */
|
12 |
echo '<form method="post" action=""> |
13 |
Username: <input type="text" name="user_name" />
|
14 |
Password: <input type="password" name="user_pass">
|
15 |
Password again: <input type="password" name="user_pass_check">
|
16 |
E-mail: <input type="email" name="user_email">
|
17 |
<input type="submit" value="Add category" />
|
18 |
</form>'; |
19 |
}
|
20 |
else
|
21 |
{
|
22 |
/* so, the form has been posted, we'll process the data in three steps:
|
23 |
1. Check the data
|
24 |
2. Let the user refill the wrong fields (if necessary)
|
25 |
3. Save the data
|
26 |
*/
|
27 |
$errors = array(); /* declare the array for later use */ |
28 |
|
29 |
if(isset($_POST['user_name'])) |
30 |
{
|
31 |
//the user name exists
|
32 |
if(!ctype_alnum($_POST['user_name'])) |
33 |
{
|
34 |
$errors[] = 'The username can only contain letters and digits.'; |
35 |
}
|
36 |
if(strlen($_POST['user_name']) > 30) |
37 |
{
|
38 |
$errors[] = 'The username cannot be longer than 30 characters.'; |
39 |
}
|
40 |
}
|
41 |
else
|
42 |
{
|
43 |
$errors[] = 'The username field must not be empty.'; |
44 |
}
|
45 |
|
46 |
|
47 |
if(isset($_POST['user_pass'])) |
48 |
{
|
49 |
if($_POST['user_pass'] != $_POST['user_pass_check']) |
50 |
{
|
51 |
$errors[] = 'The two passwords did not match.'; |
52 |
}
|
53 |
}
|
54 |
else
|
55 |
{
|
56 |
$errors[] = 'The password field cannot be empty.'; |
57 |
}
|
58 |
|
59 |
if(!empty($errors)) /*check for an empty array, if there are errors, they're in this array (note the ! operator)*/ |
60 |
{
|
61 |
echo 'Uh-oh.. a couple of fields are not filled in correctly..'; |
62 |
echo '<ul>'; |
63 |
foreach($errors as $key => $value) /* walk through the array so all the errors get displayed */ |
64 |
{
|
65 |
echo '<li>' . $value . '</li>'; /* this generates a nice error list */ |
66 |
}
|
67 |
echo '</ul>'; |
68 |
}
|
69 |
else
|
70 |
{
|
71 |
//the form has been posted without, so save it
|
72 |
//notice the use of mysql_real_escape_string, keep everything safe!
|
73 |
//also notice the sha1 function which hashes the password
|
74 |
$sql = "INSERT INTO |
75 |
users(user_name, user_pass, user_email ,user_date, user_level)
|
76 |
VALUES('" . mysql_real_escape_string($_POST['user_name']) . "', |
77 |
'" . sha1($_POST['user_pass']) . "', |
78 |
'" . mysql_real_escape_string($_POST['user_email']) . "', |
79 |
NOW(),
|
80 |
0)"; |
81 |
|
82 |
$result = mysql_query($sql); |
83 |
if(!$result) |
84 |
{
|
85 |
//something went wrong, display the error
|
86 |
echo 'Something went wrong while registering. Please try again later.'; |
87 |
//echo mysql_error(); //debugging purposes, uncomment when needed
|
88 |
}
|
89 |
else
|
90 |
{
|
91 |
echo 'Successfully registered. You can now <a href="signin.php">sign in</a> and start posting! :-)'; |
92 |
}
|
93 |
}
|
94 |
}
|
95 |
|
96 |
include 'footer.php'; |
97 |
?>
|
Hay mucha explicación en los comentarios que hice en el archivo, así que asegúrese de revisarlos. El procesamiento de los datos se lleva a cabo en tres partes:
- Validando los datos
- Si los datos no son válidos, muestre el formulario de nuevo
- Si los datos son válidos, guarde el registro en la base de datos
La parte de PHP es bastante autoexplicativa. La consulta SQL, sin embargo, probablemente necesite un poco más de explicación.
1 |
INSERT INTO |
2 |
users(user_name, user_pass, user_email ,user_date, user_level) |
3 |
VALUES('" . mysql_real_escape_string($_POST['user_name']) . "', |
4 |
'" . sha1($_POST['user_pass']) . "', |
5 |
'" . mysql_real_escape_string($_POST['user_email']) . "', |
6 |
NOW(), |
7 |
0); |
En la línea 1 tenemos la declaración INSERT INTO que habla por sí misma. El nombre de la tabla se especifica en la segunda línea. Las palabras entre corchetes representan las columnas en las que queremos insertar los datos. La declaración VALUES indica a la base de datos que hemos terminado declarando los nombres de las columnas y es hora de especificar los valores. Aquí hay algo nuevo: mysql_real_escape_string. La función escapa de los caracteres especiales en una cadena no escapada, así que es seguro colocarla en una consulta. Esta función DEBE ser siempre utilizada, con muy pocas excepciones. Hay demasiados scripts que no lo usan y que pueden ser hackeados realmente fáciles. No corra el reisgo, use mysql_real_escape_string().
"Nunca inserte una contraseña simple tal como está. DEBE cifrarla siempre".
Además, puede ver que la función sha1() se usa para encriptar la contraseña del usuario. Esto también es algo muy importante para recordar. Nunca inserte una contraseña simple tal cual. DEBE siempre encriptarla. Imagine un hacker que de alguna manera logra acceder a su base de datos. Si ve todas las contraseñas de texto sin formato, puede iniciar sesión en cualquier cuenta (administrativa) que desee. Si las columnas de contraseña contienen cadenas sha1, tiene que descifrarlas primero, lo que es casi imposible.
Nota: también es posible usar md5(), siempre uso sha1() porque los puntos de referencia han demostrado que es un poco más rápido, aunque no demasiado. Puede reemplazar sha1 con md5 si lo desea.
Si el proceso de registro fue exitoso, debería ver algo como esto:



Intente actualizar su pantalla phpMyAdmin, un nuevo registro debería ser visible en la tabla de usuarios.
Paso 6: Agregar Autenticación y Niveles de Usuario
Un aspecto importante de un foro es la diferencia entre los usuarios habituales y los administradores/moderadores. Dado que este es un foro pequeño y adicionar funciones como agregar nuevos moderadores y cosas así tomaría demasiado tiempo, nos enfocaremos en el proceso de inicio de sesión y crearemos algunas funciones de administración, como crear nuevas categorías y cerrar un hilo.
Ahora que ha completado el paso anterior, vamos a hacer que su cuenta recién creada sea una cuenta de administrador. En phpMyAdmin, haga clic en la tabla de usuarios y luego en 'Navegar'. Su cuenta probablemente aparecerá de inmediato. Haga clic en el icono de edición y cambie el valor del campo user_level de 0 a 1. Eso es todo por ahora. No notará ninguna diferencia en nuestra aplicación de inmediato, pero cuando agreguemos las características de administración una cuenta normal y su cuenta tendrán diferentes capacidades.
El proceso de inicio de sesión funciona de la siguiente manera:
- Un visitante ingresa los datos del usuario y envía el formulario
- Si el nombre de usuario y la contraseña son correctos, podemos iniciar una sesión
- Si el nombre de usuario y la contraseña son incorrectos, mostramos el formulario nuevamente con un mensaje



El archivo signin.php está debajo. No crea que no estoy explicando lo que estoy haciendo, pero revise los comentarios en el archivo. Es mucho más fácil de entender de esa manera.
1 |
<?php
|
2 |
//signin.php
|
3 |
include 'connect.php'; |
4 |
include 'header.php'; |
5 |
|
6 |
echo '<h3>Sign in</h3>'; |
7 |
|
8 |
//first, check if the user is already signed in. If that is the case, there is no need to display this page
|
9 |
if(isset($_SESSION['signed_in']) && $_SESSION['signed_in'] == true) |
10 |
{
|
11 |
echo 'You are already signed in, you can <a href="signout.php">sign out</a> if you want.'; |
12 |
}
|
13 |
else
|
14 |
{
|
15 |
if($_SERVER['REQUEST_METHOD'] != 'POST') |
16 |
{
|
17 |
/*the form hasn't been posted yet, display it
|
18 |
note that the action="" will cause the form to post to the same page it is on */
|
19 |
echo '<form method="post" action=""> |
20 |
Username: <input type="text" name="user_name" />
|
21 |
Password: <input type="password" name="user_pass">
|
22 |
<input type="submit" value="Sign in" />
|
23 |
</form>'; |
24 |
}
|
25 |
else
|
26 |
{
|
27 |
/* so, the form has been posted, we'll process the data in three steps:
|
28 |
1. Check the data
|
29 |
2. Let the user refill the wrong fields (if necessary)
|
30 |
3. Varify if the data is correct and return the correct response
|
31 |
*/
|
32 |
$errors = array(); /* declare the array for later use */ |
33 |
|
34 |
if(!isset($_POST['user_name'])) |
35 |
{
|
36 |
$errors[] = 'The username field must not be empty.'; |
37 |
}
|
38 |
|
39 |
if(!isset($_POST['user_pass'])) |
40 |
{
|
41 |
$errors[] = 'The password field must not be empty.'; |
42 |
}
|
43 |
|
44 |
if(!empty($errors)) /*check for an empty array, if there are errors, they're in this array (note the ! operator)*/ |
45 |
{
|
46 |
echo 'Uh-oh.. a couple of fields are not filled in correctly..'; |
47 |
echo '<ul>'; |
48 |
foreach($errors as $key => $value) /* walk through the array so all the errors get displayed */ |
49 |
{
|
50 |
echo '<li>' . $value . '</li>'; /* this generates a nice error list */ |
51 |
}
|
52 |
echo '</ul>'; |
53 |
}
|
54 |
else
|
55 |
{
|
56 |
//the form has been posted without errors, so save it
|
57 |
//notice the use of mysql_real_escape_string, keep everything safe!
|
58 |
//also notice the sha1 function which hashes the password
|
59 |
$sql = "SELECT |
60 |
user_id,
|
61 |
user_name,
|
62 |
user_level
|
63 |
FROM
|
64 |
users
|
65 |
WHERE
|
66 |
user_name = '" . mysql_real_escape_string($_POST['user_name']) . "' |
67 |
AND
|
68 |
user_pass = '" . sha1($_POST['user_pass']) . "'"; |
69 |
|
70 |
$result = mysql_query($sql); |
71 |
if(!$result) |
72 |
{
|
73 |
//something went wrong, display the error
|
74 |
echo 'Something went wrong while signing in. Please try again later.'; |
75 |
//echo mysql_error(); //debugging purposes, uncomment when needed
|
76 |
}
|
77 |
else
|
78 |
{
|
79 |
//the query was successfully executed, there are 2 possibilities
|
80 |
//1. the query returned data, the user can be signed in
|
81 |
//2. the query returned an empty result set, the credentials were wrong
|
82 |
if(mysql_num_rows($result) == 0) |
83 |
{
|
84 |
echo 'You have supplied a wrong user/password combination. Please try again.'; |
85 |
}
|
86 |
else
|
87 |
{
|
88 |
//set the $_SESSION['signed_in'] variable to TRUE
|
89 |
$_SESSION['signed_in'] = true; |
90 |
|
91 |
//we also put the user_id and user_name values in the $_SESSION, so we can use it at various pages
|
92 |
while($row = mysql_fetch_assoc($result)) |
93 |
{
|
94 |
$_SESSION['user_id'] = $row['user_id']; |
95 |
$_SESSION['user_name'] = $row['user_name']; |
96 |
$_SESSION['user_level'] = $row['user_level']; |
97 |
}
|
98 |
|
99 |
echo 'Welcome, ' . $_SESSION['user_name'] . '. <a href="index.php">Proceed to the forum overview</a>.'; |
100 |
}
|
101 |
}
|
102 |
}
|
103 |
}
|
104 |
}
|
105 |
|
106 |
include 'footer.php'; |
107 |
?>
|
Esta es la consulta que está en el archivo signin.php:
1 |
SELECT
|
2 |
user_id, |
3 |
user_name, |
4 |
user_level
|
5 |
FROM
|
6 |
users
|
7 |
WHERE
|
8 |
user_name = '" . mysql_real_escape_string($_POST['user_name']) . "' |
9 |
AND
|
10 |
user_pass = '" . sha1($_POST['user_pass']) |
Es obvio que necesitamos una verificación para saber si las credenciales suministradas pertenecen a un usuario existente. Muchos scripts recuperan la contraseña de la base de datos y la comparan usando PHP. Si hacemos esto directamente a través de SQL, la contraseña será almacenada en la base de datos una vez durante el registro y nunca más la abandonará. Esto es más seguro, porque toda la acción real ocurre en la capa de la base de datos y no en nuestra aplicación.
Si el usuario inició sesión con éxito, estamos haciendo algunas cosas:
1 |
<?php
|
2 |
//set the $_SESSION['signed_in'] variable to TRUE
|
3 |
$_SESSION['signed_in'] = true; |
4 |
//we also put the user_id and user_name values in the $_SESSION, so we can use it at various pages
|
5 |
while($row = mysql_fetch_assoc($result)) |
6 |
{
|
7 |
$_SESSION['user_id'] = $row['user_id']; |
8 |
$_SESSION['user_name'] = $row['user_name']; |
9 |
}
|
10 |
?>
|
Primero, establecemos la variable 'signed_in' $_SESSION a true, para que podamos usarla en otras páginas para asegurarnos de que el usuario haya iniciado sesión. También colocamos el nombre de usuario y el ID de usuario en la variable $_SESSION para usar en otra página. Finalmente, mostramos un enlace a la descripción general del foro para que el usuario pueda comenzar de inmediato.
Por supuesto, iniciar sesión requiere otra función, ¡cerrar sesión! El proceso de cierre de sesión es en realidad mucho más fácil que el proceso de inicio de sesión. Debido a que toda la información sobre el usuario se almacena en las variables $_SESSION, todo lo que tenemos que hacer es deshacerlas y mostrar un mensaje.
Ahora que hemos establecido las variables $_SESSION, podemos determinar si alguien ha iniciado sesión. Hagamos un último cambio simple en header.php:
Reemplazar:
1 |
<div id="userbar">Hello Example. Not you? Log out.</div> |
Con:
1 |
<?php
|
2 |
<div id="userbar"> |
3 |
if($_SESSION['signed_in']) |
4 |
{
|
5 |
echo 'Hello' . $_SESSION['user_name'] . '. Not you? <a href="signout.php">Sign out</a>'; |
6 |
}
|
7 |
else
|
8 |
{
|
9 |
echo '<a href="signin.php">Sign in</a> or <a href="sign up">create an account</a>.'; |
10 |
}
|
11 |
</div> |
Si un usuario inicia sesión, verá su nombre en la página principal con un enlace a la página de cierre de sesión. ¡Nuestra autenticación está hecha! Por ahora nuestro foro debería verse así:



Paso 7: Creando una Categoría
Queremos crear categorías así que comencemos con hacer un formulario.
1 |
<form method="post" action=""> |
2 |
Category name: <input type="text" name="cat_name" /> |
3 |
Category description: <textarea name="cat_description" /></textarea> |
4 |
<input type="submit" value="Add category" /> |
5 |
</form>
|
Este paso se parece mucho al Paso 4 (Registrar un usuario), así que no voy a hacer una explicación detallada aquí. Si siguió todos los pasos, debería ser capaz de entender esto de manera bastante rápida.
1 |
<?php
|
2 |
//create_cat.php
|
3 |
include 'connect.php'; |
4 |
|
5 |
if($_SERVER['REQUEST_METHOD'] != 'POST') |
6 |
{
|
7 |
//the form hasn't been posted yet, display it
|
8 |
echo '<form method='post' action=''> |
9 |
Category name: <input type='text' name='cat_name' /> |
10 |
Category description: <textarea name='cat_description' /></textarea> |
11 |
<input type='submit' value='Add category' /> |
12 |
</form>'; |
13 |
}
|
14 |
else
|
15 |
{
|
16 |
//the form has been posted, so save it
|
17 |
$sql = ìINSERT INTO categories(cat_name, cat_description) |
18 |
VALUES('' . mysql_real_escape_string($_POST['cat_name']) . ì', |
19 |
'' . mysql_real_escape_string($_POST['cat_description']) . ì')'; |
20 |
$result = mysql_query($sql);
|
21 |
if(!$result)
|
22 |
{
|
23 |
//something went wrong, display the error
|
24 |
echo 'Error' . mysql_error(); |
25 |
}
|
26 |
else
|
27 |
{
|
28 |
echo 'New category successfully added.'; |
29 |
}
|
30 |
}
|
31 |
?>
|
Como puede ver, hemos iniciado el script con la comprobación $_SERVER, después de verificar si el usuario tiene derechos de administrador, lo cual es necesario para crear una categoría. El formulario se muestra si no se ha enviado ya. Si ha sido enviado, los valores se guardan. Una vez más, se prepara una consulta SQL y luego se ejecuta.



Paso 8: Agregar Categorías a index.php
Hemos creado algunas categorías, por lo que ahora podemos mostrarlas en la página principal. Agreguemos la siguiente consulta al área de contenido de index.php.
1 |
SELECT
|
2 |
categories.cat_id, |
3 |
categories.cat_name, |
4 |
categories.cat_description, |
5 |
FROM
|
6 |
categories
|
Esta consulta selecciona todas las categorías y sus nombres y descripciones de la tabla de categorías. Solo necesitamos un poco de PHP para mostrar los resultados. Si agregamos esa parte al igual que hicimos en los pasos anteriores, el código se verá así.
1 |
<?php
|
2 |
//create_cat.php
|
3 |
include 'connect.php'; |
4 |
include 'header.php'; |
5 |
|
6 |
$sql = "SELECT |
7 |
cat_id,
|
8 |
cat_name,
|
9 |
cat_description,
|
10 |
FROM
|
11 |
categories"; |
12 |
|
13 |
$result = mysql_query($sql); |
14 |
|
15 |
if(!$result) |
16 |
{
|
17 |
echo 'The categories could not be displayed, please try again later.'; |
18 |
}
|
19 |
else
|
20 |
{
|
21 |
if(mysql_num_rows($result) == 0) |
22 |
{
|
23 |
echo 'No categories defined yet.'; |
24 |
}
|
25 |
else
|
26 |
{
|
27 |
//prepare the table
|
28 |
echo '<table border="1"> |
29 |
<tr>
|
30 |
<th>Category</th>
|
31 |
<th>Last topic</th>
|
32 |
</tr>'; |
33 |
|
34 |
while($row = mysql_fetch_assoc($result)) |
35 |
{
|
36 |
echo '<tr>'; |
37 |
echo '<td class="leftpart">'; |
38 |
echo '<h3><a href="category.php?id">' . $row['cat_name'] . '</a></h3>' . $row['cat_description']; |
39 |
echo '</td>'; |
40 |
echo '<td class="rightpart">'; |
41 |
echo '<a href="topic.php?id=">Topic subject</a> at 10-10'; |
42 |
echo '</td>'; |
43 |
echo '</tr>'; |
44 |
}
|
45 |
}
|
46 |
}
|
47 |
|
48 |
include 'footer.php'; |
49 |
?>
|
Observe cómo estamos usando cat_id para crear enlaces a category.php. Todos los enlaces a esta página se verán así: category.php?cat_id=x, donde x puede ser cualquier valor numérico. Esto puede ser nuevo para usted. Podemos verificar la URL con PHP por valores $ _GET. Por ejemplo, tenemos este enlace:
1 |
category.php?cat_id=23 |
La instrucción echo $ _GET[ëcat_id '];' mostrará '23'. En los próximos pasos, utilizaremos este valor para recuperar los temas cuando visualicemos una sola categoría, pero los temas no se pueden ver si aún no los hemos creado. ¡Así que vamos a crear algunos temas!
Paso 9: Creando un Tema
En este paso, combinaremos las técnicas que aprendimos en los pasos anteriores. Estamos comprobando si un usuario ha iniciado sesión, usaremos una consulta de entrada para crear el tema y crear algunos formularios HTML básicos.
La estructura de create_topic.php difícilmente se puede explicar en una lista o algo así, así que la reescribí en pseudo-código.
1 |
<?php
|
2 |
if(user is signed in) |
3 |
{
|
4 |
//the user is not signed in
|
5 |
}
|
6 |
else
|
7 |
{
|
8 |
//the user is signed in
|
9 |
if(form has not been posted) |
10 |
{
|
11 |
//show form
|
12 |
}
|
13 |
else
|
14 |
{
|
15 |
//process form
|
16 |
}
|
17 |
}
|
18 |
?>
|
Aquí está el código real de esta parte de nuestro foro, revise las explicaciones debajo del código para ver lo que está haciendo.
1 |
<?php
|
2 |
//create_cat.php
|
3 |
include 'connect.php'; |
4 |
include 'header.php'; |
5 |
|
6 |
echo '<h2>Create a topic</h2>'; |
7 |
if($_SESSION['signed_in'] == false) |
8 |
{
|
9 |
//the user is not signed in
|
10 |
echo 'Sorry, you have to be <a href="/forum/signin.php">signed in</a> to create a topic.'; |
11 |
}
|
12 |
else
|
13 |
{
|
14 |
//the user is signed in
|
15 |
if($_SERVER['REQUEST_METHOD'] != 'POST') |
16 |
{
|
17 |
//the form hasn't been posted yet, display it
|
18 |
//retrieve the categories from the database for use in the dropdown
|
19 |
$sql = "SELECT |
20 |
cat_id,
|
21 |
cat_name,
|
22 |
cat_description
|
23 |
FROM
|
24 |
categories"; |
25 |
|
26 |
$result = mysql_query($sql); |
27 |
|
28 |
if(!$result) |
29 |
{
|
30 |
//the query failed, uh-oh :-(
|
31 |
echo 'Error while selecting from database. Please try again later.'; |
32 |
}
|
33 |
else
|
34 |
{
|
35 |
if(mysql_num_rows($result) == 0) |
36 |
{
|
37 |
//there are no categories, so a topic can't be posted
|
38 |
if($_SESSION['user_level'] == 1) |
39 |
{
|
40 |
echo 'You have not created categories yet.'; |
41 |
}
|
42 |
else
|
43 |
{
|
44 |
echo 'Before you can post a topic, you must wait for an admin to create some categories.'; |
45 |
}
|
46 |
}
|
47 |
else
|
48 |
{
|
49 |
|
50 |
echo '<form method="post" action=""> |
51 |
Subject: <input type="text" name="topic_subject" />
|
52 |
Category:'; |
53 |
|
54 |
echo '<select name="topic_cat">'; |
55 |
while($row = mysql_fetch_assoc($result)) |
56 |
{
|
57 |
echo '<option value="' . $row['cat_id'] . '">' . $row['cat_name'] . '</option>'; |
58 |
}
|
59 |
echo '</select>'; |
60 |
|
61 |
echo 'Message: <textarea name="post_content" /></textarea> |
62 |
<input type="submit" value="Create topic" />
|
63 |
</form>'; |
64 |
}
|
65 |
}
|
66 |
}
|
67 |
else
|
68 |
{
|
69 |
//start the transaction
|
70 |
$query = "BEGIN WORK;"; |
71 |
$result = mysql_query($query); |
72 |
|
73 |
if(!$result) |
74 |
{
|
75 |
//Damn! the query failed, quit
|
76 |
echo 'An error occured while creating your topic. Please try again later.'; |
77 |
}
|
78 |
else
|
79 |
{
|
80 |
|
81 |
//the form has been posted, so save it
|
82 |
//insert the topic into the topics table first, then we'll save the post into the posts table
|
83 |
$sql = "INSERT INTO |
84 |
topics(topic_subject,
|
85 |
topic_date,
|
86 |
topic_cat,
|
87 |
topic_by)
|
88 |
VALUES('" . mysql_real_escape_string($_POST['topic_subject']) . "', |
89 |
NOW(),
|
90 |
" . mysql_real_escape_string($_POST['topic_cat']) . ", |
91 |
" . $_SESSION['user_id'] . " |
92 |
)"; |
93 |
|
94 |
$result = mysql_query($sql); |
95 |
if(!$result) |
96 |
{
|
97 |
//something went wrong, display the error
|
98 |
echo 'An error occured while inserting your data. Please try again later.' . mysql_error(); |
99 |
$sql = "ROLLBACK;"; |
100 |
$result = mysql_query($sql); |
101 |
}
|
102 |
else
|
103 |
{
|
104 |
//the first query worked, now start the second, posts query
|
105 |
//retrieve the id of the freshly created topic for usage in the posts query
|
106 |
$topicid = mysql_insert_id(); |
107 |
|
108 |
$sql = "INSERT INTO |
109 |
posts(post_content,
|
110 |
post_date,
|
111 |
post_topic,
|
112 |
post_by)
|
113 |
VALUES
|
114 |
('" . mysql_real_escape_string($_POST['post_content']) . "', |
115 |
NOW(),
|
116 |
" . $topicid . ", |
117 |
" . $_SESSION['user_id'] . " |
118 |
)"; |
119 |
$result = mysql_query($sql); |
120 |
|
121 |
if(!$result) |
122 |
{
|
123 |
//something went wrong, display the error
|
124 |
echo 'An error occured while inserting your post. Please try again later.' . mysql_error(); |
125 |
$sql = "ROLLBACK;"; |
126 |
$result = mysql_query($sql); |
127 |
}
|
128 |
else
|
129 |
{
|
130 |
$sql = "COMMIT;"; |
131 |
$result = mysql_query($sql); |
132 |
|
133 |
//after a lot of work, the query succeeded!
|
134 |
echo 'You have successfully created <a href="topic.php?id='. $topicid . '">your new topic</a>.'; |
135 |
}
|
136 |
}
|
137 |
}
|
138 |
}
|
139 |
}
|
140 |
|
141 |
include 'footer.php'; |
142 |
?>
|
Discutiré esta página en dos partes, mostrando el formulario y procesando el formulario.
Mostrando el formulario
Estamos comenzando con un formulario HTML simple. En realidad, hay algo especial aquí, porque usamos un menú desplegable. Este menú desplegable se llena con datos de la base de datos, usando esta consulta:
1 |
SELECT
|
2 |
cat_id, |
3 |
cat_name, |
4 |
cat_description
|
5 |
FROM
|
6 |
categories
|
Esa es la única parte potencialmente confusa aquí; es un buen código, como puede ver al mirar el archivo create_topic.php en la parte inferior de este paso.
Procesando el formulario
El proceso para guardar el tema consta de dos partes: guardar el tema en la tabla de temas y guardar la primera publicación en la tabla de publicaciones. Esto requiere algo bastante avanzado que va un poco más allá del alcance de este tutorial. Se llama transacción, lo que básicamente significa que comenzamos ejecutando el comando de inicio y luego retrocedemos cuando hay errores en la base de datos y ejecutamos cuando todo salió bien. Más sobre transacciones.
1 |
<?php
|
2 |
//start the transaction
|
3 |
$query = "BEGIN WORK;"; |
4 |
$result = mysql_query($query); |
5 |
//stop the transaction
|
6 |
$sql = "ROLLBACK;"; |
7 |
$result = mysql_query($sql); |
8 |
//commit the transaction
|
9 |
$sql = "COMMIT;"; |
10 |
$result = mysql_query($sql); |
11 |
?>
|
La primera consulta que se utiliza para guardar los datos es la consulta de creación de tema, que se ve así:
1 |
INSERT INTO |
2 |
topics(topic_subject, |
3 |
topic_date, |
4 |
topic_cat, |
5 |
topic_by) |
6 |
VALUES('" . mysql_real_escape_string($_POST['topic_subject']) . "', |
7 |
NOW(), |
8 |
" . mysql_real_escape_string($_POST['topic_cat']) . ", |
9 |
" . $_SESSION['user_id'] . ") |
Al principio se definen los campos, luego los valores que se insertarán. Ya vimos el primero, es solo una cadena de texto que se hace segura usando mysql_real_escape_string(). El segundo valor, NOW(), es una función de SQL para la hora actual. El tercer valor, sin embargo, es un valor que no hemos visto antes. Se refiere a una identificación (válida) de una categoría. El último valor se refiere a un user_id (existente) que es, en este caso, el valor de $_SESSION [ëuser_id ']. Esta variable se declaró durante el proceso de inicio de sesión.
Si la consulta se ejecuta sin errores procedemos a la segunda consulta. Recuerde que todavía estamos haciendo una transacción aquí. Si hubiéramos recibido errores, habríamos usado el comando ROLLBACK.
1 |
INSERT INTO |
2 |
posts(post_content, |
3 |
post_date, |
4 |
post_topic, |
5 |
post_by) |
6 |
VALUES
|
7 |
('" . mysql_real_escape_string($_POST['post_content']) . "', |
8 |
NOW(), |
9 |
" . $topicid . ", |
10 |
" . $_SESSION['user_id'] . ") |
Lo primero que hacemos en este código es usar mysql_insert_id() para recuperar la última ID generada del campo topic_id en la tabla de temas. Como puede recordar en los primeros pasos de este tutorial, el ID se genera en la base de datos usando auto_increment.
Luego, la publicación se inserta en la tabla de publicaciones. Esta consulta se parece mucho a la consulta de temas. La única diferencia es que esta publicación se refiere al tema y al tema referido a una categoría. Desde el principio, decidimos crear un buen modelo de datos y aquí está el resultado: una buena estructura jerárquica.



Paso 10: Vista de Categoría
Vamos a hacer una página de resumen para una sola categoría. Acabamos de crear una categoría, sería útil poder ver todos los temas en ella. Primero, cree una página llamada category.php.
Una breve lista de las cosas que necesitamos:
Necesario para mostrar la categoría
- cat_name
- cat_description
Necesario para mostrar todos los temas
- topic_id
- topic_subject
- topic_date
- topic_cat
Vamos a crear las dos consultas SQL que recuperan exactamente estos datos de la base de datos.
1 |
SELECT
|
2 |
cat_id, |
3 |
cat_name, |
4 |
cat_description
|
5 |
FROM
|
6 |
categories
|
7 |
WHERE
|
8 |
cat_id = " . mysql_real_escape_string($_GET['id']) |
La consulta anterior selecciona todas las categorías de la base de datos.
1 |
SELECT
|
2 |
topic_id, |
3 |
topic_subject, |
4 |
topic_date, |
5 |
topic_cat
|
6 |
FROM
|
7 |
topics
|
8 |
WHERE
|
9 |
topic_cat = " . mysql_real_escape_string($_GET['id']) |
La consulta anterior se ejecuta en el bucle while en el que hacemos echo a las categorías. Al hacerlo de esta manera, veremos todas las categorías y el último tema para cada uno de ellas.
El código completo de category.php será el siguiente:
1 |
<?php
|
2 |
//create_cat.php
|
3 |
include 'connect.php'; |
4 |
include 'header.php'; |
5 |
|
6 |
//first select the category based on $_GET['cat_id']
|
7 |
$sql = "SELECT |
8 |
cat_id,
|
9 |
cat_name,
|
10 |
cat_description
|
11 |
FROM
|
12 |
categories
|
13 |
WHERE
|
14 |
cat_id = " . mysql_real_escape_string($_GET['id']); |
15 |
|
16 |
$result = mysql_query($sql); |
17 |
|
18 |
if(!$result) |
19 |
{
|
20 |
echo 'The category could not be displayed, please try again later.' . mysql_error(); |
21 |
}
|
22 |
else
|
23 |
{
|
24 |
if(mysql_num_rows($result) == 0) |
25 |
{
|
26 |
echo 'This category does not exist.'; |
27 |
}
|
28 |
else
|
29 |
{
|
30 |
//display category data
|
31 |
while($row = mysql_fetch_assoc($result)) |
32 |
{
|
33 |
echo '<h2>Topics in ′' . $row['cat_name'] . '′ category</h2>'; |
34 |
}
|
35 |
|
36 |
//do a query for the topics
|
37 |
$sql = "SELECT |
38 |
topic_id,
|
39 |
topic_subject,
|
40 |
topic_date,
|
41 |
topic_cat
|
42 |
FROM
|
43 |
topics
|
44 |
WHERE
|
45 |
topic_cat = " . mysql_real_escape_string($_GET['id']); |
46 |
|
47 |
$result = mysql_query($sql); |
48 |
|
49 |
if(!$result) |
50 |
{
|
51 |
echo 'The topics could not be displayed, please try again later.'; |
52 |
}
|
53 |
else
|
54 |
{
|
55 |
if(mysql_num_rows($result) == 0) |
56 |
{
|
57 |
echo 'There are no topics in this category yet.'; |
58 |
}
|
59 |
else
|
60 |
{
|
61 |
//prepare the table
|
62 |
echo '<table border="1"> |
63 |
<tr>
|
64 |
<th>Topic</th>
|
65 |
<th>Created at</th>
|
66 |
</tr>'; |
67 |
|
68 |
while($row = mysql_fetch_assoc($result)) |
69 |
{
|
70 |
echo '<tr>'; |
71 |
echo '<td class="leftpart">'; |
72 |
echo '<h3><a href="topic.php?id=' . $row['topic_id'] . '">' . $row['topic_subject'] . '</a><h3>'; |
73 |
echo '</td>'; |
74 |
echo '<td class="rightpart">'; |
75 |
echo date('d-m-Y', strtotime($row['topic_date'])); |
76 |
echo '</td>'; |
77 |
echo '</tr>'; |
78 |
}
|
79 |
}
|
80 |
}
|
81 |
}
|
82 |
}
|
83 |
|
84 |
include 'footer.php'; |
85 |
?>
|
Y aquí está el resultado final de nuestra página de categorías:



Paso 11: Vista de Temas
Las consultas SQL en este paso son complicadas. La parte PHP es todo lo que ya ha visto antes. Echemos un vistazo a las consultas. La primera recupera información básica sobre el tema:
1 |
SELECT
|
2 |
topic_id, |
3 |
topic_subject
|
4 |
FROM
|
5 |
topics
|
6 |
WHERE
|
7 |
topics.topic_id = " . mysql_real_escape_string($_GET['id']) |
Esta información se muestra en el encabezado de la tabla que usaremos para mostrar todos los datos. A continuación, recuperamos todas las publicaciones de este tema de la base de datos. La siguiente consulta nos proporciona exactamente lo que necesitamos:
1 |
SELECT
|
2 |
posts.post_topic, |
3 |
posts.post_content, |
4 |
posts.post_date, |
5 |
posts.post_by, |
6 |
users.user_id, |
7 |
users.user_name |
8 |
FROM
|
9 |
posts
|
10 |
LEFT JOIN |
11 |
users
|
12 |
ON
|
13 |
posts.post_by = users.user_id |
14 |
WHERE
|
15 |
posts.post_topic = " . mysql_real_escape_string($_GET['id']) |
Esta vez, queremos información de los usuarios y de la tabla de publicaciones, así que usamos LEFT JOIN de nuevo. La condición es: la identificación del usuario debe ser la misma que el campo post_by. De esta forma, podemos mostrar el nombre de usuario del usuario que respondió en cada publicación.
La vista final del tema se ve así:



Paso 12: Agregar una Respuesta
Vamos a crear la última parte que falta de este foro, la posibilidad de agregar una respuesta. Comenzaremos por crear un formulario:
1 |
<form method="post" action="reply.php?id=5"> |
2 |
<textarea name="reply-content"></textarea> |
3 |
<input type="submit" value="Submit reply" /> |
4 |
</form>
|



El código de reply.php completo se ve así.
1 |
<?php
|
2 |
//create_cat.php
|
3 |
include 'connect.php'; |
4 |
include 'header.php'; |
5 |
|
6 |
if($_SERVER['REQUEST_METHOD'] != 'POST') |
7 |
{
|
8 |
//someone is calling the file directly, which we don't want
|
9 |
echo 'This file cannot be called directly.'; |
10 |
}
|
11 |
else
|
12 |
{
|
13 |
//check for sign in status
|
14 |
if(!$_SESSION['signed_in']) |
15 |
{
|
16 |
echo 'You must be signed in to post a reply.'; |
17 |
}
|
18 |
else
|
19 |
{
|
20 |
//a real user posted a real reply
|
21 |
$sql = "INSERT INTO |
22 |
posts(post_content,
|
23 |
post_date,
|
24 |
post_topic,
|
25 |
post_by)
|
26 |
VALUES ('" . $_POST['reply-content'] . "', |
27 |
NOW(),
|
28 |
" . mysql_real_escape_string($_GET['id']) . ", |
29 |
" . $_SESSION['user_id'] . ")"; |
30 |
|
31 |
$result = mysql_query($sql); |
32 |
|
33 |
if(!$result) |
34 |
{
|
35 |
echo 'Your reply has not been saved, please try again later.'; |
36 |
}
|
37 |
else
|
38 |
{
|
39 |
echo 'Your reply has been saved, check out <a href="topic.php?id=' . htmlentities($_GET['id']) . '">the topic</a>.'; |
40 |
}
|
41 |
}
|
42 |
}
|
43 |
|
44 |
include 'footer.php'; |
45 |
?>
|
Los comentarios en el código detallan bastante lo que está sucediendo. Estamos buscando un usuario real y luego insertando la publicación en la base de datos.



Terminando
Ahora que ha terminado este tutorial, debe tener una comprensión mucho mejor de lo que se necesita para construir un foro. ¡Espero que mis explicaciones sean lo suficientemente claras! Gracias de nuevo por leer.



