0% found this document useful (0 votes)
95 views35 pages

Forum PHP Mysql

This document provides instructions for creating a PHP/MySQL powered forum from scratch in multiple steps. It begins by describing the database tables needed - Users, Categories, Topics, and Posts - and provides the SQL code to create these tables. It then explains the relationships between the tables and adds foreign keys. Finally, it introduces a header and footer system to include basic markup on all forum pages.

Uploaded by

Ion Ion
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
95 views35 pages

Forum PHP Mysql

This document provides instructions for creating a PHP/MySQL powered forum from scratch in multiple steps. It begins by describing the database tables needed - Users, Categories, Topics, and Posts - and provides the SQL code to create these tables. It then explains the relationships between the tables and adds foreign keys. Finally, it introduces a header and footer system to include basic markup on all forum pages.

Uploaded by

Ion Ion
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
You are on page 1/ 35

How to Create a PHP/MySQL Powered

In this tutorial, we're going to build a PHP/MySQL powered forum from scratch. This tutorial is
perfect for getting used to basic PHP and database usage. Let's dive right in!

Step 1: Creating Database Tables


It's always a good idea to start with creating a good data model when building an application.
Let's describe our application in one sentence: We are going to make a forum which has users
who create topics in various categories. Other users can post replies. As you can see, I
highlighted a couple of nouns which represent our table names.

Users

 Categories
 Topics
 Posts

These three objects are related to each other, so we'll process that in our table design. Take a look
at the scheme below.
Looks pretty neat, huh? Every square is a database table. All the columns are listed in it and the
lines between them represent the relationships. I'll explain them further, so it's okay if it doesn't
make a lot of sense to you right now.

I'll discuss each table by explaining the SQL, which I created using the scheme above. For your
own scripts you can create a similar scheme and SQL too. Some editors like MySQL Workbench
(the one I used) can generate .sql files too, but I would recommend learning SQL because it's
more fun to do it yourself. A SQL introduction can be found at W3Schools.

Users Table

01
02CREATE TABLE users (
user_id INT(8) NOT NULL AUTO_INCREMENT,
03user_name VARCHAR(30) NOT NULL,
04user_pass VARCHAR(255) NOT NULL,
05user_email VARCHAR(255) NOT NULL,
06user_date DATETIME NOT NULL,
user_level INT(8) NOT NULL,
07UNIQUE INDEX user_name_unique (user_name),
08PRIMARY KEY (user_id)
09) TYPE=INNODB;
10

The CREATE TABLE statement is used to indicate we want to create a new table, of course. The
statement is followed by the name of the table and all the columns are listed between the
brackets. The names of all the fields are self-explanatory, so we'll only discuss the data types
below.

user_id

"A primary key is used to uniquely identify each row in a table."

The type of this field is INT, which means this field holds an integer. The field cannot be empty
(NOT NULL) and increments which each record inserted. At the bottom of the table you can see
the user_id field is declared as a primary key. A primary key is used to uniquely identify each
row in a table. No two distinct rows in a table can have the same value (or combination of
values) in all columns. That might be a bit unclear, so here's a little example.

There is a user called John Doe. If another users registers with the same name, there's a problem,
because: which user is which? You can't tell and the database can't tell either. By using a primary
key this problem is solved, because both topics are unique.

All the other tables have got primary keys too and they work the same way.

user_name

This is a text field, called a VARCHAR field in MySQL. The number between brackets is the
maximum length. A user can choose a username up to 30 characters long. This field cannot be
NULL. At the bottom of the table you can see this field is declared UNIQUE, which means the
same username cannot be registered twice. The UNIQUE INDEX part tells the database we want
to add a unique key. Then we define the name of the unique key, user_name_unique in this case.
Between brackets is the field the unique key applies to, which is user_name.

user_pass

This field is equal to the user_name field, except the maximum length. Since the user password,
no matter what length, is hashed with sha1(), the password will always be 40 characters long.

user_email

This field is equal to the user_pass field.

user_date

This is a field in which we'll store the date the user registered. It's type is DATETIME and the
field cannot be NULL.

user_level

This field contains the level of the user, for example: '0' for a regular user and '1' for an admin.
More about this later.

Categories Table

1CREATE TABLE categories (


2cat_id INT(8) NOT NULL AUTO_INCREMENT,
3cat_name VARCHAR(255) NOT NULL,
4 cat_description VARCHAR(255) NOT NULL,
5 UNIQUE INDEX cat_name_unique (cat_name),
PRIMARY KEY (cat_id)
6) TYPE=INNODB;
7

These data types basically work the same way as the ones in the users table. This table also has a
primary key and the name of the category must be an unique one.

Topics Table

1
CREATE TABLE topics (
2topic_id INT(8) NOT NULL AUTO_INCREMENT,
3topic_subject VARCHAR(255) NOT NULL,
4topic_date DATETIME NOT NULL,
5topic_cat INT(8) NOT NULL,
topic_by INT(8) NOT NULL,
6
PRIMARY KEY (topic_id)
7) TYPE=INNODB;
8
This table is almost the same as the other tables, except for the topic_by field. That field refers to
the user who created the topic. The topic_cat refers to the category the topic belongs to. We
cannot force these relationships by just declaring the field. We have to let the database know this
field must contain an existing user_id from the users table, or a valid cat_id from the categories
table. We'll add some relationships after I've discussed the posts table.

