Examples - The PHP Project Guide (2014)

The PHP Project Guide (2014)

16. Examples

You’ve arrived at the fun chapter of the book where we’ll be discussing the thought process that may go into development. If you haven’t already started to think about how to approach the design, structure and functionality of just about any website yet, then hopefully this chapter will clarify this for you.

16.1 Building a forum

This is probably one of the most sought after applications to build, and can actually be relatively easy depending on how advanced you want or need to get. Most people will associate a forum with a complicated social platform packed full of features, so they fail to see what a forum actually consists of - content. Content in this form is extremely easy to display and store, and then the features of a forum can be built around this data, allowing users to interact either with forum topics, posts or other users. In this example, we’ll be looking at the following parts of a forum:

1. Ability for users to create an account and sign in

2. Listing categories

3. Listing topics

4. List posts within topics

5. Ability to post a new topic and a discussion about how we may implement replies to topics

The above is what makes a forum. Obviously, we’ll need to take user permissions into account, but this chapter isn’t technical. It’s simply to assist the thought process of building something like this. The steps you take to build forum software are very similar to 90% of websites. As we discussed earlier in the book, most websites are simply made up of the ability to display and store data.

User authentication

Your user authentication system can be built quickly and easily, but needs to be done properly and securely. It’s at this point that you may consider using a framework or at least taking advice from an expert in this area. Remember, simply using a framework doesn’t guarantee that what you’re building will be entirely secure. If you’ve already built something, address the code and look for any security issues that may cause a problem. Escaping data for SQL injection often won’t be good enough for general security, so you’ll need to consider:

· Session security

· Cookie security (if you’re implementing this functionality to remember users)

· Spam protection, so multiple accounts can’t be registered repeatedly

· Login attempt protection so your login system isn’t open to brute force attacks on user accounts

· Sanitizing user input (yes, even usernames, email addresses and other user data that may be output) to help protect against XSS attacks

· Strong password encryption

· Ensuring that passwords are salted so if hashed passwords are obtained at any point, it’s a lot harder to crack passwords using rainbow tables

· A host of other security holes that may be present in your website

Category listing

Forum software usually lists categories that can be selected and will show topics within each category. When a user makes a post, they will choose a category to post to. As with most databases, we’ll have a field in our topics table that references which category a topic belongs to, and therefore it makes sense to start with these, as they’ll be shown on the homepage. You’d start with building your table, which would probably contain the following fields:

· cat_id

· name

· description

That’s as simple as it needs to be, but can include a variety of other fields which you can always add to later. Looking at the above, this is all that’s required to start populating the table and to output these in a list. We’re simply not overcomplicating things. Now, however you’re querying your database, you’d select all three fields and you’re done. Your query may look something like this:

1 SELECT `cat_id`, `name`, `description`

2 FROM `categories`

If you need to order them specifically and think this may change in the future, add a marker to sort by. For example, you could add ‘order’ as a field and then do:

1 SELECT `cat_id`, `name`, `description`

2 FROM `categories`

3 ORDER BY `order`

4 DESC

So, if you only needed the ‘PHP’ category at the top, you could just add a 1 integer here. If you needed to continue to sort, you could have ‘PHP’ as 3, ‘MySQL’ as 2 and ‘CSS’ as 1.

Now you’ve queried, you’d naturally loop through the results, again, using the method you’ve chosen to do this. Let’s say you were outputting within an unordered list, it may end up looking something like the following:

1 <ul>

2 <li><a href="category.php?id=1">PHP</a></li>

3 <li><a href="category.php?id=2">MySQL</a></li>

4 <li><a href="category.php?id=3">CSS</a></li>

5 </ul>

You’d arrive at this output of markup by looping through and outputting the cat_id where the id= part of the URL is and the category name within the anchor element. The description has been omitted for clarity, however this could just as easily be added in. Bear in mind you may not have built category.php yet, and this doesn’t matter. You should now have a vague idea of how this may work, where in category.php we need to pick up this ID that has been passed through. Remember earlier we discussed that for every topic created, it would contain a reference to which category it had been created within? Well, the query on category.php can now pick up all topics only assigned to that category.

Listing topics within a category

We have no topics and nowhere to even store topics, so we’ll need to create the topics table first. As with most tables, let’s keep this as simple as it needs to be. It’s far too easy at this point to start wandering off and thinking about what other fields you need, so let’s create only the field we need to make this work. We can add the rest later. The fields we need are:

