Making a Forum

Making a Forum

by Louis Stowasser

In this tutorial we are going to learn how to create a forum. Note that this tutorial isn't going to be just a copy and paste tutorial (Although doing that will give you a fully functional forum). You will hopefully learn the basics and if you get stuck, you can email me at louisstow AT hotmail DOT com or I am on the forums 24/7 so just make a topic there. There will be no extra processing pages for this tutorial. All the form processing will be done on the same page.

PLEASE READ THE COMMENTS TO UNDERSTAND HOW THE CODE WORKS! - I use comments in the code to tell you what I am doing and why. At then end of the code I give a brief summary of what the code does and give links to other resources that may help.

To get all the files used to make this forum, including the SQL Dump file, click here.

INGREDIENTS

  • ColdFusion 6+
  • MySQL, MsSQL or any other database. (I tried testing with MS Access but hardly anything worked. So those with a decent DBMS, your safe. To access users, www.mysql.com)
  • A decent code editor
  • A pot of coffee. This may take a while

DATABASE SETUP

I am going to assume you are using MySQL. If so, the link to all the files contains an SQL Dump file of the database. If not, you can change the data types to whatever you see necessary. You will need 4 tables:

Users Posts Topics Forums
userID - PK
user_name - varchar
pass_word - varchar
joinDate - date
posts - integer
role - integer
postID - PK
postBody - text
topicID - integer
userID - integer
postTime - DateTime
topicID - PK
topicTitle - varchar
userID - integer
forumID - integer
sticky - integer
views - integer
forumID - PK
forumName - varchar
forumDescr - varchar
startUser - varchar

Now that we have the backbone of the forum, we will start on the pages. Now I'm not going to design this forum for you, but what I will do is make it easy for you to implement your own design.