Posts Table

1
CREATE TABLE posts (
2post_id INT(8) NOT NULL AUTO_INCREMENT,
3post_content TEXT NOT NULL,
4post_date DATETIME NOT NULL,
5post_topic INT(8) NOT NULL,
post_by INT(8) NOT NULL,
6PRIMARY KEY (post_id)
7) TYPE=INNODB;
8

This is the same as the rest of the tables; there's also a field which refers to a user_id here: the
post_by field. The post_topic field refers to the topic the post belongs to.

"A foreign key is a referential constraint between two tables. The foreign key identifies a column
or a set of columns in one (referencing) table that refers to a column or set of columns in another
(referenced) table."

Now that we've executed these queries, we have a pretty decent data model, but the relations are
still missing. Let's start with the definition of a relationship. We're going to use something called
a foreign key. A foreign key is a referential constraint between two tables. The foreign key
identifies a column or a set of columns in one (referencing) table that refers to a column or set of
columns in another (referenced) table. Some conditions:

 The column in the referencing table the foreign key refers to must be a primary key
 The values that are referred to must exist in the referenced table

By adding foreign keys the information is linked together which is very important for database
normalization. Now you know what a foreign key is and why we're using them. It's time to add
them to the tables we've already made by using the ALTER statement, which can be used to
change an already existing table.

We'll link the topics to the categories first:

ALTER TABLE topics ADD FOREIGN KEY(topic_cat) REFERENCES categories(cat_id) ON


1DELETE CASCADE ON UPDATE CASCADE;

The last part of the query already says what happens. When a category gets deleted from the
database, all the topics will be deleted too. If the cat_id of a category changes, every topic will be
updated too. That's what the ON UPDATE CASCADE part is for. Of course, you can reverse this
to protect your data, so that you can't delete a category as long as it still has topics linked to it. If
you would want to do that, you could replace the 'ON DELETE CASCADE' part with 'ON
DELETE RESTRICT'. There is also SET NULL and NO ACTION, which speak for themselves.

Every topic is linked to a category now. Let's link the topics to the user who creates one.

ALTER TABLE topics ADD FOREIGN KEY(topic_by) REFERENCES users(user_id) ON DELETE


1RESTRICT ON UPDATE CASCADE;

This foreign key is the same as the previous one, but there is one difference: the user can't be
deleted as long as there are still topics with the user id of the user. We don't use CASCADE here
because there might be valuable information in our topics. We wouldn't want that information to
get deleted if someone decides to delete their account. To still give users the opportunity to delete
their account, you could build some feature that anonymizes all their topics and then delete their
account. Unfortunately, that is beyond the scope of this tutorial.

Link the posts to the topics:

ALTER TABLE posts ADD FOREIGN KEY(post_topic) REFERENCES topics(topic_id) ON


1DELETE CASCADE ON UPDATE CASCADE;

And finally, link each post to the user who made it:

ALTER TABLE posts ADD FOREIGN KEY(post_by) REFERENCES users(user_id) ON DELETE


1RESTRICT ON UPDATE CASCADE;

That's the database part! It was quite a lot of work, but the result, a great data model, is definitely
worth it.

Step 2: Introduction to the Header/Footer System


Each page of our forum needs a few basic things, like a doctype and some markup. That's why
we'll include a header.php file at the top of each page, and a footer.php at the bottom. The
header.php contains a doctype, a link to the stylesheet and some important information about the
forum, such as the title tag and metatags.

header.php

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"


"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="nl" lang="nl">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="description" content="A short description." />
<meta name="keywords" content="put, keywords, here" />
<title>PHP-MySQL forum</title>
<link rel="stylesheet" href="style.css" type="text/css">
</head>
<body>
<h1>My forum</h1>
<div id="wrapper">
<div id="menu">
<a class="item" href="/forum/index.php">Home</a> -
<a class="item" href="/forum/create_topic.php">Create a topic</a>
-
<a class="item" href="/forum/create_cat.php">Create a category</a>

<div id="userbar">
<div id="userbar">Hello Example. Not you? Log out.</div>
</div>
<div id="content">

The wrapper div will be used to make it easier to style the entire page. The menu div obviously
contains a menu with links to pages we still have to create, but it helps to see where we're going
a little bit. The userbar div is going to be used for a small top bar which contains some
information like the username and a link to the logout page. The content page holds the actual
content of the page, obviously.

The attentive reader might have already noticed we're missing some things. There is no </body>
or </html> tag. They're in the footer.php page, as you can see below.

</div><!-- content -->


</div><!-- wrapper -->
<div id="footer">Created for
Nettuts+</div>
</body>
</html>

When we include a header and a footer on each page the rest of the page get embedded between
the header and the footer. This method has got some advantages. First and foremost, everything
will be styled correctly. A short example:

<?php
$error = false;
if($error = false)
{
//the beautifully styled content, everything looks
good
echo '<div id="content">some text</div>';
}
else
{
//bad looking, unstyled error :-(
}
?>
As you can see, a page without errors will result in a nice page with the content. But if there's an
error, everything looks really ugly; so that's why it's better to make sure not only real content is
styled correctly, but also the errors we might get.

Another advantage is the possibility of making quick changes. You can see for yourself by
editing the text in footer.php when you've finished this tutorial; you'll notice that the footer
changes on every page immediately. Finally, we add a stylesheet which provides us with some
basic markup - nothing too fancy.