· topic_id

· cat_id

· user_id

· title

· timestamp

We don’t necessarily need the timestamp value stored, but it is commonplace to show when a topic was created and this also helps for ordering topics within a category. We have two fields that will reference other tables, and we’ll use these later to join data. So, in our query we could join the users and categories table to return the category name and the username (amongst any other data required) of the user who posted the topic. We’ll not get into this just yet, but let’s instead take a quick look at how we may construct a query to output a list of topics, and we’ll then look at the resulting sample output HTML. When we hit category.php we need to take in the ID passed to the URL, which would look something like:

1 $cat_id = (int)$_GET['id'];

The cat_id variable hasn’t been checked for existence and likewise we haven’t checked that this category actually exists, so you’d need to ensure this is done. The query to follow would look something like the below:

1 SELECT `topics`.`topic_id`, `topics`.`title`, `topics`.`timestamp`, `users`.`user\

2 name`, `categories`.`name`

3 FROM `topics`, `users`, `categories`

4 WHERE `topics`.`user_id` = `users`.`user_id`

5 AND `topics`.`cat_id` = `categories`.`cat_id`

6 AND `topics`.`cat_id` = $cat_id

7 ORDER BY `topics`.`timestamp`

8 DESC

Returning all topics within one category isn’t advisable, so would need to be broken up with pagination to avoid loading thousands of topics at once. This could be done with the MySQL LIMIT clause, but we won’t discuss this yet as there are a variety of different ways we can paginate.

Assuming the user has visited category.php?id= 3 (our CSS category), the resulting output would be something like:

1 <div class="topic">

2 <h2><a href="topic.php?id=1">Help with CSS</a></h2>

3 <div class="author"><a href="users.php?id=5">Billy</a></div>

4 <div class="category">in <a href="category.php?id=3">CSS</a></div>

5 </div>

We have everything we need from the query, most of which is coming from the topics table (cat_id, user_id, title) but we’ve also taken the user’s username from the users table and the category name so we can display it in a friendly manner. You may have noticed we’re not storing any post contents within the topics table, and this is simply because we’re going to create another table to house topic posts, including the first post when a topic is created. This means doing a similar things as topics, but this time linking posts to the topic they belong to.

Posts within topics

As mentioned, posts are stored in another table with a reference to what topic they belong to. This helps keep everything separate and avoids duplication of data. For example, if you were storing post text along with the topics, you’d have to duplicate the topic_title for every post, which is obviously unnecessary, a waste of space, slow, and unmaintainable. Instead, we create a posts table with the following fields:

· post_id

· topic_id

· user_id

· text

· timestamp

This is all we need to identify where each post belongs, who posted it and the ability to order the list of posts by the timestamp. So, when we land on topic.php?id=1, we need to show the posts within the first topic. We’ll also need to show metadata such as the topic title, who posted each post and perhaps some count results like how many posts there are within the topic. We can achieve all of this with a single query. There is absolutely no need to query for each part of what we need to show, which is a common problem amongst developers. Too many queries will slow down a page. We’ll assume that once we’ve hit the page, the relevant sanitization will occur on the GET value passed via the URL. The query we write may look something like:

1 SELECT `posts`.`text`, COUNT(`post_id`) as `post_count`, `topics`.`title`, `topic\

2 s`.`cat_id`,

3 `categories`.`name`, `users`.`username`

4 FROM `posts`, `topics`, `categories`, `users`

5 WHERE `posts`.`topic_id` = $topic_id

6 AND `topics`.`cat_id` = `categories`.`cat_id`

7 ORDER BY `posts`.`timestamp`

8 DESC

So we’ve now returned all the data we need, and we could return more if we like (such as user post count). We’ll skip an example for this, as the markup may get a little too long and we’ve already seen what an example of output may look like with the categories and topics listings which are similar.

You now have a functioning listing of forum categories, topics and posts within topics. It’s not been difficult at all, and the only thing left to do to make this a technically functioning forum would be to create page that allows users to post a new topic, or reply to a topic. It makes a lot of sense to keep the functionality to post a new topic and the functionality to reply to a topic the same (keeping the same code), as there are only a few minor adjustments that would be made to complete either action. For example, the URL for posting a new topic may be:

post.php

And the URL to reply to a topic may be:

post.php?id=25?reply

