First, I would recommend that you use Mysqli Prepared Statements. These are more secure than the escape functions as it entirely separates the query structure from the query parameters.
You can use either strip_tags or htmlentities to make certain that no malicious code is being output to the browser. You'll also probably want to run all dynamic content through some sort of filter function before outputting it to the browser. This is one area where a well-organized and cohesive OO approach can help because you can assign all dynamic content to a single object which is then responsible for rendering the output to the browser so that you can always feel safe knowing that your content is XSS safe without having to search through hundreds of echo statements across multiple pages.
However, these two suggestions really only touch the tip of the iceberg when it comes to PHP security. There are a number of other issues that have to also be dealt with such as:
I would recommend that you take a look at the following sites for additional, and more comprehensive treatment on the topic of PHP security:
- shiflett.org
- phpsecurity.org (This is another Chris Shiflett site, but I'm not certain whether he has additional content here that isn't on his personal site)
- phpsec.org Not as much content there as I would like, but there are some useful things to know such as making certain configuration files that contain sensitive information (like database passwords) are not stored in publicly available directories.
On that final item, if it is possible, you will want to look into using .htaccess files or their IIS equivalent to make certain only the files you intend access to are publicly available. Otherwise, all of your other security measures will be vain.
Update: I thought of a couple other issues relating to security in your project. If you remember the unfortunate incident with a Twitter administrator's account being hacked a little bit ago, it was allowed to happen for several reasons that could have all been easily resolved.
1) Password log in attempts where not throttled. In other words if a user attempts to log in and fails then a repeat request from their IP might require 2 seconds to process the second time, then 4 the next and so on. This makes it impractical for a bot to make continuous log in attempts.
2) Secure passwords were not required. As passwords have higher requirements (length and types of characters required such as alphanumeric and special characters), then dictionary attacks become altogether impractical because it is much easier to throw a dictionary at hacking a password than it is to come up with random strings of numbers and letters. Also the amount of time crack a password increases exponentially as more characters are added.
You can see this post from Jeff Atwood for more information on this particular topic.