body {
background-color: #4E4E4E;
text-align: center; /* make sure IE centers the page too */
}

#wrapper {
width: 900px;
margin: 0 auto; /* center the page */
}

#content {
background-color: #fff;
border: 1px solid #000;
float: left;
font-family: Arial;
padding: 20px 30px;
text-align: left;
width: 100%; /* fill up the entire div */
016}
017
018#menu {
float: left;
019 border: 1px solid #000;
020 border-bottom: none; /* avoid a double border */
021 clear: both; /* clear:both makes sure the content div
022doesn't float next to this one but stays under it */
023 width:100%;
height:20px;
024 padding: 0 30px;
025 background-color: #FFF;
026 text-align: left;
027 font-size: 85%;
028 }
029
#menu a:hover {
030 background-color: #009FC1;
031}
032
033#userbar {
034 background-color: #fff;
float: right;
035 width: 250px;
036}
037
038#footer {
clear: both;
039
}
040
041/* begin table styles */
042table {
043 border-collapse: collapse;
044 width: 100%;
}
045
046table a {
047 color: #000;
048}
049
050table a:hover {
051 color:#373737;
text-decoration: none;
052}
053
054th {
055 background-color: #B40E1F;
056 color: #F0F0F0;
}
057
058td {
059 padding: 5px;
060}
061
062/* Begin font styles */
063h1, #footer {
font-family: Arial;
064 color: #F1F3F1;
065}
066
067h3 {margin: 0; padding: 0;}
068
069/* Menu styles */
070.item {
background-color: #00728B;
071 border: 1px solid #032472;
072 color: #FFF;
073 font-family: Arial;
074 padding: 3px;
text-decoration: none;
075}
076
077.leftpart {
078 width: 70%;
079 }
080
081.rightpart {
width: 30%;
082}
083
084
085
086
087
088
089
090
091
092
.small {
093 font-size: 75%;
094 color: #373737;
095}
096#footer {
font-size: 65%;
097 padding: 3px 0 0 0;
098}
099
100.topic-post {
101 height: 100px;
102 overflow: auto;
}
103
104.post-content {
105 padding: 30px;
106}
107
108textarea {
width: 500px;
109
height: 200px;
110 }
111
112
113
114
115
116
117
118
119

Step 3: Getting Ready for Action


Before we can read anything from our database, we need a connection. That's what connect.php
is for. We'll include it in every file we are going to create.

01<?php
02//connect.php
$server = 'localhost';
03
$username = 'usernamehere';
04
05
06$password = 'passwordhere';
07$database = 'databasenamehere';
08
09if(!mysql_connect($server, $username, $password))
{
10 exit('Error: could not establish database connection');
11 }
12if(!mysql_select_db($database)
13{
exit('Error: could not select the database');
14}
15?>
16

Simply replace the default values of the variables at the top of the page with your own date, save
the file and you're good to go!

Step 4: Displaying the Forum Overview


Since we're just started with some basic techniques, we're going to make a simplified version of
the forum overview for now.

01
02<?php
//create_cat.php
03include 'connect.php';
04include 'header.php';
05
06echo '<tr>';
07 echo '<td class="leftpart">';
echo '<h3><a href="category.php?id=">Category name</a></h3> Category
08description goes here';
09 echo '</td>';
10 echo '<td class="rightpart">';
11 echo '<a href="topic.php?id=">Topic subject</a> at 10-10';
12 echo '</td>';
echo '</tr>';
13include 'footer.php';
14?>
15

There you have it: a nice and clean overview. We'll be updating this page throughout the tutorial
so that it becomes more like the end result, step by step!

Step 5: Signing up a User


Let's start by making a simple HTML form so that a new user can register.
A PHP page is needed to process the form. We're going to use a $_SERVER variable. The
$_SERVER variable is an array with values that are automatically set with each request. One of
the values of the $_SERVER array is 'REQUEST_METHOD'. When a page is requested with
GET, this variable will hold the value 'GET'. When a page is requested via POST, it will hold the
value 'POST'. We can use this value to check if a form has been posted. See the signup.php page
below.

01<?php
02//signup.php
include 'connect.php';
03include 'header.php';
04
05echo '<h3>Sign up</h3>';
06
07if($_SERVER['REQUEST_METHOD'] != 'POST')
08{
/*the form hasn't been posted yet, display it
09 note that the action="" will cause the form to post to the same page
10it is on */
11 echo '<form method="post" action="">
12 Username: <input type="text" name="user_name" />
13 Password: <input type="password" name="user_pass">
Password again: <input type="password" name="user_pass_check">
14 E-mail: <input type="email" name="user_email">
15 <input type="submit" value="Add category" />
16 </form>';
17}
18else
{
19 /* so, the form has been posted, we'll process the data in three steps:
20 1. Check the data
21 2. Let the user refill the wrong fields (if necessary)
22 3. Save the data
*/
23 $errors = array(); /* declare the array for later use */
24
25 if(isset($_POST['user_name']))
26 {
//the user name exists
27 if(!ctype_alnum($_POST['user_name']))
28 {
29 $errors[] = 'The username can only contain letters and digits.';
30 }
if(strlen($_POST['user_name']) > 30)
31 {
32 $errors[] = 'The username cannot be longer than 30 characters.';
33 }
34 }
35 else
{
36 $errors[] = 'The username field must not be empty.';
37 }
38
39
40 if(isset($_POST['user_pass']))
41 {
42 if($_POST['user_pass'] != $_POST['user_pass_check'])
{
43 $errors[] = 'The two passwords did not match.';
44 }
45 }
46 else
{
47
$errors[] = 'The password field cannot be empty.';
48 }
49
50 if(!empty($errors)) /*check for an empty array, if there are errors,
51they're in this array (note the ! operator)*/
52 {
echo 'Uh-oh.. a couple of fields are not filled in correctly..';
53 echo '<ul>';
54 foreach($errors as $key => $value) /* walk through the array so all
55the errors get displayed */
56 {
57 echo '<li>' . $value . '</li>'; /* this generates a nice error
list */
58 }
59 echo '</ul>';
60 }
61 else
{
62
//the form has been posted without, so save it
63 //notice the use of mysql_real_escape_string, keep everything safe!
64 //also notice the sha1 function which hashes the password
65 $sql = "INSERT INTO
66 users(user_name, user_pass, user_email ,user_date,
user_level)
67 VALUES('" . mysql_real_escape_string($_POST['user_name']) .
68"',
69 '" . sha1($_POST['user_pass']) . "',
'" . mysql_real_escape_string($_POST['user_email']) .
70
71
72
73
74"',
75 NOW(),
76 0)";
77
$result = mysql_query($sql);
78
if(!$result)
79 {
80 //something went wrong, display the error
81 echo 'Something went wrong while registering. Please try again
82 later.';
//echo mysql_error(); //debugging purposes, uncomment when
83needed
84 }
85 else
86 {
87 echo 'Successfully registered. You can now <a
href="signin.php">sign in</a> and start posting! :-)';
88 }
89 }
90}
91
92include 'footer.php';
93?>
94
95
96
97