PAGES

  • layout.cfm - This will be where you put your blasted HTML code so I don't have to do it.
  • Application.cfm - Where we setup the main variables.
  • index.cfm - The main page where the forum categories are displayed, including info about the last poster and how many post/topics.
  • login.cfm - Where the user logs in.
  • logout.cfm - Where the user logs out (if we're lucky).
  • register.cfm - The user registers their account here.
  • viewforum.cfm - Where all the topics are listed and more info including views, replies and the last poster is displayed.
  • viewtopic.cfm - Will display a topic/thread whatever you like to call it.
  • newpost.cfm - This will be the page where users reply to topics.
  • newtopic.cfm - This is where users will create a new topic/thread/discussion.
  • editpost.cfm - Where users can edit their post.
  • edittopic.cfm - Where users edit their topic.

LAYOUT.CFM

This will be a custom tag to automatically fill in the HTML code. You should put the start of your code before the <cfelse> and the rest after it. Ray Camden has an article about this here: http://www.coldfusionjedi.com/index.cfm/2007/9/3/ColdFusion-custom-tag-for-layout-example

Code:

<cfif thisTag.executionMode EQ "start">

<cfparam name="attributes.title" default="myForum">

<!--- Display the header of our design --->
<html>
<head>
<title>
<cfoutput>#attributes.title#</cfoutput></title>
<body>
<div align="center" style="background:#ffd579; border-bottom:4px #ffcc00 solid"><h1 id="top">My Forum</h1>
<cfif structKeyExists(session,'userID')>
<!--- This checks to see if the session userID is set. --->
<cfquery name="uname" datasource="#request.dsn#">
<!--- If the above statement returns true, we query the database to find out their username --->
SELECT user_name FROM Users WHERE userID = <cfqueryparam value="#session.userID#" cfsqltype="cf_sql_integer">
</cfquery>
Welcome <strong><cfoutput>#uname.user_name#</cfoutput>!</strong> - [<a href="logout.cfm">Logout</a>] <!--- Then we greet the user and display the logout link --->
<cfelse> <!--- If the session is not set, meaning the user is not logged in, we display the login or register links --->
[<a href="Login.cfm">Login</a>] [<a href="register.cfm">Register</a>]
</cfif>
</div>

<cfelse>
<!--- Display the footer of our design --->
<hr>
<div align="center">
&copy; My Forum 2008</div>
</body>
</html>


</cfif>

So what we are doing is checking if the custom tag is in the starting stage. If so then display the html for the top half of our design. If not, display the bottom half. I added an attribute to the custom tag called title, which will be the page title. I defaulted it to "myForum" so I only have to set the title when I want to. The HTML is very basic so I recommend putting in your own design. Something you should keep in there though, is the query and checking if the session is set.

APPLICATION.CFM

This is where we setup the variables and start the application. I would normally use Application.cfc but let's just keep this simple.

Code:

<cfapplication name="MyForum" sessionmanagement="yes" sessiontimeout="#createTimeSpan(0,0,30,0)#" applicationtimeout="#createTimeSpan(0,0,20,0)#" clientmanagement="no">
<cfset request.dsn = "myforumdsn">

The cfapplication will create our application. You may change the duration of the session time out by changing the createtimespan variable in the sessiontimeout attribute.

We also set a request variable of our datasource which in my case is "myforumdsn".

INDEX.CFM

This will be our main page and the first time we use our layout custom tag.

Code:

<cfquery datasource="#request.dsn#" name="forums">
SELECT f.forumID,f.forumName,t.total
FROM Forums f
     LEFT OUTER JOIN ( <!--- We join a sub query of the post table and topics table in another join to return the post count for each forum --->
SELECT Topics.forumID,COUNT(Posts.postID) AS total FROM Topics LEFT JOIN Posts ON Posts.topicID = Topics.topicID GROUP BY Topics.forumID
) t
ON t.forumID = f.forumID
</cfquery>

<cf_layout title="Welcome to MyForum">
<!--- Our first use of the layout custom tag. This will automatically inject our design code written in layout.cfm --->
<h2>Welcome to MyForum</h2>
<table width="100%"><tr>
<th>Forum</th><th>Posts</th>
</tr>
<cfoutput query="forums"> <!--- Here we are outputing the "forums" query. The forums query will return every forum with a post count --->
<tr height="35"<cfif currentrow MOD 2> bgcolor="##ebebeb"</cfif>> <!--- The simple if statement checks if the outputed row is even, if it is then it makes the background color grey. So basically it makes alternate row colours --->
<td><a href="viewforum.cfm?f=#forumID#">#forumName#</a></td><td align="center">#val(total)#</td>
</tr>
</cfoutput>
</table>
</cf_layout>

The query may look complicated. That's because it is. What we are doing is joining 3 tables together to tell us the post count of each forum. We use the custom <cf_layout> tag that we created earlier to implement the design and do a simple query output to display the data.

For more information on the alternating row colors, check out this tutorial by Pablo: http://tutorial22.easycfm.com/

LOGIN.CFM

The user will be able to login on this page.

Code:

<cfif NOT structIsEmpty(form)> <!--- This statement checks if the form has been submited. If so then continue with the next bit of code --->
<cfquery datasource="#request.dsn#" name="verify">
SELECT userID,user_name,pass_word
FROM Users
WHERE user_name = <cfqueryparam value="#form.user#" cfsqltype="cf_sql_char"> AND <!--- We are comparing the submited form details with the database to find a match. --->
pass_word = <cfqueryparam value="#form.pass#" cfsqltype="cf_sql_char">
</cfquery>

<cfif verify.recordcount> <!--- If a match was found then continue --->
<cfset session.userID = verify.userID>
<!--- We set the session variables to the userID found. This will be used throughout the forum --->
<cflocation url="index.cfm" addtoken="no">
<!--- The user is now logged in so send them back to the starting page --->
<cfelse>
<!--- If a match was NOT found then the following code will be executed --->
<script>
alert("We could not find a user with those details. Please try again"); <!--- Alert them that the user was not found --->
self.location = 'login.cfm'; <!--- Send them to the login page to try and login again --->
</script>
</cfif>
</cfif>

<cf_layout>
<h2>Login</h2>

<form action="login.cfm" method="post"> <!--- Display the login form. We use 2 input text forms. For the username and password --->
Username: <input type="text" name="user" /><br />
Password: <input type="password" name="pass" /><br />
<input type="submit" name="submit" value="Login" />
<input type="button" value="Back" onclick="history.go(-1);" /> <!--- The back button contains simple javascript that sends them back in history --->

</cf_layout>

So we query the database where the username and password are equal to the one specified. If we find a match, we set their session to the user id found. If not, we give them a javascript popup. You can change this to whatever you like. You may want to log this failed attempt incase anyone is trying to login by brute force.

Here is a tutorial by pabdog regarding user authentication: http://tutorial8.easycfm.com

LOGOUT.CFM

It is very doubtful that users will actually logout by clicking this, but it's good to have it anyway. This is the simplest page yet.

Code:

<cfset StructClear(session)> <!--- We clear the session structure --->
<cflocation url="login.cfm" addtoken="no"> <!--- Then direct them back to the login page --->

And that's all the code needed!!! Simple code for once.

For a tutorial on clearing the session structure, see this tutorial: http://tutorial24.easycfm.com/

REGISTER.CFM

There are many things you will want to change on this page. For example you may want to send a confirmation email. But to make this forum work, we will create a basic registration page.

Code:

<cfif NOT structIsEmpty(form)> <!--- You should know what this does already but incase you forgot, it checks if the form has been submit. I'm not going to tell you again so remember it!! :) --->
<cfquery name="insuser" datasource="#request.dsn#"> <!--- Insert the provided form values in the database. Normally we would validate what goes in here but I think your old enough to do that yourself hehe --->
INSERT INTO users(user_name,pass_word,posts,role,joinDate)
VALUES (
<cfqueryparam value="#form.user#" cfsqltype="cf_sql_char">
, <!--- CFQUERYPARAM. CJ's first love! This is a great tag and you should ALWAYS use it --->
<cfqueryparam value="#form.pass#" cfsqltype="cf_sql_char">
,
0,
<cfif structKeyExists(form,'role')>
2<cfelse>1</cfif>, <!--- If the checkbox to decide if the user is to be a mod has been selected, then insert the number 2 which from now on will represent a moderator --->
<cfqueryparam value="#CreateODBCDate(now())#" cfsqltype="cf_sql_date">
<!--- This will enter the date that the user registered on --->
)
</cfquery>


<cflocation url="login.cfm" addtoken="no"> <!--- Now that they are a valid user, get them to login --->
</cfif>

<cf_layout>
<h2>Register</h2>
<form action="register.cfm" method="post">

<p>
Username: <input type="text" name="user" /><br />
Password: <input type="password" name="pass" />
</p>

<p>
<input type="checkbox" name="role" /> Moderator <!--- Obviously you will want to restrict this ability to mods only, but I leave that up to you --->
</p>

<p>
<input type="submit" value="Register!" />
<input type="button" value="Back" onclick="self.location='index.cfm';" />

</p>
</form>

</cf_layout>

There would be 10,000,000.034 things you want to change here. For starters you would want to check if the username has been taken. If it has than throw an error. You would want to make sure no special characters are used in the username. But I'll leave all the validation up to you. This is just to give you a head start.

VIEWFORUM.CFM

This page will display all the topics in the chosen forum.

Code:

<cfquery datasource="#request.dsn#" name="forumInfo"> <!--- Return the name of the chosen forum --->
SELECT forumName FROM Forums WHERE forumID = <cfqueryparam value="#URL.f#" cfsqltype="cf_sql_integer">
</cfquery>

<cfquery datasource="#request.dsn#" name="topics"> <!--- This query will return all the topics in the chosen forum as well as post count, if its sticky, author, views and last post --->
SELECT t.topicID,t.topicTitle,t.views,t.sticky,u.userID,u.user_name, COUNT(p.postID) AS replies, MAX(p.postTime) AS lastpost
FROM Topics t LEFT OUTER JOIN Posts p ON t.topicID = p.topicID
              LEFT OUTER JOIN Users u ON u.userID = t.userID
WHERE t.forumID = <cfqueryparam value="#URL.f#" cfsqltype="cf_sql_integer">
GROUP BY t.topicID
ORDER BY t.sticky desc,lastpost desc <!--- We want the sticky topics to be displayed first. Then the one with the most recent post is next --->
</cfquery>

<cfif structKeyExists(session,'userID')> <!--- If the user is logged in, do a query to see what role they have --->
<cfquery name="role" datasource="#request.dsn#">
SELECT role FROM Users WHERE userID = <cfqueryparam value="#session.userID#" cfsqltype="cf_sql_integer"> </cfquery>
</cfif>

<cf_layout>
<cfoutput><h2>#forumInfo.forumName#</h2> <!--- Output the forum name as the heading --->
<a href="index.cfm">Home</a> &gt; #forumInfo.forumName#<br /> <!--- Display the breadcrumbs from the start --->
<div align="center">
[<a href="newtopic.cfm?f=#url.f#">New Topic</a>]</div></cfoutput>

<table width="100%"><tr>
<th width="15"></th><th>Topic</th><th width="150">Posted By</th><th width="60">Views</th><th width="60">Replies</th><th width="150">Last Post</th></tr>

<cfoutput query="topics"> <!--- Output that big complicated query with all the topics in it --->
<tr height="35"<cfif currentrow MOD 2> bgcolor="##ebebeb"</cfif>> <!--- That ol' alternating row color thingo --->
<td align="center"><strong>#currentrow#</strong></td> <!--- This will output its current position in the query. It isnt necessary so you may delete if you dislike it --->
<td><a href="viewtopic.cfm?t=#topicID#">#topicTitle#</a> <cfif sticky>(Topic Pinned)</cfif> <!--- If the topic is sticky, notify the user --->
<cfif structKeyExists(session,'userID') AND (session.userID EQ userID OR role.role EQ 2)>
<!--- Using the query to find out the users role, we check if the user is the original poster or a mod. If they are one of the 2, display the edit link --->
[<a href="edittopic.cfm?t=#topicID#">Edit</a>]
</cfif></td>
<td>#user_name#</td> <!--- Here we just output the returned query variables into columns --->
<td align="center">
#views#</td>
<td align="center">
#replies-1#</td>
<td width="150">
#TimeFormat(lastpost,"h:mm tt")#, #DateFormat(lastpost,"d mmm")#</td> <!--- TimeFormat() and DateFormat() format the time to our pleasing. I like displaying the day then the month as 3 letters. ie 26 Jun. And as for time, we just use the basic format (10:07 PM). This link will show you how to format your dates:
DateFormat() - http://livedocs.adobe.com/coldfusion/7/htmldocs/00000441.htm
TimeFormat() - http://livedocs.adobe.com/coldfusion/7/htmldocs/00000651.htm --->

</tr>
</cfoutput>

</table>
</cf_layout>

This is fairly similar to viewforum.cfm. The query gets the details for each topic and gets a count of the replies from the posts table. We then output that data into a table. I have subtracted the replies column by 1 otherwise the main body of the topic will be counted as a reply when it is not.


All ColdFusion Tutorials By Author: Louis Stowasser