LISTING 28.16 Continued } else if (mysql_num_rows($result)==0) { echo “There is nobody subscribed to list number $listid”; return false; } else { include(‘class.html.mime.mail.inc’); $mail = new html_mime_mail(); // read in the text version $filename = “archive/$listid/$mailid/text.txt”; $fp = fopen ($filename, “r”); $text = fread($fp, filesize($filename)); fclose ($fp); // read in the HTML version $filename = “archive/$listid/$mailid/index.html”; $fp = fopen ($filename, “r”); $html = fread($fp, filesize($filename)); fclose ($fp); // get the list of images that relate to this message $query = “select path, mimetype from images where mailid = $mailid”; if(db_connect()) { $result = mysql_query($query); if(!$result) { echo “<p>Unable to get image list from database.”; return false; } $num = mysql_numrows($result); for($i = 0; $i<$num; $i++) { //load each image from disk $filename = “archive/$listid/$mailid/”.mysql_result($result, $i, 0); $fp = fopen($filename, ‘r’); $image = fread($fp, filesize($filename)); fclose($fp); // add images to the mimemail object $mail->add_html_image($image, mysql_result($result, $i, 0), Building a Mailing List Manager C HAPTER 28 28 BUILDING A MAILING LIST MANAGER 705 34 7842 CH28 3/6/01 3:46 PM Page 705 LISTING 28.16 Continued mysql_result($result, $i, 1)); } } // add HTML and text to the mimemail object $mail->add_html($html, $text); // note that we build and encode the message here outside the loop, // not repeatedly inside the loop $mail->build_message(); if($status == ‘STORED’) { //send the HTML version of the message to administrator $mail->send(get_real_name($admin_user), $admin_user, $from_name, $from_address, $subject); //send the text version of the message to administrator mail(get_real_name($admin_user).” <”.$admin_user.”>”, $subject, $text, “From: $from_name <$from_address>”); echo “Mail sent to $admin_user”; $query = “update mail set status = ‘TESTED’ where mailid = $mailid”; if(db_connect()) { $result = mysql_query($query); } echo “<p>Press send again to send mail to whole list.<center>”; display_button(‘send’, “&id=$mailid”); echo “</center>”; } else if($status == ‘TESTED’) { //send to whole list $query = “select subscribers.realname, sub_lists.email, subscribers.mimetype from sub_lists, subscribers where listid = $listid and sub_lists.email = subscribers.email”; if(!db_connect()) return false; Building Practical PHP and MySQL Projects P ART V 706 34 7842 CH28 3/6/01 3:46 PM Page 706 LISTING 28.15 Continued $result = mysql_query($query); if(!$result) echo “<p>Error getting subscriber list”; $count = 0; // for each subscriber while( $subscriber = mysql_fetch_row($result) ) { if($subscriber[2]==’H’) //send HTML version to people who want it $mail->send($subscriber[0], $subscriber[1], $from_name, $from_address, $subject); else //send text version to people who don’t want HTML mail mail($subscriber[0].” <”.$subscriber[1].”>”, $subject, $text, “From: $from_name <$from_address>”); $count++; } $query = “update mail set status = ‘SENT’, sent = now() where mailid = $mailid”; if(db_connect()) { $result = mysql_query($query); } echo “<p>A total of $count messages were sent.”; } else if($status == ‘SENT’) { echo “<p>This mail has already been sent.”; } } } This function does several different things. It test mails the newsletter to the administrator before sending it. It keeps track of this by track- ing the status of a piece of mail in the database. When the upload script uploads a piece of mail, it sets the initial status of that mail to “STORED”. If the send() function finds that a mail has the status “STORED”, it will update this to “TESTED” and send it to the administrator. The status “TESTED” means the newsletter has been test mailed to the administrator. If the status is “TESTED”, it will be changed to “SENT” and sent to the whole list. Building a Mailing List Manager C HAPTER 28 28 BUILDING A MAILING LIST MANAGER 707 34 7842 CH28 3/6/01 3:46 PM Page 707 This means each piece of mail must essentially be sent twice: once in test mode and once in real mode. The function also sends two different kinds of email: the text version, which it sends using PHP’s mail() function; and the HTML kind, which it sends using the HTML MIME Mail class. We’ve used mail() many times in this book, so let’s look at how we use the HTML MIME Mail class. We will not cover this class comprehensively, but instead explain how we have used it in this fairly typical application. We begin by including the class file and creating an instance of the class: include(‘class.html.mime.mail.inc’); $mail = new html_mime_mail(); We then load the image details from the database and loop through them, adding each image to the piece of mail we want to send: $mail->add_html_image($image, mysql_result($result, $i, 0), mysql_result($result, $i, 1)); The three parameters we pass to add_html_image() are the actual image content as read from the file, the filename, and the file’s MIME type. We also need to add the body text, in both HTML and text formats: $mail->add_html($html, $text); Then we create the actual body of the email: $mail->build_message(); Finally, having built the message body, we can send it. We do this by retrieving and looping through each of the users subscribed to this list, and using either the HTML MIMEMIME Mail send() or regular mail() depending on the user’s MIME type preference: if($subscriber[2]==’H’) //send HTML version to people who want it $mail->send($subscriber[0], $subscriber[1], $from_name, $from_address, $subject); else //send text version to people who don’t want HTML mail mail($subscriber[0].” <”.$subscriber[1].”>”, $subject, $text, “From: $from_name <$from_address>”); The first parameter of $mail->send() should be the user’s actual name, and the second parameter should be his email address. That’s it! We have now completed building the mailing list application. Building Practical PHP and MySQL Projects P ART V 708 34 7842 CH28 3/6/01 3:46 PM Page 708 Extending the Project As usual with these projects, there are many ways you could extend the functionality. You might like to • Confirm membership with subscribers so that people can’t be subscribed without their permission. This is typically done by sending email to their accounts and deleting those who do not reply. This approach will also clean out any incorrect email addresses from the database. • Give the administrator powers to approve or reject users who want to subscribe to their lists. • Add open list functionality that allows any member to send email to the list. • Let only registered members see the archive for a particular mailing list. • Allow users to search for lists that match specific criteria. For example, users might be interested in golf newsletters. Once the number of newsletters grows past a particular size, a search would be useful to find specific ones. Next In the next chapter, we will implement a Web forum application that will enable users to have online discussions structured by topic and conversational threads. Building a Mailing List Manager C HAPTER 28 28 BUILDING A MAILING LIST MANAGER 709 34 7842 CH28 3/6/01 3:46 PM Page 709 34 7842 CH28 3/6/01 3:46 PM Page 710 CHAPTER 29 Building Web Forums 35 7842 CH29 3/6/01 3:34 PM Page 711 Building Practical PHP and MySQL Projects P ART V 712 One good way to get users to return to your site is to offer Web forums. These can be used for purposes as varied as philosophical discussion groups and product technical support. In this chapter, we implement a Web forum in PHP. An alternative is to use an existing package, such as Phorum, to set up your forums. Web forums are sometimes also called discussion boards or threaded discussion groups. The idea of a forum is that people can post articles or questions to them, and others can read and reply to their questions. Each topic of discussion in a forum is called a thread. We will implement a Web forum called blah-blah with the following functionality. Users will be able to • Start new threads of discussion by posting articles • Post articles in reply to existing articles • View articles that have been posted • View the threads of conversation in the forum • View the relationship between articles, that is, see which articles are replies to other articles The Problem Setting up a forum is actually quite an interesting problem. We will need some way of storing the articles in a database with author, title, date, and content information. At first glance this might not seem much different from the Book-O-Rama database. However, the way most threaded discussion software works is that, along with showing you the available articles, it will show you the relationship between articles. That is, you are able to see which articles are replies to other articles (and which article they’re following up) and which articles are new topics of discussion. You can see examples of discussion boards that implement this in many places, including Slashdot: http://slashdot.org Deciding how to display these relationships will require some careful thought. For this system, a user should be able to view an individual message, a thread of conversation with the relation- ships shown, or all the threads on the system. Users must also be able to post new topics or replies. This is the easy part. Solution Components As we’ve said previously, storing and retrieving the author and text of a message is easy. 35 7842 CH29 3/6/01 3:34 PM Page 712 The most difficult part of this application is finding a database structure that will store the information we want, and a way of navigating that structure efficiently. The structure of articles in a discussion might look like the one shown in Figure 29.1. Building Web Forums C HAPTER 29 29 BUILDING WEB FORUMS 713 Initial posting Reply 1 Reply 1 to Reply 1 Reply 2 Reply 3 Reply 1 to Reply 3 Reply 1 to Reply 1 FIGURE 29.1 An article in a threaded discussion might be the first article in a new topic, but more commonly it is a response to another article. In this diagram, you can see that we have an initial posting starting off a topic, with three replies. Some of the replies have replies. These replies could have replies, and so on. Looking at the diagram gives us a clue as to how we can store and retrieve the article data and the links between articles. This diagram shows a tree structure. If you’ve done much program- ming, you’ll know that this is one of the staple data structures used. In the diagram there are nodes—or articles—and links—or relationships between articles—just as in any tree structure. (If you are not familiar with trees as a data structure, don’t worry—we will cover the basics as we go.) The tricks to getting this all to work are 1. Finding a way to map this tree structure into storage—in our case, into a MySQL database. 2. Finding a way to reconstruct the data as required. We will begin by implementing a MySQL database that will enable us to store articles between use. We will build simple interfaces to enable saving of articles. When we load the list of articles for viewing, we will load the headers of each article into a tree_node PHP class. Each tree_node will contain an article’s headers and a set of the replies to that article. 35 7842 CH29 3/6/01 3:34 PM Page 713 The replies will be stored in an array. Each reply will itself be a tree_node, that can contain an array of replies to that article, which are themselves tree_nodes, and so on. This continues until we reach the so-called leaf nodes of the tree, the nodes that do not have any replies. We will then have a tree structure that looks like the one in Figure 29.1. Some terminology: The message that we are replying to can be called the parent node of the current node. Any replies to the message can be called the children of the current node. If you imagine that this tree structure is like a family tree, this will be easy to remember. The first article in this tree structure—the one with no parent—is sometimes called the root node. Building Practical PHP and MySQL Projects P ART V 714 This can be unintuitive because we usually draw the root node at the top of dia- grams, unlike the roots of real trees. NOTE To build and display this tree structure, we will write recursive functions. (We discussed recur- sion in Chapter 5, “Reusing Code and Writing Functions.”) We decided to use a class for this structure because it’s the easiest way to build a complex, dynamically expanding data structure for this application. It also means we have quite simple, elegant code to do something quite complex. Solution Overview To really understand what we have done with this project, it’s probably a good idea to work through the code, which we’ll do in a moment. There is less bulk in this application than in some of the others, but the code is a bit more complex. There are only three real pages in the application. We will have a main index page that shows all the articles in the forum as links to the articles. From here, you will be able to add a new article, view a listed article, or change the way the articles are viewed by expanding and collapsing branches of the tree. (More on this in a minute.) From the article view, you will be able to post a reply to that article or view the existing replies to that article. The new article page enables you to enter a new post, either a reply to an existing message, or a new unrelated message. The system flow diagram is shown in Figure 29.2. 35 7842 CH29 3/6/01 3:34 PM Page 714 . for purposes as varied as philosophical discussion groups and product technical support. In this chapter, we implement a Web forum in PHP. An alternative is to use an existing package, such as Phorum,. in Figure 29.1. Building Web Forums C HAPTER 29 29 BUILDING WEB FORUMS 713 Initial posting Reply 1 Reply 1 to Reply 1 Reply 2 Reply 3 Reply 1 to Reply 3 Reply 1 to Reply 1 FIGURE 29.1 An article. 710 CHAPTER 29 Building Web Forums 35 7842 CH29 3/6/01 3:34 PM Page 711 Building Practical PHP and MySQL Projects P ART V 712 One good way to get users to return to your site is to offer Web forums.