A lot of explanation is in the comments I made in the file, so be sure to check them out. The
processing of the data takes place in three parts:

 Validating the data


 If the data is not valid, show the form again
 If the data is valid, save the record in the database

The PHP part is quite self-explanatory. The SQL-query however probably needs a little more
explanation.

1INSERT INTO
2 users(user_name, user_pass, user_email ,user_date, user_level)
3VALUES('" . mysql_real_escape_string($_POST['user_name']) . "',
4 '" . sha1($_POST['user_pass']) . "',
5 '" . mysql_real_escape_string($_POST['user_email']) . "',
NOW(),
6 0);
7
On line 1 we have the INSERT INTO statement which speaks for itself. The table name is
specified on the second line. The words between the brackets represent the columns in which we
want to insert the data. The VALUES statement tells the database we're done declaring column
names and it's time to specify the values. There is something new here:
mysql_real_escape_string. The function escapes special characters in an unescaped string , so
that it is safe to place it in a query. This function MUST always be used, with very few
exceptions. There are too many scripts that don't use it and can be hacked real easy. Don't take
the risk, use mysql_real_escape_string().

"Never insert a plain password as-is. You MUST always encrypt it."

Also, you can see that the function sha1() is used to encrypt the user's password. This is also a
very important thing to remember. Never insert a plain password as-is. You MUST always
encrypt it. Imagine a hacker who somehow manages to get access to your database. If he sees all
the plain-text passwords he could log into any (admin) account he wants. If the password
columns contain sha1 strings he has to crack them first which is almost impossible.

Note: it's also possible to use md5(), I always use sha1() because benchmarks have proved it's a
tiny bit faster, not much though. You can replace sha1 with md5 if you like.

If the signup process was successful, you should see something like this:

Try refreshing your phpMyAdmin screen, a new record should be visible in the users table.

Step 6: Adding Authentication and User Levels


An important aspect of a forum is the difference between regular users and admins/moderators.
Since this is a small forum and adding features like adding new moderators and stuff would take
way too much time, we'll focus on the login process and create some admin features like creating
new categories and closing a thread.
Now that you've completed the previous step, we're going to make your freshly created account
an admin account. In phpMyAdmin, click on the users table, and then 'Browse'. Your account
will probably pop up right away. Click the edit icon and change the value of the user_level field
from 0 to 1. That's it for now. You won't notice any difference in our application immediately, but
when we've added the admin features a normal account and your account will have different
capabilities.

The sign-in process works the following way:

 A visitor enters user data and submits the form


 If the username and password are correct, we can start a session
 If the username and password are incorrect, we show the form again with a message

The signin.php file is below. Don't think I'm not explaining what I'm doing, but check out the
comments in the file. It's much easier to understand that way.

