1. Code
  2. Coding Fundamentals
  3. Databases & SQL

Cómo Crea un Foro Potenciado por PHP/MySQL desde Cero

Scroll to top

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.