Where 25 is the topic_id the user is replying to. This could be built as a small asset and included into the topic.php page, as when replying the $_GET[‘id’] variable would already be present and this could just be flagged with the reply GET variable in the query string we’ve seen above. We’ll talk about the post.php page now, and how everything will be handled. We’re not going to build this as if it were to be include within topic.php, so we’ll look at it in isolation as if it were on a page of its own.

New topics and replying to topics

In this discussion, we’ll look more closely at security and validation of data considering with any application that takes and displays user input you should be very careful to do so. I’ll assume basic markup has been created to deal with entry, and you understand how forms work by submitting data either to the same page, or to another page. In this case, the action attribute for your form should be left blank in order to submit the data to the same page. The fields contained within your form should be a category dropdown, topic title, topic body and a submit button. It goes without mentioning that your user should be logged in before being able to access this page.

You already know what the topics and posts tables look like, but here’s a quick reminder of their structure:

For the topics table:

· topic_id

· cat_id

· user_id

· title

· timestamp

For the posts table:

· post_id

· topic_id

· user_id

· text

· timestamp

We already know that the category listing will show all topics from the topics table, allow the user to click through to them and then view all posts associated with that topic, so really all we need to do is populate the tables, making sure we sanitize any data going into this table. We’ll bring some validation into this discussion by briefly looking at how we should validate and sanitize.

Once you’ve submitted your form, you’ll need to pick up the category the user has chosen, the topic title and the post body (which will become the first post of this topic). The dropdown (HTML select element) should be populated using whatever function or method you previously used to list categories on the homepage. This will loop through the returned categories and output them within a select element, with each option element being the category and containing a value matching the category ID. Ensure you’re reusing whatever function or method does this, as there is no need to duplicate the query. Your output may look something like:

1 <select name="category">

2 <option value="1">PHP</option>

3 <option value="2">MySQL</option>

4 <option value="3">CSS</option>

5 </select>

When the form is submitted, you’ll then be able to access the cat_id using $_POST[‘cat_id’]. Of course, you’ll need to introduce a function or method that checks that this cat_id is valid and exists, perhaps something like:

