Finally the image is saved as a jpeg file in the location defined by the variable $saveto, after which both the original and the resized image canvases are removed from memory using the imagedestroy function, returning the memory that was used. Displaying the Current Profile Last but not least, so that the user can see what the current profile looks like before editing it, the showProfile function from rnfunctions.php is called prior to outputting the form HTML. If no profile exists yet, nothing will be displayed. The result of loading Example 20-9 into a browser is shown in Figure 20-4, where you can see that the textarea has been prepopulated with the “about me” text. Figure 20-4. Editing a user profile rnprofile.php | 421 Example 20-9. rnprofile.php <?php // rnprofile.php include_once 'rnheader.php'; if (!isset($_SESSION['user'])) die("<br /><br />You need to login to view this page"); $user = $_SESSION['user']; echo "<h3>Edit your Profile</h3>"; if (isset($_POST['text'])) { $text = sanitizeString($_POST['text']); $text = preg_replace('/\s\s+/', ' ', $text); $query = "SELECT * FROM rnprofiles WHERE user='$user'"; if (mysql_num_rows(queryMysql($query))) { queryMysql("UPDATE rnprofiles SET text='$text' where user='$user'"); } else { $query = "INSERT INTO rnprofiles VALUES('$user', '$text')"; queryMysql($query); } } else { $query = "SELECT * FROM rnprofiles WHERE user='$user'"; $result = queryMysql($query); if (mysql_num_rows($result)) { $row = mysql_fetch_row($result); $text = stripslashes($row[1]); } else $text = ""; } $text = stripslashes(preg_replace('/\s\s+/', ' ', $text)); if (isset($_FILES['image']['name'])) { $saveto = "$user.jpg"; move_uploaded_file($_FILES['image']['tmp_name'], $saveto); $typeok = TRUE; switch($_FILES['image']['type']) { case "image/gif": $src = imagecreatefromgif($saveto); break; case "image/jpeg": // Both regular and progressive jpegs case "image/pjpeg": $src = imagecreatefromjpeg($saveto); break; 422 | Chapter 20: Bringing It All Together case "image/png": $src = imagecreatefrompng($saveto); break; default: $typeok = FALSE; break; } if ($typeok) { list($w, $h) = getimagesize($saveto); $max = 100; $tw = $w; $th = $h; if ($w > $h && $max < $w) { $th = $max / $w * $h; $tw = $max; } elseif ($h > $w && $max < $h) { $tw = $max / $h * $w; $th = $max; } elseif ($max < $w) { $tw = $th = $max; } $tmp = imagecreatetruecolor($tw, $th); imagecopyresampled($tmp, $src, 0, 0, 0, 0, $tw, $th, $w, $h); imageconvolution($tmp, array( // Sharpen image array(−1, −1, −1), array(−1, 16, −1), array(−1, −1, −1) ), 8, 0); imagejpeg($tmp, $saveto); imagedestroy($tmp); imagedestroy($src); } } showProfile($user); echo <<<_END <form method='post' action='rnprofile.php' enctype='multipart/form-data'> Enter or edit your details and/or upload an image:<br /> <textarea name='text' cols='40' rows='3'>$text</textarea><br /> Image: <input type='file' name='image' size='14' maxlength='32' /> <input type='submit' value='Save Profile' /> </pre></form> _END; ?> rnprofile.php | 423 rnmembers.php Using Example 20-10, rnmembers.php, your users will be able to find other members and choose to add them as friends (or drop them if they are already friends). This program has two modes. The first lists all members and their relationships to you, and the second shows a user’s profile. Viewing a User’s Profile The code for the latter mode comes first, where a test is made for the GET variable 'view'. If it exists, a user wants to view someone’s profile, so the program does that using the showProfile function, along with providing a couple of links to the user’s friends and messages. Adding and Dropping Friends After that the two GET variables 'add' and 'remove' are tested. If one or the other has a value, it will be the username of a user to either add or drop as a friend. This is achieved by looking the user up in the MySQL rnfriends table and either inserting a friend username or removing it from the table. And, of course, every posted variable is first passed through sanitizeString to ensure it is safe to use with MySQL. Listing All Members The final section of code issues a SQL query to list all usernames. The code places the number returned in the variable $num before outputting the page heading. A for loop then iterates through each and every member, fetching their details and then looking them up in the rnfriends table to see if they are either being followed by or a follower of the user. If someone is both a follower and a followee, they are classed as a mutual friend. By the way, this section of code is particularly amenable to a template solution such as Smarty. The variable $t1 is nonzero when the user is following another member, and $t2 is nonzero when another member is following the user. Depending on these values, text is displayed after each username showing their relationship (if any) to the current user. Icons are also displayed to show the relationships. A double pointing arrow means that the users are mutual friends. A left-pointing arrow indicates the user is following an- other member. And a right-pointing arrow indicates that another member is following the user. Finally, depending on whether the user is following another member, a link is provided to either add or drop that member as a friend. 424 | Chapter 20: Bringing It All Together When you call Example 20-10 up in a browser, it will look like Figure 20-5. See how the user is invited to “follow” a nonfollowing member, but if the member is already following the user, a “recip” link to reciprocate the friendship is offered. In the case of a user already following another member, the user can select “drop” to stop the following. Figure 20-5. Using the members module Example 20-10. rnmembers.php <?php // rnmembers.php include_once 'rnheader.php'; if (!isset($_SESSION['user'])) die("<br /><br />You must be logged in to view this page"); $user = $_SESSION['user']; if (isset($_GET['view'])) { $view = sanitizeString($_GET['view']); if ($view == $user) $name = "Your"; else $name = "$view's"; echo "<h3>$name Page</h3>"; showProfile($view); echo "<a href='rnmessages.php?view=$view'>$name Messages</a><br />"; die("<a href='rnfriends.php?view=$view'>$name Friends</a><br />"); } rnmembers.php | 425 if (isset($_GET['add'])) { $add = sanitizeString($_GET['add']); $query = "SELECT * FROM rnfriends WHERE user='$add' AND friend='$user'"; if (!mysql_num_rows(queryMysql($query))) { $query = "INSERT INTO rnfriends VALUES ('$add', '$user')"; queryMysql($query); } } elseif (isset($_GET['remove'])) { $remove = sanitizeString($_GET['remove']); $query = "DELETE FROM rnfriends WHERE user='$remove' AND friend='$user'"; queryMysql($query); } $result = queryMysql("SELECT user FROM rnmembers ORDER BY user"); $num = mysql_num_rows($result); echo "<h3>Other Members</h3><ul>"; for ($j = 0 ; $j < $num ; ++$j) { $row = mysql_fetch_row($result); if ($row[0] == $user) continue; echo "<li><a href='rnmembers.php?view=$row[0]'>$row[0]</a>"; $query = "SELECT * FROM rnfriends WHERE user='$row[0]' AND friend='$user'"; $t1 = mysql_num_rows(queryMysql($query)); $query = "SELECT * FROM rnfriends WHERE user='$user' AND friend='$row[0]'"; $t2 = mysql_num_rows(queryMysql($query)); $follow = "follow"; if (($t1 + $t2) > 1) { echo " ↔ is a mutual friend"; } elseif ($t1) { echo " ← you are following"; } elseif ($t2) { $follow = "recip"; echo " → is following you"; } if (!$t1) { 426 | Chapter 20: Bringing It All Together echo " [<a href='rnmembers.php?add=".$row[0] . "'>$follow</a>]"; } else { echo " [<a href='rnmembers.php?remove=".$row[0] . "'>drop</a>]"; } } ?> On a production server, there could be thousands or even hundreds of thousands of users, so you would probably substantially modify this program to include searching the “about me” text, and support paging of the output a screen at a time. rnfriends.php The module that shows a user’s friends and followers is Example 20-11, rnfriends.php. This interrogates the rnfriends table just like the rnmembers.php pro- gram, but only for a single user. It then shows all of that user’s mutual friends and followers along with the people they are following. All the followers are saved into an array called $followers and all the people being followed are placed in an array called $following. Then a neat piece of code is used to extract all those that are both following and followed by the user, like this: $mutual = array_intersect($followers, $following); The array_intersect function extracts all members common to both arrays and returns a new array containing only those people. This array is then stored in $mutual. Now it’s possible to use the array_diff function for each of the $followers and $following arrays to keep only those people who are not mutual friends, like this: $followers = array_diff($followers, $mutual); $following = array_diff($following, $mutual); This results in the array $mutual containing only mutual friends, $followers containing only followers (and no mutual friends), and $following containing only people being followed (and no mutual friends). Armed with these arrays, it’s a simple matter to separately display each category of members, as can be seen in Figure 20-6. The PHP sizeof function returns the number of elements in an array; here I use it just to trigger code when the size is nonzero (that is, friends of that type exist). Note how, by using the variables $name1, $name2, and $name3 in the relevant places, the code can tell when you’re looking at your own friends list, using the words Your and You are, instead of simply displaying the username. rnfriends.php | 427 Figure 20-6. Displaying a user’s friends and followers Example 20-11. rnfriends.php <?php // rnfriends.php include_once 'rnheader.php'; if (!isset($_SESSION['user'])) die("<br /><br />You need to login to view this page"); $user = $_SESSION['user']; if (isset($_GET['view'])) $view = sanitizeString($_GET['view']); else $view = $user; if ($view == $user) { $name1 = "Your"; $name2 = "Your"; $name3 = "You are"; } else { $name1 = "<a href='rnmembers.php?view=$view'>$view</a>'s"; $name2 = "$view's"; $name3 = "$view is"; } echo "<h3>$name1 Friends</h3>"; showProfile($view); $followers = array(); $following = array(); 428 | Chapter 20: Bringing It All Together $query = "SELECT * FROM rnfriends WHERE user='$view'"; $result = queryMysql($query); $num = mysql_num_rows($result); for ($j = 0 ; $j < $num ; ++$j) { $row = mysql_fetch_row($result); $followers[$j] = $row[1]; } $query = "SELECT * FROM rnfriends WHERE friend='$view'"; $result = queryMysql($query); $num = mysql_num_rows($result); for ($j = 0 ; $j < $num ; ++$j) { $row = mysql_fetch_row($result); $following[$j] = $row[0]; } $mutual = array_intersect($followers, $following); $followers = array_diff($followers, $mutual); $following = array_diff($following, $mutual); $friends = FALSE; if (sizeof($mutual)) { echo "<b>$name2 mutual friends</b><ul>"; foreach($mutual as $friend) echo "<li><a href='rnmembers.php?view=$friend'>$friend</a>"; echo "</ul>"; $friends = TRUE; } if (sizeof($followers)) { echo "<b>$name2 followers</b><ul>"; foreach($followers as $friend) echo "<li><a href='rnmembers.php?view=$friend'>$friend</a>"; echo "</ul>"; $friends = TRUE; } if (sizeof($following)) { echo "<b>$name3 following</b><ul>"; foreach($following as $friend) echo "<li><a href='rnmembers.php?view=$friend'>$friend</a>"; $friends = TRUE; } if (!$friends) echo "<ul><li>None yet"; rnfriends.php | 429 echo "</ul><a href='rnmessages.php?view=$view'>View $name2 messages</a>"; ?> rnmessages.php The last of the main modules is Example 20-12, rnmessages.php. The program starts by checking whether a message has been posted in the POST variable 'text'. If so, it is inserted into the rnmessages table. At the same time, the value of 'pm' is also stored. This indicates whether a message is private or public. A 0 represents a public message and 1 is private. Next, the user’s profile and a form for entering a message are displayed, along with radio buttons to choose between a private or public message. After this, all the messages are shown, depending on whether they are private or public. If they are public, all users can see them, but private messages are visible only to the sender and recipient. This is all handled by a couple of queries to the MySQL database. Additionally, when a mes- sage is private, it is introduced by the word “whispered” and shown in italic. Finally, the program displays a couple of links to refresh the messages, in case another user has posted one in the meantime, and to view the user’s friends. The trick using the variables $name1 and $name2 is again used so that when you view your own profile the word Your is displayed instead of the username. You can see the result of viewing this program with a browser in Figure 20-7. Note how users viewing their own messages are provided with links to erase any they don’t want. Figure 20-7. The messaging module 430 | Chapter 20: Bringing It All Together . been prepopulated with the “about me” text. Figure 2 0-4 . Editing a user profile rnprofile.php | 421 Example 2 0-9 . rnprofile.php <?php // rnprofile.php include_once 'rnheader.php'; if. /> <input type='submit' value='Save Profile' /> </pre></form> _END; ?> rnprofile.php | 423 rnmembers.php Using Example 2 0-1 0, rnmembers .php, your users. simply displaying the username. rnfriends.php | 427 Figure 2 0-6 . Displaying a user’s friends and followers Example 2 0-1 1. rnfriends.php <?php // rnfriends.php include_once 'rnheader.php'; if