001<?php
002//signin.php
include 'connect.php';
003include 'header.php';
004
005echo '<h3>Sign in</h3>';
006
007//first, check if the user is already signed in. If that is the case, there
008is no need to display this page
if(isset($_SESSION['signed_in']) && $_SESSION['signed_in'] == true)
009{
010 echo 'You are already signed in, you can <a href="signout.php">sign
011 out</a> if you want.';
012}
013else
{
014 if($_SERVER['REQUEST_METHOD'] != 'POST')
015 {
016 /*the form hasn't been posted yet, display it
017 note that the action="" will cause the form to post to the same
page it is on */
018
019 echo '<form method="post" action="">
Username: <input type="text" name="user_name" />
020
Password: <input type="password" name="user_pass">
021 <input type="submit" value="Sign in" />
022 </form>';
023 }
024 else
{
025 /* so, the form has been posted, we'll process the data in three
026steps:
027 1. Check the data
028 2. Let the user refill the wrong fields (if necessary)
029 3. Varify if the data is correct and return the correct
response
030 */
031 $errors = array(); /* declare the array for later use */
032
033 if(!isset($_POST['user_name']))
034 {
$errors[] = 'The username field must not be empty.';
035
}
036
037 if(!isset($_POST['user_pass']))
038 {
039 $errors[] = 'The password field must not be empty.';
040 }
041
if(!empty($errors)) /*check for an empty array, if there are
042
errors, they're in this array (note the ! operator)*/
043 {
044 echo 'Uh-oh.. a couple of fields are not filled in
045correctly..';
046 echo '<ul>';
foreach($errors as $key => $value) /* walk through the array so
047all the errors get displayed */
048 {
049 echo '<li>' . $value . '</li>'; /* this generates a nice
050error list */
051 }
echo '</ul>';
052 }
053 else
054 {
055 //the form has been posted without errors, so save it
//notice the use of mysql_real_escape_string, keep everything
056safe!
057 //also notice the sha1 function which hashes the password
058 $sql = "SELECT
059 user_id,
060 user_name,
user_level
061 FROM
062 users
063 WHERE
064 user_name = '" .
mysql_real_escape_string($_POST['user_name']) . "'
065
066 AND
067 user_pass = '" . sha1($_POST['user_pass']) . "'";
068
069 $result = mysql_query($sql);
070 if(!$result)
{
071 //something went wrong, display the error
072 echo 'Something went wrong while signing in. Please try
073again later.';
074 //echo mysql_error(); //debugging purposes, uncomment when
needed
075 }
076 else
077 {
078 //the query was successfully executed, there are 2
079 possibilities
//1. the query returned data, the user can be signed in
080 //2. the query returned an empty result set, the
081credentials were wrong
082 if(mysql_num_rows($result) == 0)
083 {
echo 'You have supplied a wrong user/password
084
combination. Please try again.';
085 }
086 else
087 {
088 //set the $_SESSION['signed_in'] variable to TRUE
$_SESSION['signed_in'] = true;
089
090 //we also put the user_id and user_name values in the
091$_SESSION, so we can use it at various pages
092 while($row = mysql_fetch_assoc($result))
093 {
094 $_SESSION['user_id'] = $row['user_id'];
$_SESSION['user_name'] = $row['user_name'];
095 $_SESSION['user_level'] = $row['user_level'];
096 }
097
098 echo 'Welcome, ' . $_SESSION['user_name'] . '. <a
099 href="index.php">Proceed to the forum overview</a>.';
100 }
}
101 }
102 }
103}
104
105include 'footer.php';
106?>
107

This is the query that's in the signin.php file:


01
02SELECT
user_id,
03 user_name,
04 user_level
05FROM
06 users
WHERE
07
user_name = '" . mysql_real_escape_string($_POST['user_name']) . "'
08AND
09 user_pass = '" . sha1($_POST['user_pass'])
10

It's obvious we need a check to tell if the supplied credentials belong to an existing user. A lot of
scripts retrieve the password from the database and compare it using PHP. If we do this directly
via SQL the password will be stored in the database once during registration and never leave it
again. This is safer, because all the real action happens in the database layer and not in our
application.

If the user is signed in successfully, we're doing a few things:

01<?php
02//set the $_SESSION['signed_in'] variable to TRUE
03$_SESSION['signed_in'] = true;
04//we also put the user_id and user_name values in the $_SESSION, so we can
use it at various pages
05while($row = mysql_fetch_assoc($result))
06{
07 $_SESSION['user_id'] = $row['user_id'];
08 $_SESSION['user_name'] = $row['user_name'];
09 }
?>
10

First, we set the 'signed_in' $_SESSION var to true, so we can use it on other pages to make sure
the user is signed in. We also put the username and user id in the $_SESSION variable for usage
on a different page. Finally, we display a link to the forum overview so the user can get started
right away.

Of course signing in requires another function, signing out! The sign-out process is actually a lot
easier than the sign-in process. Because all the information about the user is stored in
$_SESSION variables, all we have to do is unset them and display a message.

Now that we've set the $_SESSION variables, we can determine if someone is signed in. Let's
make a last simple change to header.php:

Replace:

1<div id="userbar">Hello Example. Not you? Log out.</div>


With:

01<?php
02<div id="userbar">
if($_SESSION['signed_in'])
03 {
04 echo 'Hello' . $_SESSION['user_name'] . '. Not you? <a
05href="signout.php">Sign out</a>';
06 }
07 else
{
08 echo '<a href="signin.php">Sign in</a> or <a href="sign up">create
09an account</a>.';
10 }
11 </div>

If a user is signed in, he will see his or her name displayed on the front page with a link to the
signout page. Our authentication is done! By now our forum should look like this:

Step 7: Creating a Category


