Posted in ColdFusion | Posted on 05-23-2006 | 4,053 views
Welcome to the second part of my series on how to create a simple mailing list in ColdFusion. Be sure to read the first entry in the series before starting this one. As previously mentioned, the goal of this application is to create a simple way for users to sign up at your web site. An administrator can then use a tool to send an email to folks who have signed up. Today's entry will deal with the administrator a bit. Now I'm going to cheat a bit. I don't want to spend a lot of time on security and all that, so I'm going to write a script and place it in the same folder as my other files. Obviously in a real world application this file would be placed in a protected folder. The specific item to add to our application today is a simple interface to list the subscribers and add/delete folks. Later in the series I'll discuss how folks can delete themselves, but the honest truth is that even if you provide such a method, folks will still email you (or call you) and demand that you remove them. So lets work on a tool that will make that simple.
The following script will handle listing subscribers, removing subscribers, as well as adding them:
2 <cfset application.maillist.unsubscribe(url.delete)>
3</cfif>
4
5<cfif structKeyExists(form, "add") and len(trim(form.email)) and isValid("email", form.email)>
6 <cfset application.maillist.subscribe(form.email)>
7</cfif>
8
9<cfset members = application.maillist.getMembers()>
10
11<cfoutput>
12<p>
13Your mail list has
14 <cfif members.recordCount is 0>
15 no members
16 <cfelseif members.recordCount is 1>
17 1 member
18 <cfelse>
19 #members.recordCount# members
20 </cfif>. You may use the table below to remove any member, or the form to add a new member.
21</p>
22</cfoutput>
23
24<cfif members.recordCount gte 1>
25
26 <p>
27 <table border="1">
28 <tr>
29 <th>Email Address</th>
30 <td> </td>
31 </tr>
32 <cfloop query="members">
33 <tr <cfif currentRow mod 2>bgcolor="yellow"</cfif>>
34 <cfoutput>
35 <td>#email#</td>
36 <td><a href="listmembers.cfm?delete=#token#">Delete</a></td>
37 </cfoutput>
38 </tr>
39 </cfloop>
40 </table>
41 </p>
42
43</cfif>
44
45<form action="listmembers.cfm" method="post">
46<input type="text" name="email"> <input type="submit" name="add" value="Add Subscriber">
47</form>
There is a lot going on here, so let's handle it line by line. At the top of the script I have two checks. The first is for list removals. I check for the value, url.delete, and if it exists, I call out to my CFC to unsubscribe the user. I'm using the token instead of the email address. (You will see this later in the script.) The reason for this is that the token serves as a good primary key for the table. Sure, I know the email addresses are unique, but I'm also going to use something similar for the front end. Therefore, I just pass the token to the method.
Adding a subscriber is also rather simple. I look for the submit button (named "add") and check to see if the email address is valid. Because this is the admin I do less hand holding. I'm not going to display an error if the email address isn't valid. Obviously you can change this in your own code. I tend to be a bit cruel in my own administrator tools.
The next section of the script gets the members from the mailing list and displays a simple count of members along with a nicely designed table. (Yes, the nicely designed part is a joke.) I had mentioned above that I use the token for deletions. Now you see where this comes from. Each delete link passes it back to the script.
Last but not least, I added a simple form with one field and a button. This lets the administrator quickly add email address to the mail list.
Alright, now that I showed you the front end, let's look at the new version of the CFC:
2
3<cffunction name="init" returnType="maillist" output="false" access="public">
4 <cfargument name="dsn" type="string" required="true">
5
6 <cfset variables.dsn = arguments.dsn>
7
8 <cfreturn this>
9</cffunction>
10
11<cffunction name="getMembers" returnType="query" output="false" access="public"
12 hint="Returns a query of everyone subscribed.">
13 <cfset var q = "">
14
15 <cfquery name="q" datasource="#variables.dsn#">
16 select email, token
17 from subscribers
18 order by email asc
19 </cfquery>
20
21 <cfreturn q>
22</cffunction>
23
24<cffunction name="subscribe" returnType="boolean" output="false" access="public"
25 hint="Adds a user to the mailinst list, if and only if the person wasn't already on the list.">
26 <cfargument name="email" type="string" required="true">
27 <cfset var checkIt = "">
28
29 <cfif not isValid("email", arguments.email)>
30 <cfthrow message="#arguments.email# is not a valid email address.">
31 </cfif>
32
33 <!--- only add if the user doesn't already exist. --->
34 <cflock name="maillist" type="exclusive" timeout="30">
35 <cfquery name="checkIt" datasource="#variables.dsn#">
36 select email
37 from subscribers
38 where email = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.email#">
39 </cfquery>
40
41 <cfif checkIt.recordCount is 0>
42
43 <cfquery datasource="#variables.dsn#">
44 insert into subscribers(email,token)
45 values(<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.email#">,<cfqueryparam cfsqltype="cf_sql_varchar" value="#createUUID()#">)
46 </cfquery>
47
48 <cfreturn true>
49
50 <cfelse>
51
52 <cfreturn false>
53
54 </cfif>
55
56 </cflock>
57
58</cffunction>
59
60<cffunction name="unsubscribe" returnType="void" output="false" access="public"
61 hint="Removes a user to the mailinst list.">
62 <cfargument name="token" type="uuid" required="true">
63
64 <cfquery datasource="#variables.dsn#">
65 delete from subscribers
66 where token = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.token#">
67 </cfquery>
68</cffunction>
69
70</cfcomponent>
Let's focus on the changes from last time. There are two new methods, getMembers and unsubscribe. Both are rather simple, so I won't say a lot about them. If you have questions though, please add a comment.
Thats it for this entry. As a general FYI, I may not be able to write part three till Friday. I've got a presentation tomorrow night (are you coming?) and Thursday is packed. Also, I made a small tweak to the Application.cfc file. I added a small hook to let me reinit the application using a URL variable. It's in there if you want to take a peak, but isn't anything special. As before, I've attached a zip to this entry so you can download the code and look for yourself. The next entry will add the mailer to the application. (The whole point of the series!)


The download link to the zip isn't showing.
Anyway all of your tutorial makes sense and works at my end except for when the subscriber them self (i.e not via the mailing list admin page/s) tries to unsubscribe
using your template TEMPUnsubscribe.cfm included in your archive file causes the problem. You have not included a way for the user to pass the token accros in teh URL query string.
If the user enters his email address blah@blah.net
the result sent to unsubscribe.cfm will be:
unsubscribe.cfm?emailaddress=ulric@uk2.net&unsubscribe=Unsubscribe
and this will cause the database deletion to fail and we get the result "Sorry, but you were not unsubscribed. Please ensure that you have copied the URL correctly from your mail client.".
Would it be possible for you to update the unsubscribe pages so that token is included somehow. I cant figure it out :(
many thanks
Major apologies!
Ulric
One thing I noticed is the behavior of the app if someone is already subscribed. If the email address already exists, the visitor gets the same message about thank you for registering. Is it possible to tell the visitor that he/she is already registered?
Comment 4 written by ulric on 13 March 2009, at 3:06 PM
Sorry Ray! I did not figure out all i needed was unsubscribe.cfm?token=%token% for user self service unsubscribe to work, and that I should ignore template TEMPunsubscribe.cfm.
Major apologies!
<cfquery datasource="maillist">
UPDATE subscribers
SET token = <cfqueryparam cfsqltype="cf_sql_varchar" value="#createUUID()#">
WHERE token IS NULL
</cfquery>
<cfquery datasource="maillist">
UPDATE subscribers
SET token = <cfqueryparam cfsqltype="cf_sql_varchar" value="#createUUID()#">
WHERE id > 14
</cfquery>
and it did create UUIDs, but they were all the same. If this is at all possible, it probably involves some kind of LOOP, doesn't it?
[Add Comment] [Subscribe to Comments]