1 public function exists($cat_id) {

2 $query = mysqli_query("

3 SELECT COUNT(`cat_id`)

4 FROM `categories`

5 WHERE `cat_id` = " . (int)$cat_id);

6

7 return ((int)mysqli_result($query, 0) === 1) ? true : false;

8 }

I’ve used non object oriented MySQLi functions within this method to simplify the example. There’s nothing strictly wrong with this anyway.

Now, this will ensure that your category exists, so we can start to sanitize other data and check for its validity. We won’t go through any code here, but let’s take a look at the topic title and the first post.

· Topic title - this should have a maximum value which should be defined in your database table also. The data type should already be a varchar, forcing you to specify the length of the field. Let’s say this is 50, which is a reasonable amount. Checking the length of $_POST[‘title’] isn’t strictly necessary as MySQL will populate the table and just truncate (cut off) any data that exceeds 50 characters. But this will generate an error, you may want to stop this and let your user know. Applying a maxlength attribute to your form field will stop any characters beyond 50 (e.g. maxlength=”50”), but this can either be changed, or data can be sent via an HTTP header or another form so is not reliable. Use the strlen function to determine the length of the title and return an error if it’s too long.

· Post - This is the same deal, although you should have the data type set to text within your database table which will allow a very large value. It’s wise to limit this in PHP, even if you allow large amounts of data. You could, for example, limit this to 10,000 characters. You could use the maxlength attribute once again here and alert the user if too much data is entered.

For both of these fields, there is absolutely no reason to trust the input. We’ve already touched on security but let’s review what we need to do with these fields to ensure it’s clean.

Use an escaping function/method native to the database you’re using, such as mysqli_real_escape_string for MySQL. This escapes characters like single quotes that could cause an SQL injection to take place within the query. Use the htmlentities function to convert any characters that may be interpreted by the browser to their entity equivalent. For example, this would change

1 <script>

to

1 &lt;script&gt;

which looks a mess in plain text, but will be shown as

1 <script>

within a browser. This effectively stops any HTML being embedded into the topic title or topic post.

You could also use strip_tags to remove any tags, but don’t rely on this alone. Your users may also need to post content with tags in without them being interpreted by the browser, so won’t want this content to be stripped.

When you’re done clearing up data, you need to insert it. The first thing to do is insert the topic data into the topic table. This creates a topic record. We then need to populate the posts table with the first post data, linking it back to the topic_id we’ve just created. But, how do we fetch the ID of the last inserted topic? That’s fairly simple and for most systems is perfectly acceptable to use. MySQL provides the mysqli_insert_id function and will return the integer of the last inserted auto_increment field (topic_id in the case of the topics table).

So, with that in mind, this code could be used to populate the topic and post table:

1 $cat_id = $_POST['category'];

2 $title = $_POST['title'];

3 $post = $_POST['post'];

4

5 /* IMPORTANT: Validation and sanitization goes here */

6

7 if (empty($errors)) {

8 mysqli_query("INSERT INTO `topics` (`cat_id`, `user_id`, `title`, `timestamp`) V\

9 ALUES ($cat_id, $user_id, $title, UNIX_TIMESTAMP())");

10

11 $topic_id = mysqli_insert_id();

12

13 mysqli_query("INSERT INTO `posts` (`topic_id`, `user_id`, `text`, `timestamp`) V\

14 ALUES ($topic_id, $user_id, $post, UNIX_TIMESTAMP())");

15 }

I’ve used the MySQLi function set once again to demonstrate this as simply as possible.

So what next? The category that posted this topic was posted to will now show a topic with the title specified. It was also to show the username of the person who posted it as when we joined the users table earlier, and clicking through will loop all posts within this section.

Now it’s your turn to build the reply functionality. Remember, this could be built directly into everything we’ve done, as the only thing to vary is that it’ll only insert a new record into the posts table with the topic being replied to and the user posting the reply. Just remember everything needs to be checked for validity and you need to implement security measures. The good thing about combining the reply functionality into the new topic form is that you’re not duplicating code and therefore can focus on the validation and sanitization of data in one area, meaning you’re less likely to forget something in either place.

We’ve not looked at code that can be directly copied, but more thought about the process and links between data and how everything should work. The point here is that if you’re unsure about building such functionality, you may be able to give it a go and even if you struggle, this will help you learn.

16.2 Pagination

Pagination is something that can apply to anything. Any data that needs to be broken up into sections or pages can take advantage of the methods we’ll be discussing here. As this is slightly more technical, we’ll look at more code and how we can query a database efficiently as not to load too much data in at once. It’s easy to load in all records from a table and then pagination, but instead it’s quicker to load in only the records you need for the page you need to display.

To start, let’s take a look at a query that returns all records from a table, guestbook.

1 SELECT `post_id`, `name`, `message`, `timestamp`

2 FROM `guestbook`

3 ORDER BY `timestamp`

4 DESC

If we had 100 records here, it’s not going to significantly slow down, but it’s inconvenient for a user to see all records output on a page, not to mention that the size of the HTML document will increase, slowing down the page load time for the user. Let’s take a look at slightly modified query using the LIMIT clause and performing an additional query to select the total number of all records. Note that we could have used sql_calc_rows_found, but some tests show that it can perform slower, particularly depending on how your database is set up.

So, we do:

1 SELECT `post_id`, `name`, `message`, `timestamp`, (SELECT (`post_id`) FROM `guest\

2 book`) as `count`

3 FROM `guestbook`

4 LIMIT 0, 10

We’ve performed a subquery to return the count of the table, despite the fact we’ve limited the query overall result set to 10. Using the LIMIT clause, we’ve started the record set at 0 (the first record) and specified we want to return 10 records. So, we return the first 10 results and our count field could contain the value of 100. We can use the overall count to work out how many pages there are based on how many we’re showing per page. The calculation for this is:

1 $pages = ceil($row_count / $per_page);

All the PHP function ceil does is round up, so, in our case:

1 $pages = ceil(100 / 10); // returns 10

This is obvious though, so let’s take a look at an example that would mean ceil had some use. Imagine we had 68 records and wanted to show 10 per page. Without ceil this would be:

1 $pages = 68 / 10;

This leaves us with 6.8 pages. Clearly this isn’t right, hence why we round up, to accommodate for the records that trickle onto the 7th page.

1 $pages = ceil(68 / 10); // 7

Now that hopefully everything makes sense, it’s easy to see how this can give great flexibility. You could give the user control over how many pages they wanted to view, perhaps using a GET value:

1 $per_page = $_GET['per_page'];

2 $per_page = ($per_page !== 0 && ($per_page <= 30 && $per_page < $row_count)) ? $p\

3 er_page : 10;

4 $pages = ceil($row_count / $per_page);

And, of course, you could check whether the user defined value is suitable for the amount of records you have, but this isn’t necessary as the code above would render anything useless (e.g. text values or a 0 value) or anything too high or larger than the record count to a default value of 10.

To list your pages, you could use a simple for loop to do something like:

1 for($p = 1; $p <= $pages; $p++) {

2 echo '<a href="?per_page=', $p, '">', $p, '</a>';

3 }

This would create a list of anchor elements (links), appending ?per_page= onto the end of each URL, which could then be picked up using the code we looked at above. You may also want to include a check to highlight the page the user is currently viewing, perhaps by adding a class to the anchor. All you need to do is compare the value of $p within the loop to the current GET value, if it is set you’ll know what the current page is.

Hopefully you’ve gained a lot from the above logic, but now we need to put it all together. This will vary depending on how and where you’re implementing pagination. For example, you could be implementing it into a generic function or a specific method that returns data for a guestbook, perhaps. Either way, the following examples should give you a general idea of how to implement pagination.

If the URL was guestbook.php?page=1:

1 public function entries($page, $per_page) {

2 $start = ((int)$page == 1) ? 0 : (int)$page * $per_page;

3

4 mysqli_query("SELECT `post_id`, `name`, `message`, `timestamp`, (SELECT (`post_i\

5 d`) FROM `guestbook`) as `count`

6 FROM `guestbook`

7 LIMIT " . $start . , " . (int)$per_page);

8

9 // resturn result set

10 }

Note that the MySQLi function set is used for clarity, instead of the object oriented solution.

We’ve now introduced this into a method, with a new variable, start. This will start at 0 if the page variable is set to 1, otherwise it’ll be increments of how many results are to be displayed per page. The result set we return will contain the count field we generated from our subquery earlier and from this the total number of pages can be calculated. This is only a taster of code but there isn’t much more involved than this. Also bear in mind that pagination will always require data sent by the client to specify what page they want to view. This means that it’s extremely important to be careful to sanitize data being using with queries. In this case, because we’re only dealing with whole numbers, I’ve simply cast these values to an integer before using them within the query.

To put what we’ve looked at into practice and to try building your own pagination system, it’s a good idea to start with a database full of data. Once you’ve got the hang of it, you can then build it into your existing application or work with it so it’s more reusable and a bit tidier and can be quickly and easily implemented anywhere on your website or another project.

16.3 AJAX Content

Once you grasp the basics of using AJAX (Asynchronous JavaScript and XML) either alone or using a library like jQuery, it becomes extremely easy to use. There are a few points that should be raised, however, including efficiency and security as well as the best way to minimise the amount of code written. In this example, we’ll look at how you may start to include AJAX requests into your website to allow content to be displayed and updated without page refreshes, as AJAX requests are simply asynchronous requests to files. The nature of this makes it extremely useful to process PHP within these requests, which then means data can be either returned and displayed to the user, a user can update something and receive a confirmation message or you can perform periodic checks for new data (e.g. a chat window pulling in data every 10 seconds). The best reason to incorporate AJAX requests is to aid UX (User Experience), so don’t use it unnecessarily just for the sake of it. At points when a user is posting a forum topic, for example, it’s customary to see a user redirected to the topic they just posted and therefore an AJAX request might be unnecessary here. You may, however, incorporate client-side JavaScript validation to enhance UX. However, remember to include server-side validation in addition to this.

Whatever you decide to use AJAX for, the following examples will help you grasp this. We’ll be using jQuery to perform requests simply because jQuery has been built with support in mind, meaning that it’s quick implementation will work across the majority of browsers. jQuery also provides a rich set of options for AJAX requests and allows us to very easily return data and process it, include traversing through XML nodes (although not part of the jQuery AJAX method). You can find short examples of basic usage of the jQuery AJAX method at the official jQuery documentation pages.

Simple processing of data

The example we’ll look at is submitting an email address that will be stored in a mailing list table within the website’s main database. This will involve an email address field and a submit button, and will include full server side validation. We’re doing the validation server side because we’ll return an error that will be shown to the user instantly anyway. With websites with high traffic, you may want to include client-side validation here to save requests to the server, but it’ll complicate things in this example.

We’ll start by identifying what resources we need. We need:

1. The jQuery library

2. Our own JavaScript file

3. Basic markup

4. A PHP file to process and send the validated, sanitized email address to our database.

The jQuery library and your own external JavaScript file should be loaded into your page just before your ending body tag, and will look something like:

1 <script src="ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>

2 <script src="js/global.js"></script>

You’ll notice the jQuery version we’re pulling in is from Google’s CDN. This almost always makes the request to the file faster and means it’s more than likely cached for users that visit your website. Our own JavaScript file is located within the js directory of our server and at this stage should be a blank file.

We then need some basic markup on our page in order to display a field and submit button to our user:

1 <div id="newsletter">

2 <input type="text" class="email" placeholder="Enter your email address...">

3 <a href="#" class="submit">Sign up</a>

4 <div class="message"></div>

5 </div>

Here we have a wrapper container, a field to enter an email address with HTML5 placeholder text to guide the user, and an anchor. We’re using an anchor simply because styling this is a lot easier than submit button, and we’re not using a form element anyway. Because the data isn’t directly submitted to a page through the use of the default HTML form functionality we can leave this out. We also have an element that serves as a container for any data returned from our request so we can display errors or confirmation messages.

Now for the jQuery code. At present, the following is incomplete and only returns what the PHP file we’re addressing outputs. This allows us to determine whether the AJAX request has been successful or not because we’ll see the data output from newsletter.php to us within a JavaScript alert dialog.

1 $('#newsletter .submit').on('click', function() {

2 var email_entry = $(this).parent().find('.email').val();

3 $.post('../ajax/newsletter.php', {email: email_entry}, function(data) {

4 alert(data);

5 });

6 });

You can see we’re sending the data to newsletter.php within the ajax folder of our root directory. We’re traversing back a directory because this file is located within the js directory. Above, when a user clicks the submit button we store the email address with a variable in the scope of the event handler. This is then passed to the post method of jQuery and sent, named ‘email’. After this request is sent, we see a callback function with the argument ‘data’ which contains whatever has been returned by newsletter.php, which we place into a JavaScript alert. We’re using the jQuery post method and not the AJAX method, as the AJAX method uses post or get anyway. If we needed to provide more options we could have written the above the following way, although at this stage unnecessary:

1 $.ajax({

2 url: '../ajax/newsletter.php',

3 type: 'post',

4 'success': function(data) {

5 alert(data);

6 }

7 });

Now we know that we’re successfully sending the request to newsletter.php, create the below file in the ajax directory. The code we then need to output at least something is:

1 <?php

2 if (isset($_POST['email'])) {

3 $email = trim($_POST['email']);

4 echo $email;

5 }

You can see we’re accessing the email value sent using POST. This is simply because all that the jQuery post method is doing is sending the data as an HTTP request, so is no different from using a form, it just does it without needing to refresh the page. We’re also just simply using echo to output the email address value, trimmed (useful for validating later). This will be carried over into the data argument we used within the callback function and will be output using a JavaScript alert dialog we implemented earlier.

Now we’re done, we can validate, sanitize and use whatever method we’re using to insert into the database. We won’t show the code to check if the email exists, or the code that actually inserts the email address, because it doesn’t directly relate to the point of demonstrating AJAX. However, here’s what you may do to validate, including some comments where action points may be required:

1 <?php

2 if (isset($_POST['email'])) {

3 $errors = array();

4 $email = trim($_POST['email']);

5

6 if (empty($email)) {

7 $errors[] = 'Email address required';

8 } else {

9 if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {

10 $errors[] = 'That\'s not a valid email address';

11 }

12

13 if (strlen($email) > 100) {

14 $errors[] = 'Your email address is too long';

15 }

16

17 /* apply a check here for whether the email address exists in the database alre\

18 ady */

19 }

20

21 if (!empty($errors)) {

22 echo '<ul><li>', explode('</li><li>', $errors), '</li></ul>';

23 } else {

24 /* insert email address into database, remembering to sanitize appropriately */

25 echo 'Thanks, we\'ve stored your email';

26 }

27 }

28 ?>

On the client side, you would then remove the JavaScript alert and replace this with something like:

1 $('#newsletter .submit').on('click', function() {

2 var this_ = $(this),

3 email_entry = this_.parent().find('.email').val(),

4 message = this_.parent().find('.message');

5

6 $.post('../ajax/newsletter.php', {email: email_entry}, function(data) {

7 message.text(data);

8 });

9 })

We’ve done some things slightly differently here, and introduced two new variables. We’re caching the $(this) selector so it can be used within the callback function of the AJAX request (as otherwise it’ll refer to the object returned) and we’ve also selected the message element so we can inject the output from our PHP file into it.