We want to create categories so let's start with making a form.

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" />
</form>
5
This step looks a lot like Step 4 (Signing up a user'), so I'm not going to do an in-depth
explanation here. If you followed all the steps you should be able to understand this somewhat
quickly.

01
02
03<?php
04//create_cat.php
05include 'connect.php';
06
if($_SERVER['REQUEST_METHOD'] != 'POST')
07{
08 //the form hasn't been posted yet, display it
09 echo '<form method='post' action=''>
10 Category name: <input type='text' name='cat_name' />
Category description: <textarea name='cat_description' /></textarea>
11
<input type='submit' value='Add category' />
12 </form>';
13}
14else
15{
//the form has been posted, so save it
16 $sql = ìINSERT INTO categories(cat_name, cat_description)
17 VALUES('' . mysql_real_escape_string($_POST['cat_name']) . ì',
18 '' . mysql_real_escape_string($_POST['cat_description']) .
19ì')';
20 $result = mysql_query($sql);
if(!$result)
21 {
22 //something went wrong, display the error
23 echo 'Error' . mysql_error();
24 }
else
25 {
26 echo 'New category successfully added.';
27 }
28}
29?>
30
31

As you can see, we've started the script with the $_SERVER check, after checking if the user has
admin rights, which is required for creating a category. The form gets displayed if it hasn't been
submitted already. If it has, the values are saved. Once again, a SQL query is prepared and then
executed.
Step 8: Adding Categories to index.php
We've created some categories, so now we're able to display them on the front page. Let's add the
following query to the content area of index.php.

1SELECT
2 categories.cat_id,
3 categories.cat_name,
4 categories.cat_description,
FROM
5
categories
6

This query selects all categories and their names and descriptions from the categories table. We
only need a bit of PHP to display the results. If we add that part just like we did in the previous
steps, the code will look like this.

01<?php
02//create_cat.php
include 'connect.php';
03include 'header.php';
04
05$sql = "SELECT
06 cat_id,
07 cat_name,
cat_description,
08
09
10
11 FROM
12 categories";
13
14$result = mysql_query($sql);
15
16if(!$result)
{
17 echo 'The categories could not be displayed, please try again later.';
18}
19else
20{
if(mysql_num_rows($result) == 0)
21 {
22 echo 'No categories defined yet.';
23 }
24 else
25 {
//prepare the table
26 echo '<table border="1">
27 <tr>
28 <th>Category</th>
29 <th>Last topic</th>
</tr>';
30
31
while($row = mysql_fetch_assoc($result))
32 {
33 echo '<tr>';
34 echo '<td class="leftpart">';
35 echo '<h3><a href="category.php?id">' .
$row['cat_name'] . '</a></h3>' . $row['cat_description'];
36 echo '</td>';
37 echo '<td class="rightpart">';
38 echo '<a href="topic.php?id=">Topic subject</a>
39at 10-10';
40 echo '</td>';
echo '</tr>';
41 }
42 }
43}
44
45include 'footer.php';
46?>
47
48
49

Notice how we're using the cat_id to create links to category.php. All the links to this page will
look like this: category.php?cat_id=x, where x can be any numeric value. This may be new to
you. We can check the url with PHP for $_GET values. For example, we have this link:
1category.php?cat_id=23

The statement echo $_GET[ëcat_id'];' will display '23'. In the next few steps we'll use this value
to retrieve the topics when viewing a single category, but topics can't be viewed if we haven't
created them yet. So let's create some topics!

Step 9: Creating a Topic


In this step, we're combining the techniques we learned in the previous steps. We're checking if a
user is signed in, we'll use an input query to create the topic and create some basic HTML forms.

The structure of create_topic.php can hardly be explained in a list or something, so I rewrote it in


pseudo-code.

01
02<?php
03if(user is signed in)
04{
05 //the user is not signed in
06 }
else
07{
08 //the user is signed in
09 if(form has not been posted)
10 {
//show form
11
}
12 else
13 {
14 //process form
15 }
}
16?>
17
18

Here's the real code of this part of our forum, check the explanations below the code to see what
it's doing.

001<?php
002//create_cat.php
include 'connect.php';
003include 'header.php';
004
005echo '<h2>Create a topic</h2>';
006if($_SESSION['signed_in'] == false)
007{
//the user is not signed in
008 echo 'Sorry, you have to be <a href="/forum/signin.php">signed in</a>
009to create a topic.';
010}
011 else
012{
//the user is signed in
013 if($_SERVER['REQUEST_METHOD'] != 'POST')
014 {
015 //the form hasn't been posted yet, display it
016 //retrieve the categories from the database for use in the dropdown
$sql = "SELECT
017 cat_id,
018 cat_name,
019 cat_description
020 FROM
021 categories";
022
$result = mysql_query($sql);
023
024 if(!$result)
025 {
026 //the query failed, uh-oh :-(
027 echo 'Error while selecting from database. Please try again
028 later.';
}
029 else
030 {
031 if(mysql_num_rows($result) == 0)
032 {
//there are no categories, so a topic can't be posted
033 if($_SESSION['user_level'] == 1)
034 {
035 echo 'You have not created categories yet.';
036 }
037 else
{
038 echo 'Before you can post a topic, you must wait for an
039admin to create some categories.';
040 }
041 }
else
042
{
043
044 echo '<form method="post" action="">
045 Subject: <input type="text" name="topic_subject" />
046 Category:';
047
048 echo '<select name="topic_cat">';
while($row = mysql_fetch_assoc($result))
049
{
050 echo '<option value="' . $row['cat_id'] . '">' .
051$row['cat_name'] . '</option>';
052 }
053 echo '</select>';
054
echo 'Message: <textarea name="post_content" /></textarea>
055 <input type="submit" value="Create topic" />
056 </form>';
057 }
}
058
}
059 else
060 {
061 //start the transaction
062 $query = "BEGIN WORK;";
063 $result = mysql_query($query);
064
if(!$result)
065 {
066 //Damn! the query failed, quit
067 echo 'An error occured while creating your topic. Please try
068again later.';
069 }
else
070 {
071
072 //the form has been posted, so save it
073 //insert the topic into the topics table first, then we'll save
074the post into the posts table
075 $sql = "INSERT INTO
topics(topic_subject,
076 topic_date,
077 topic_cat,
078 topic_by)
079 VALUES('" .
080mysql_real_escape_string($_POST['topic_subject']) . "',
NOW(),
081 " .
082mysql_real_escape_string($_POST['topic_cat']) . ",
083 " . $_SESSION['user_id'] . "
084 )";
085
$result = mysql_query($sql);
086 if(!$result)
087 {
088 //something went wrong, display the error
089 echo 'An error occured while inserting your data. Please
090 try again later.' . mysql_error();
$sql = "ROLLBACK;";
091 $result = mysql_query($sql);
092 }
093 else
094 {
//the first query worked, now start the second, posts query
095
//retrieve the id of the freshly created topic for usage in
096the posts query
097 $topicid = mysql_insert_id();
098
099 $sql = "INSERT INTO
100 posts(post_content,
post_date,
101 post_topic,
102 post_by)
103
104
105
106
107
108
109 VALUES
110 ('" .
mysql_real_escape_string($_POST['post_content']) . "',
111 NOW(),
112 " . $topicid . ",
113 " . $_SESSION['user_id'] . "
114 )";
115 $result = mysql_query($sql);
116
if(!$result)
117 {
118 //something went wrong, display the error
119 echo 'An error occured while inserting your post.
120Please try again later.' . mysql_error();
121 $sql = "ROLLBACK;";
$result = mysql_query($sql);
122 }
123 else
124 {
125 $sql = "COMMIT;";
$result = mysql_query($sql);
126
127 //after a lot of work, the query succeeded!
128 echo 'You have successfully created <a href="topic.php?
129id='. $topicid . '">your new topic</a>.';
130 }
131 }
}
132 }
133}
134
135include 'footer.php';
136?>
137
138
139
140
141
142

I'll discuss this page in two parts, showing the form and processing the form.

Showing the form


We're starting with a simple HTML form. There is actually something special here, because we
use a dropdown. This dropdown is filled with data from the database, using this query:
1SELECT
2 cat_id,
3 cat_name,
4 cat_description
FROM
5
categories
6

That's the only potentially confusing part here; it's quite a piece of code, as you can see when
looking at the create_topic.php file at the bottom of this step.

Processing the form

The process of saving the topic consists of two parts: saving the topic in the topics table and
saving the first post in the posts table. This requires something quite advanced that goes a bit
beyond the scope of this tutorial. It's called a transaction, which basically means that we start by
executing the start command and then rollback when there are database errors and commit when
everything went well. More about transactions.

01
02<?php
//start the transaction
03
$query = "BEGIN WORK;";
04$result = mysql_query($query);
05//stop the transaction
06$sql = "ROLLBACK;";
07$result = mysql_query($sql);
08//commit the transaction
$sql = "COMMIT;";
09$result = mysql_query($sql);
10?>
11

The first query being used to save the data is the topic creation query, which looks like this:

1
2INSERT INTO
topics(topic_subject,
3 topic_date,
4 topic_cat,
5 topic_by)
6 VALUES('" . mysql_real_escape_string($_POST['topic_subject']) . "',
NOW(),
7 " . mysql_real_escape_string($_POST['topic_cat']) . ",
8 " . $_SESSION['user_id'] . ")
9

At first the fields are defined, then the values to be inserted. We've seen the first one before, it's
just a string which is made safe by using mysql_real_escape_string(). The second value, NOW(),
is a SQL function for the current time. The third value, however, is a value we haven't seen
before. It refers to a (valid) id of a category. The last value refers to an (existing) user_id which
is, in this case, the value of $_SESSION[ëuser_id']. This variable was declared during the sign in
process.

If the query executed without errors we proceed to the second query. Remember we are still
doing a transaction here. If we would've got errors we would have used the ROLLBACK
command.

01
02INSERT INTO
posts(post_content,
03 post_date,
04 post_topic,
05 post_by)
06 VALUES
('" . mysql_real_escape_string($_POST['post_content']) . "',
07
NOW(),
08 " . $topicid . ",
09 " . $_SESSION['user_id'] . ")
10

The first thing we do in this code is use mysql_insert_id() to retrieve the latest generated id from
the topic_id field in the topics table. As you may remember from the first steps of this tutorial,
the id is generated in the database using auto_increment.

Then the post is inserted into the posts table. This query looks a lot like the topics query. The
only difference is that this post refers to the topic and the topic referred to a category. From the
start, we decided to create a good data model and here is the result: a nice hierarchical structure.

Step 10: Category View


We're going to make an overview page for a single category. We've just created a category, it
would be handy to be able to view all the topics in it. First, create a page called category.php.

A short list of the things we need:

Needed for displaying the category

 cat_name
 cat_description

Needed for displaying all the topics

 topic_id
 topic_subject
 topic_date
 topic_cat

Let's create the two SQL queries that retrieve exactly this data from the database.

1
SELECT
2 cat_id,
3 cat_name,
4 cat_description
5 FROM
categories
6
WHERE
7 cat_id = " . mysql_real_escape_string($_GET['id'])
8

The query above selects all the categories from the database.

1
2SELECT
topic_id,
3 topic_subject,
4 topic_date,
5 topic_cat
6 FROM
topics
7
WHERE
8 topic_cat = " . mysql_real_escape_string($_GET['id'])
9

The query above is executed in the while loop in which we echo the categories. By doing it this
way, we'll see all the categories and the latest topic for each of them.
The complete code of category.php will be the following:

01<?php
02//create_cat.php
03include 'connect.php';
include 'header.php';
04
05//first select the category based on $_GET['cat_id']
06$sql = "SELECT
07 cat_id,
08 cat_name,
cat_description
09 FROM
10 categories
11 WHERE
12 cat_id = " . mysql_real_escape_string($_GET['id']);
13
14$result = mysql_query($sql);
15
if(!$result)
16{
17 echo 'The category could not be displayed, please try again later.' .
18mysql_error();
19}
20else
{
21 if(mysql_num_rows($result) == 0)
22 {
23 echo 'This category does not exist.';
24 }
else
25 {
26 //display category data
27 while($row = mysql_fetch_assoc($result))
28 {
29 echo '<h2>Topics in ′' . $row['cat_name'] . '′ category</h2>';
}
30
31 //do a query for the topics
32 $sql = "SELECT
33 topic_id,
34 topic_subject,
topic_date,
35 topic_cat
36 FROM
37 topics
38 WHERE
39 topic_cat = " . mysql_real_escape_string($_GET['id']);
40
$result = mysql_query($sql);
41
42 if(!$result)
43 {
44 echo 'The topics could not be displayed, please try again
45 later.';
46 }
else
47 {
48
49
50
51
52
53 if(mysql_num_rows($result) == 0)
54 {
55 echo 'There are no topics in this category yet.';
}
56 else
57 {
58 //prepare the table
59 echo '<table border="1">
<tr>
60 <th>Topic</th>
61 <th>Created at</th>
62 </tr>';
63
64 while($row = mysql_fetch_assoc($result))
65 {
echo '<tr>';
66 echo '<td class="leftpart">';
67 echo '<h3><a href="topic.php?id=' .
68$row['topic_id'] . '">' . $row['topic_subject'] . '</a><h3>';
69 echo '</td>';
70 echo '<td class="rightpart">';
echo date('d-m-Y',
71strtotime($row['topic_date']));
72 echo '</td>';
73 echo '</tr>';
74 }
}
75
}
76 }
77}
78
79include 'footer.php';
80?>
81
82
83
84
85

And here is the final result of our categories page:


Step 11: Topic View
The SQL queries in this step are complicated ones. The PHP-part is all stuff that you've seen
before. Let's take a look at the queries. The first one retrieves basic information about the topic:

1SELECT
2 topic_id,
3 topic_subject
4 FROM
5 topics
WHERE
6 topics.topic_id = " . mysql_real_escape_string($_GET['id'])
7

This information is displayed in the head of the table we will use to display all the data. Next, we
retrieve all the posts in this topic from the database. The following query gives us exactly what
we need:

01SELECT
posts.post_topic,
02
posts.post_content,
03 posts.post_date,
04 posts.post_by,
05 users.user_id,
06 users.user_name
FROM
07 posts
08LEFT JOIN
09 users
10 ON
11 posts.post_by = users.user_id
WHERE
12 posts.post_topic = " . mysql_real_escape_string($_GET['id'])
13
14
15

This time, we want information from the users and the posts table - so we use the LEFT JOIN
again. The condition is: the user id should be the same as the post_by field. This way we can
show the username of the user who replied at each post.

The final topic view looks like this:

Advertisement

Step 12: Adding a Reply


Let's create the last missing part of this forum, the possibility to add a reply. We'll start by
creating a form:

1<form method="post" action="reply.php?id=5">


2 <textarea name="reply-content"></textarea>
3 <input type="submit" value="Submit reply" />
</form>
4
The complete reply.php code looks like this.

01<?php
02//create_cat.php
include 'connect.php';
03include 'header.php';
04
05if($_SERVER['REQUEST_METHOD'] != 'POST')
06{
07 //someone is calling the file directly, which we don't want
echo 'This file cannot be called directly.';
08}
09else
10{
11 //check for sign in status
12 if(!$_SESSION['signed_in'])
{
13 echo 'You must be signed in to post a reply.';
14 }
15 else
16 {
//a real user posted a real reply
17 $sql = "INSERT INTO
18 posts(post_content,
19 post_date,
20 post_topic,
21 post_by)
VALUES ('" . $_POST['reply-content'] . "',
22 NOW(),
23 " . mysql_real_escape_string($_GET['id']) . ",
24
25
26
27 " . $_SESSION['user_id'] . ")";
28
29 $result = mysql_query($sql);
30
31 if(!$result)
{
32
echo 'Your reply has not been saved, please try again later.';
33 }
34 else
35 {
36 echo 'Your reply has been saved, check out <a href="topic.php?
id=' . htmlentities($_GET['id']) . '">the topic</a>.';
37 }
38 }
39}
40
41include 'footer.php';
42?>
43
44
45

The comments in the code pretty much detail what's happening. We're checking for a real user
and then inserting the post into the database.

You might also like