.

Sunday, 7 September 2014

10 Advanced PHP Tips by P. K. Priyadarshi

1. Use an SQL Injection Cheat Sheet

This particular tip is just a link to a useful resource with no discussion on how to use it. Studying various permutations of one specific attack can be useful, but your time is better spent learning how to safeguard against it. Additionally, there is much more to Web app security than SQL injection. XSS (Cross-Site Scripting) and CSRF (Cross-Site Request Forgeries), for example, are at least as common and at least as dangerous.
We can provide some much-needed context, but because we don’t want to focus too much on one attack, we’ll first take a step back. Every developer should be familiar with good security practices, and apps should be designed with these practices in mind. A fundamental rule is to never trust data you receive from somewhere else. Another rule is to escape data before you send it somewhere else. Combined, these rules can be simplified to make up a basic tenet of security: filter input, escape output (FIEO).
The root cause of SQL injection is a failure to escape output. More specifically, it is when the distinction between the format of an SQL query and the data used by the SQL query is not carefully maintained. This is common in PHP apps that construct queries as follows:


<?php

$query = "SELECT *
FROM users
WHERE name = '{$_GET['name']}'";

?>
 
 
In this case, the value of $_GET['name'] is provided by another source, the user, but it is neither filtered nor escaped.
Escaping preserves data in a new context. The emphasis on escaping output is a reminder that data used outside of your Web app needs to be escaped, else it might be misinterpreted. By contrast, filtering ensures that data is valid before it’s used. The emphasis on filtering input is a reminder that data originating outside of your Web app needs to be filtered, because it cannot be trusted.
Assuming we're using MySQL, the SQL injection vulnerability can be mitigated by escaping the name with mysql_real_escape_string(). If the name is also filtered, there is an additional layer of security. (Implementing multiple layers of security is called "defense in depth" and is a very good security practice.) The following example demonstrates filtering input and escaping output, with naming conventions used for code clarity:
 
 
<?php

// Initialize arrays for filtered and escaped data, respectively.
$clean = array();
$sql = array();

// Filter the name. (For simplicity, we require alphabetic names.)
if (ctype_alpha($_GET['name'])) {
$clean['name'] = $_GET['name'];
} else {
// The name is invalid. Do something here.
}

// Escape the name.
$sql['name'] = mysql_real_escape_string($clean['name']);

// Construct the query.
$query = "SELECT *
FROM users
WHERE name = '{$sql['name']}'";

?>
 
 
Although the use of naming conventions can help you keep up with what has and hasn't been filtered, as well as what has and hasn't been escaped, a much better approach is to use prepared statements. Luckily, with PDO, PHP developers have a universal API for data access that supports prepared statements, even if the underlying database does not.
Remember, SQL injection vulnerabilities exist when the distinction between the format of an SQL query and the data used by the SQL query is not carefully maintained. With prepared statements, you can push this responsibility to the database by providing the query format and data in distinct steps:
 
 
<?php

// Provide the query format.
$query = $db->prepare('SELECT *
FROM users
WHERE name = :name');

// Provide the query data and execute the query.
$query->execute(array('name' => $clean['name']));

?>
 
 
The PDO manual page provides more information and examples. Prepared statements offer the strongest protection against SQL injection.

2. Know the Difference Between Comparison Operators

This is a good tip, but it is missing a practical example that demonstrates when a non-strict comparison can cause problems.
If you use strpos() to determine whether a substring exists within a string (it returns FALSE if the substring is not found), the results can be misleading:
 
 
<?php

$authors = 'Chris & Sean';

if (strpos($authors, 'Chris')) {
echo 'Chris is an author.';
} else {
echo 'Chris is not an author.';
}

?>
 
 
Because the substring Chris occurs at the very beginning of Chris & Sean, strpos() correctly returns 0, indicating the first position in the string. Because the conditional statement treats this as a Boolean, it evaluates to FALSE, and the condition fails. In other words, it looks like Chris is not an author, but he is!
This can be corrected with a strict comparison:
 
 
<?php

if (strpos($authors, 'Chris') !== FALSE) {
echo 'Chris is an author.';
} else {
echo 'Chris is not an author.';
}

?>
 
 

3. Shortcut the else

This tip accidentally stumbles upon a useful practice, which is to always initialize variables before you use them. Consider a conditional statement that determines whether a user is an administrator based on the username:
 
 
<?php

if (auth($username) == 'admin') {
$admin = TRUE;
} else {
$admin = FALSE;
}

?>
 
 
This seems safe enough, because it’s easy to comprehend at a glance. Imagine a slightly more elaborate example that sets variables for name and email as well, for convenience:
 
 
<?php

if (auth($username) == 'admin') {
$name = 'Administrator';
$email = 'admin@example.org';
$admin = TRUE;
} else {
/* Get the name and email from the database. */
$query = $db->prepare('SELECT name, email
FROM users
WHERE username = :username');
$query->execute(array('username' => $clean['username']));
$result = $query->fetch(PDO::FETCH_ASSOC);
$name = $result['name'];
$email = $result['email'];
$admin = FALSE;
}

?>
 
 
Because $admin is still always explicitly set to either TRUE or FALSE, all is well, but if a developer later adds an elseif, there’s an opportunity to forget:
 
 
<?php

if (auth($username) == 'admin') {
$name = 'Administrator';
$email = 'admin@example.org';
$admin = TRUE;
} elseif (auth($username) == 'mod') {
$name = 'Moderator';
$email = 'mod@example.org';
$moderator = TRUE;
} else {
/* Get the name and email. */
$query = $db->prepare('SELECT name, email
FROM users
WHERE username = :username');
$query->execute(array('username' => $clean['username']));
$result = $query->fetch(PDO::FETCH_ASSOC);
$name = $result['name'];
$email = $result['email'];
$admin = FALSE;
$moderator = FALSE;
}

?>
 
 
If a user provides a username that triggers the elseif condition, $admin is not initialized. This can lead to unwanted behavior, or worse, a security vulnerability. Additionally, a similar situation now exists for $moderator, which is not initialized in the first condition.
By first initializing $admin and $moderator, it’s easy to avoid this scenario altogether:
 
 
<?php

$admin = FALSE;
$moderator = FALSE;

if (auth($username) == 'admin') {
$name = 'Administrator';
$email = 'admin@example.org';
$admin = TRUE;
} elseif (auth($username) == 'mod') {
$name = 'Moderator';
$email = 'mod@example.org';
$moderator = TRUE;
} else {
/* Get the name and email. */
$query = $db->prepare('SELECT name, email
FROM users
WHERE username = :username');
$query->execute(array('username' => $clean['username']));
$result = $query->fetch(PDO::FETCH_ASSOC);
$name = $result['name'];
$email = $result['email'];
}

?>
 
 
Regardless of what the rest of the code does, it’s now clear that $admin is FALSE unless it is explicitly set to something else, and the same is true for $moderator. This also hints at another good security practice, which is to fail safely. The worst that can happen as a result of not modifying $admin or $moderator in any of the conditions is that someone who is an administrator or moderator is not treated as one.
If you want to shortcut something, and you’re feeling a little disappointed that our example includes an else, we have a bonus tip that might interest you. We’re not certain it can be considered a shortcut, but we hope it’s helpful nonetheless.
Consider a function that determines whether a user is authorized to view a particular page:
 
 
<?php

function authorized($username, $page) {
if (!isBlacklisted($username)) {
if (isAdmin($username)) {
return TRUE;
} elseif (isAllowed($username, $page)) {
return TRUE;
} else {
return FALSE;
}
} else {
return FALSE;
}
}

?>
 
 
This example is actually pretty simple, because there are only three rules to consider: administrators are always allowed access; those who are blacklisted are never allowed access; and isAllowed() determines whether anyone else has access. (A special case exists when an administrator is blacklisted, but that is an unlikely possibility, so we’re ignoring it here.) We use functions for the rules to keep the code simple and to focus on the logical structure.
There are numerous ways this example can be improved. If you want to reduce the number of lines, a compound conditional can help:
 
 
<?php

function authorized($username, $page) {
if (!isBlacklisted($username)) {
if (isAdmin($username) || isAllowed($username, $page)) {
return TRUE;
} else {
return FALSE;
}
} else {
return FALSE;
}
}

?>
 
 
In fact, you can reduce the entire function to a single compound conditional:
 
 
<?php

function authorized($username, $page) {
if (!isBlacklisted($username) && (isAdmin($username) || isAllowed($username, $page)) {
return TRUE;
} else {
return FALSE;
}
}

?>
Finally, this can be reduced to a single return:
<?php

function authorized($username, $page) {
return (!isBlacklisted($username) && (isAdmin($username) || isAllowed($username, $page));
}

?>
 
 
If your goal is to reduce the number of lines, you’re done. However, note that we’re using isBlacklisted(), isAdmin(), and isAllowed() as placeholders. Depending on what’s involved in making these determinations, reducing everything to a compound conditional may not be as attractive.
This brings us to our tip. A return immediately exits the function, so if you return as soon as possible, you can express these rules very simply:
 
 
<?php

function authorized($username, $page) {

if (isBlacklisted($username)) {
return FALSE;
}

if (isAdmin($username)) {
return TRUE;
}

return isAllowed($username, $page);
}

?>
 
 
This uses more lines of code, but it’s very simple and unimpressive (we’re proudest of our code when it’s the least impressive). More importantly, this approach reduces the amount of context you must keep up with. For example, as soon as you’ve determined whether the user is blacklisted, you can safely forget about it. This is particularly helpful when your logic is more complicated.

4. Drop Those Brackets

Based on the content of this tip, we believe the author means "braces," not brackets. "Curly brackets" may mean braces to some, but "brackets" universally means "square brackets."
This tip should be unconditionally ignored. Without braces, readability and maintainability are damaged. Consider a simple example:
 
 
<?php

if (date('d M') == '21 May')
$birthdays = array('Al Franken',
'Chris Shiflett',
'Chris Wallace',
'Lawrence Tureaud');

?>
If you’re good enough, smart enough, secure enough, notorious enough, or pitied enough, you might want to party on the 21st of May:
<?php

if (date('d M') == '21 May')
$birthdays = array('Al Franken',
'Chris Shiflett',
'Chris Wallace',
'Lawrence Tureaud');
party(TRUE);

?>
 
 
Without braces, this simple addition causes you to party every day. Perhaps you have the stamina for it, so the mistake is a welcome one. Hopefully, the silly example doesn’t detract from the point, which is that the excessive partying is an unintended side effect.
In order to promote the practice of dropping braces, the previous article uses short examples such as the following:
 
 
<?php

if ($gollum == 'halfling') $height --;
else $height ++;

?>
 
 
Because each condition is constrained to a single line, such mistakes might be less likely, but this leads to another problem: inconsistencies are jarring and require more time to read and comprehend. Consistency is such a valued quality that developers often abide by a coding standard even if they dislike the coding standard itself.
We recommend always using braces:
 
 
<?php

if (date('d M') == '21 May') {
$birthdays = array('Al Franken',
'Chris Shiflett',
'Chris Wallace',
'Lawrence Tureaud');
party(TRUE);
}

?>
 
 
You’re welcome to party every day, but make sure it’s deliberate, and please be sure to invite us!

5. Favor str_replace() Over ereg_replace() and preg_replace()

We hate to sound disparaging, but this tip demonstrates the sort of misunderstanding that leads to the same misuse it’s trying to prevent. It’s an obvious truth that string functions are faster at string matching than regular expression functions, but the author’s attempt to draw a corollary from this fails miserably:
If you’re using regular expressions, then ereg_replace() and preg_replace() will be much faster than str_replace().
Because str_replace() does not support pattern matching, this statement makes no sense. The choice between string functions and regular expression functions comes down to which is fit for purpose, not which is faster. If you need to match a pattern, use a regular expression function. If you need to match a string, use a string function.

6. Use Ternary Operators

The benefit of the ternary operator is debatable (there’s only one, by the way). Here is a line of code from an audit we performed recently:
 
 
<?php

$host = strlen($host) > 0 ? $host : htmlentities($host);

?>
 
 
Oops! The author actually means to escape $host if the string length is greater than zero, but instead accidentally does the opposite. Easy mistake to make? Maybe. Easy to miss during a code audit? Certainly. Concision doesn’t necessarily make the code any better.
The ternary operator may be fine for one-liners, prototypes, and templates, but we strongly believe that an ordinary conditional statement is almost always better. PHP is descriptive and verbose. We think code should be, too.

7. Memcached

Disk access is slow. Network access is slow. Databases typically use both.
Memory is fast. Using a local cache avoids the overhead of network and disk access. Combine these truths and you get memcached, a “distributed memory object caching system” originally developed for the Perl-based blogging platform LiveJournal.
If your application isn’t distributed across multiple servers, you probably don’t need memcached. Simpler caching approaches — serializing data and storing it in a temporary file, for example — can eliminate a lot of redundant work on each request. In fact, this is the sort of low-hanging fruit we consider when helping our clients tune their apps.
One of the easiest and most universal ways to cache data in memory is to use the shared memory helpers in APC, a caching system originally developed by our colleague George Schlossnagle. Consider the following example:
 
 
<?php

$feed = apc_fetch('news');

if ($feed === FALSE) {
$feed = file_get_contents('http://example.org/news.xml');
// Store this data in shared memory for five minutes.
apc_store('news', $feed, 300);
}

// Do something with $feed.

?>
 
 
With this type of caching, you don’t have to wait on a remote server to send the feed data for every request. Some latency is incurred — up to five minutes in this example — but this can be adjusted to as close to real time as your app requires.

8. Use a Framework

All decisions have consequences. We appreciate frameworks — in fact, the main developers behind CakePHP and Solar work with us at OmniTI — but using one doesn’t magically make what you’re doing better.
In December, our colleague Paul Jones wrote an article for PHP Advent called The Framework as Franchise, in which he compares frameworks to business franchises. He refers to a suggestion by Michael Gerber from his book "The E-Myth Revisited":
Gerber notes that to run a successful business, the entrepreneur needs to act as if he is going to sell his business as a franchise prototype. It is the only way the business owner can make the business operate without him being personally involved in every decision.
This is good advice. Whether you’re using a framework or defining your own standards and conventions, it’s important to consider the value from the perspective of future developers.
Although we would love to give you a universal truth, extending this idea to suggest that a framework is always appropriate isn’t something we’re willing to do. If you ask us whether you should use a framework, the best answer we could give is, “It depends.”

9. Use the Suppression Operator Correctly

Always try to avoid using the error suppression operator. In the previous article, the author states:
The @ operator is rather slow and can be costly if you need to write code with performance in mind.
Error suppression is slow. This is because PHP dynamically changes error_reporting to 0 before executing the suppressed statement, then immediately changes it back. This is expensive.
Worse, using the error suppression operator makes it difficult to track down the root cause of a problem.
The previous article uses the following example to support the practice of assigning a variable by reference when it is unknown if $albus is set:
 
 
<?php

$albert =& $albus;

?>
 
 
Although this works — for now — relying on strange, undocumented behavior without a very good understanding of why it works is a good way to introduce bugs. Because $albert is assigned to $albus by reference, future modifications to $albus will also modify $albert.
A much better solution is to use isset(), with braces:
 
 
<?php

if (!isset($albus)) {
$albert = NULL;
}

?>
 
 
Assigning $albert to NULL is the same as assigning it to a nonexistent reference, but being explicit greatly improves the clarity of the code and avoids the referential relationship between the two variables.
If you inherit code that uses the error suppression operator excessively, we’ve got a bonus tip for you. There is a new PECL extension called Scream that disables error suppression.

10. Use isset() Instead of strlen()

This is actually a neat trick, although the previous article completely fails to explain it. Here is the missing example:
 
 
<?php

if (isset($username[5])) {
// The username is at least six characters long.
}

?>
 
 
When you treat strings as arrays, each character in the string is an element in the array. By determining whether a particular element exists, you can determine whether the string is at least that many characters long. (Note that the first character is element 0, so $username[5] is the sixth character in $username.)
The reason this is slightly faster than strlen() is complicated. The simple explanation is that strlen() is a function, and isset() is a language construct. Generally speaking, calling a function is more expensive than using a language construct.

How to Use MySQL Foreign Keys for Quicker Database Development

What are Foreign Keys?

A foreign key establishes a relationship, or constraint, between two tables.
Disclaimer! For the purpose of this example, we will create two simple database tables. They are not well designed, but will demonstrate the power of foreign keys!
  • employee: a table of company employees where each member is assigned a unique ID
  • borrowed: a table of borrowed books. Every record will reference a borrower’s employee ID.
We will define a foreign key relationship between the employee’s ID in both tables. This provides a couple of advantages:
  1. It is not possible to enter an invalid employee ID in the ‘borrowed’ table.
  2. Employee changes are handled automatically by MySQL.

Creating an Example Database

Our example database is created as follows:
CREATE DATABASE mydb;
USE mydb;
We now define our two tables. Note that InnoDB is specified as the table type and we will also add an index for the employee’s last name.

CREATE TABLE employee (
id smallint(5) unsigned NOT NULL,
firstname varchar(30),
lastname varchar(30),
birthdate date,
PRIMARY KEY (id),
KEY idx_lastname (lastname)
) ENGINE=InnoDB;

CREATE TABLE borrowed (
ref int(10) unsigned NOT NULL auto_increment,
employeeid smallint(5) unsigned NOT NULL,
book varchar(50),
PRIMARY KEY (ref)
) ENGINE=InnoDB;
We can now specify our foreign key (this could be handled in the CREATE TABLE statement, but it is shown separately here):

ALTER TABLE borrowed
ADD CONSTRAINT FK_borrowed
FOREIGN KEY (employeeid) REFERENCES employee(id)
ON UPDATE CASCADE
ON DELETE CASCADE;
This tells MySQL that we want to alter the borrowed table by adding a constraint called ‘FK_borrowed’. The employeeid column will reference the id column in the employee table – in other words, an employee must exist before they can borrow a book.
The final two lines are perhaps the most interesting. They state that if an employee ID is updated or an employee is deleted, the changes should be applied to the borrowed table.

Adding Table Data

We will now populate the tables with data. Remember that our employees must be added first:
employee:
idfirstnamelastnamebirthdate
1JohnSmith1976-01-02
2LauraJones1969-09-05
3JaneGreen1967-07-15
borrowed:
refemployeeidbook
11SitePoint Simply SQL
21SitePoint Ultimate HTML Reference
31SitePoint Ultimate CSS Reference
42SitePoint Art and Science of JavaScript
The table shows that John has borrowed 3 books, Laura has borrowed 1, and Jane has not borrowed any. Standard SQL queries can be run to find useful information such as “which books has John borrowed”:

SELECT book FROM borrowed
JOIN employee ON employee.id=borrowed.employeeid
WHERE employee.lastname='Smith'; 
 

Cascading in Action

The Accounts Department calls us with a problem: Laura’s employee ID must be changed from 2 to 22 owing to a clerical error. With standard MyISAM tables, you would need to change every table that referenced the employee ID. However, our InnoDB constraints ensure that changes are cascaded following a single update:
UPDATE employee SET id=22 WHERE id=2; If we examine our borrowed table, we will find that the update has occurred without us needing to run additional code:
borrowed:
refemployeeidbook
11SitePoint Simply SQL
21SitePoint Ultimate HTML Reference
31SitePoint Ultimate CSS Reference
422SitePoint Art and Science of JavaScript
It is a busy day and we now have the Personnel Department on the phone. John’s learnt so much from the SitePoint books, he’s left the company to set up on his own (he was frisked at the door to ensure he returned them all). Again, we need a single SQL statement:
DELETE FROM employee WHERE id=1; The deletion is cascaded through to our borrowed table, so all John’s references are removed:
borrowed:
refemployeeidbook
422SitePoint Art and Science of JavaScript
Although this is a simple example, it demonstrates the power of foreign keys. It is easy to retain data integrity without additional code or complex series of SQL commands. Note there are other alternatives to ‘CASCADE’ in your UPDATE and DELETE definitions:
  • NO ACTION or RESTRICT: the update/delete is rejected if there are one or more related foreign key values in a referencing table, i.e. you could not delete the employee until their books had been returned.
  • SET NULL: update/delete the parent table row, but set the mis-matching foreign key columns in our child table to NULL (note that the table column must not be defined as NOT NULL).
The same concepts can be applied to large-scale databases containing dozens of tables with inter-linked relationships.
 

Inserting An Array into a MySQL Database Table

Inserting An Array into a MySQL Database Table :

mysql_insert_array()

Inserts $data into $table using the associative array keys as field names and the values as values (requires an existing open database connection).

Parameters

Argument   TypeExplanation
$table      StringThe name of the database table to insert into
$data        ArrayThe associative array containing fieldnames as keys and values
$exclude String/ArrayOptional string or array of field names to exclude from the insertion. Useful for excluding certain elements when using this on $_POST

Return Values

The function returns an associative array with the following elements:
KeyDescription
mysql_error      FALSE if the query was successful, detailed MySQL error  otherwise
mysql_insert_id    The most recent ID generated from the query (only for  tables  with an AUTO_INCREMENT)
mysql_affected_rows   The number of rows affected by the query
mysql_info    MySQL information about the query

Code


  • <?php

  • function mysql_insert_array($table, $data, $exclude = array()) {



  • $fields = $values = array();



  • if( !is_array($exclude) ) $exclude = array($exclude);



  • foreach( array_keys($data) as $key ) {

  • if( !in_array($key, $exclude) ) {

  • $fields[] = "`$key`";

  • $values[] = "'" . mysql_real_escape_string($data[$key]) . "'";

  • }

  • }



  • $fields = implode(",", $fields);

  • $values = implode(",", $values);



  • if( mysql_query("INSERT INTO `$table` ($fields) VALUES ($values)") ) {

  • return array( "mysql_error" => false,

  • "mysql_insert_id" => mysql_insert_id(),

  • "mysql_affected_rows" => mysql_affected_rows(),

  • "mysql_info" => mysql_info()

  • );

  • } else {

  • return array( "mysql_error" => mysql_error() );

  • }



  • }

  • ?>


Example


  • <?php



  • // Open database here



  • // Let's pretend these values were passed by a form

  • $_POST['name'] = "Bob Marley";

  • $_POST['country'] = "Jamaica";

  • $_POST['music'] = "Reggae";

  • $_POST['submit'] = "Submit";



  • // Insert all the values of $_POST into the database table `artists`, except

  • // for $_POST['submit']. Remember, field names are determined by array keys!

  • $result = mysql_insert_array("artists", $_POST, "submit");



  • // Results

  • if( $result['mysql_error'] ) {

  • echo "Query Failed: " . $result['mysql_error'];

  • } else {

  • echo "Query Succeeded! <br />";

  • echo "<pre>";

  • print_r($result);

  • echo "</pre>";

  • }



  • // Close database



  • ?>


Since every field value is sanitized through mysql_real_escape_string(), the potential for SQL injection is reduced significantly.
In a public environment, or anywhere that users can modify the array keys, you should validate and sanitize the keys in the $data array to prevent SQL errors and injections. For example, if someone forges a POST to your script with additional fields, MySQL will most likely throw an error.
To combat this, simply make sure that the keys in the array are what you expect them to be, and disallow anything foreign.

Thursday, 4 September 2014

Create Group Chat In PHP With MySQL, jQuery And AJAX

Chatting is one of the most implemented feature on websites. Group chat makes users to share their feelings and other news easily to their friends easily. AJAX makes this chatting more beautiful, elegant, simple and comfortable. Group chats may be vulnerable to SQL injections and XSS attacks. But in this post where we're going to make a group chat that doesn't have these vulnerabilities which makes it more awesome. You can see a demo, or you can download the files directly.

Things To Note

Make sure the Time Zone on MySQL server is the same as on PHP server. If the time zone is different, the user's online presence can't be understood.
We limit the name of the user to 20 characters, because we don't want a long name that overflows the "online users" display element. There is no error message displayed when a user submits a name of more than 20 chars. If the user submits the name of 20 chars, the first 20 chars will only be inserted in to the table. Other chars after 20 chars will be removed by MySQL.
If you need Users' Typing Status display with the chat, see this tutorial after completing this tutorial.

tables.sql

The SQL code that creates the two tables needed for the group chat is contained in the tables.sql file :
-- Set MySQL timezone to UTC
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET GLOBAL time_zone = "+00:00";
-- Table structure for table `chatters`
CREATE TABLE IF NOT EXISTS `chatters` (
`name` varchar(20) NOT NULL,
`seen` varchar(20) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
-- Table structure for table `messages`
CREATE TABLE IF NOT EXISTS `messages` (
`name` varchar(20) NOT NULL,
`msg` text NOT NULL,
`posted` varchar(20) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
When you execute the above SQL code, you will have two tables, one chatters and the other messages. In chatters table, we add the logged in users' details where as the other one, we will add the messages sent by the users.

config.php

The database configuration is stored in this file. You should change the credentials according to your database :
<?
// ini_set("display_errors","on");
if(!isset($dbh)){
session_start();
date_default_timezone_set("UTC");
$musername = "username";
$mpassword = "password";
$hostname = "hostname";
$dbname = "dbname";
$dbh=new PDO('mysql:dbname='.$dbname.';host='.$hostname.";port=3306",$musername, $mpassword);
/*Change The Credentials to connect to database.*/
include("user_online.php");
}
?>
The session is started when this file loads. There is a file named user_online.php included in the file. This file deletes the offline users and updates the time stamp of the currently logged in user. This time stamp determines the online, offline status. I explained more about it in the user_online.php heading in this post.

index.php

The main character of our chat is this file. index.php joins everything together.
<?include("config.php");include("login.php");?>
<!DOCTYPE html>
<html>
 <head>
  <script src="//code.jquery.com/jquery-latest.js"></script>
  <script src="chat.js"></script>
  <link href="chat.css" rel="stylesheet"/>
  <title>PHP Group Chat With jQuery & AJAX</title>
 </head>
 <body>
  <div id="content" style="margin-top:10px;height:100%;">
   <center><h1>Group Chat In PHP</h1></center>
   <div class="chat">
    <div class="users">
     <?include("users.php");?>
    </div>
    <div class="chatbox">
     <?
     if(isset($_SESSION['user'])){
      include("chatbox.php");
     }else{
      $display_case=true;
      include("login.php");
     }
     ?>
    </div>
   </div>
  </div>
 </body>
</html>
When a user is not logged in, this file will load the login.php file which have the login box and others. If the user is logged in, then it will directly display the chatbox.php which contains the chatbox. Users who are currently online are shown using users.php file.

login.php

The login box and the login authentication, filtering, checking are added in this file. This file is included twice in the index.php file, one for checking and other for displaying login box.
<?
if(isset($_POST['name']) && !isset($display_case)){
$name=htmlspecialchars($_POST['name']);
if($name!=""){
$sql=$dbh->prepare("SELECT name FROM chatters WHERE name=?");
$sql->execute(array($name));
if($sql->rowCount()!=0){
$ermsg="<h2 class='error'>Name Taken. <a href='index.php'>Try another Name.</a></h2>";
}else{
$sql=$dbh->prepare("INSERT INTO chatters (name,seen) VALUES (?,NOW())");
$sql->execute(array($name));
$_SESSION['user']=$name;
}
}else{
$ermsg="<h2 class='error'><a href='index.php'>Please Enter A Name.</a></h2>";
}
}elseif(isset($display_case)){
if(!isset($ermsg)){
?>
<h2>Name Needed For Chatting</h2>
You must provide a name for chatting. This name will be visible to other users.<br/><br/>
<form action="index.php" method="POST">
<div>Your Name : <input name="name" placeholder="A Name Please"/></div>
<button>Submit & Start Chatting</button>
</form>
<?
}else{
echo $ermsg;
}
}
?>
If the user has submitted the login form, then this file will do the following :
  • Filter Name (Remove HTML entities)
  • Check If there is another user with the name
If everything is OK, then the file changes the user value of session to the name submitted. index.php and chatbox.php takes care of the rest.

chatbox.php

This file don't have that much content. This file contains the log out link, chat messages container and chat form :
<?
include("config.php");
if(isset($_SESSION['user'])){
?>
 <h2>Room For ALL</h2>
 <a style="right: 20px;top: 20px;position: absolute;cursor: pointer;" href="logout.php">Log Out</a>
 <div class='msgs'>
  <?include("msgs.php");?>
 </div>
 <form id="msg_form">
  <input name="msg" size="30" type="text"/>
  <button>Send</button>
</form>
<?
}
?>
The messages are displayed from msgs.php and the form is also displayed.

msgs.php

Displays the messages sent by the other users and himself/herself. A request is made to this file every 5 seconds to check new messages. When the user logs out from another page of the browser window, msgs.php will make the current page reload to make sure everything is alright.
<?
include("config.php");
$sql=$dbh->prepare("SELECT * FROM messages");
$sql->execute();
while($r=$sql->fetch()){
 echo "<div class='msg' title='{$r['posted']}'><span class='name'>{$r['name']}</span> : <span class='msgc'>{$r['msg']}</span></div>";
}
if(!isset($_SESSION['user']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH'])=='xmlhttprequest'){
 echo "<script>window.location.reload()</script>";
}
?>

send.php

When the user submits a message, the message is sent to send.php. This file handles the message, filters it and insert into database.
<?
include("config.php");
if(!isset($_SESSION['user']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH'])=='xmlhttprequest'){
 die("<script>window.location.reload()</script>");
}
if(isset($_SESSION['user']) && isset($_POST['msg'])){
 $msg=htmlspecialchars($_POST['msg']);
 if($msg!=""){
  $sql=$dbh->prepare("INSERT INTO messages (name,msg,posted) VALUES (?,?,NOW())");
  $sql->execute(array($_SESSION['user'],$msg));
 }
}
?>

users.php

Currently online users are displayed using this file. This file is also requested in every 5 seconds by jQuery.
<?
include("config.php");
echo "<h2>Users</h2>";
$sql=$dbh->prepare("SELECT name FROM chatters");
$sql->execute();
while($r=$sql->fetch()){
 echo "<div class='user'>{$r['name']}</div>";
}
?>

user_online.php

Whenever the config.php file is called, this file is also called. This file loops through the online users on table chatters and check if their time stamp is lesser that 25 seconds the current time. If it is lesser, then that user is dropped (deleted) from the table. It also updates the time stamp of the currently logged in user if there is one making it impossible for the other script to delete the current user. It is necessary to have the same time zone on MySQL server and the PHP server. If the currently logged in user is accidentally deleted in case of misunderstanding, the script will automatically add the user to the table chatters. What an important file !
<?
if(isset($_SESSION['user'])){
 $sqlm=$dbh->prepare("SELECT name FROM chatters WHERE name=?");
 $sqlm->execute(array($_SESSION['user']));
 if($sqlm->rowCount()!=0){
  $sql=$dbh->prepare("UPDATE chatters SET seen=NOW() WHERE name=?");
  $sql->execute(array($_SESSION['user']));
 }else{
  $sql=$dbh->prepare("INSERT INTO chatters (name,seen) VALUES (?,NOW())");
  $sql->execute(array($_SESSION['user']));
 }
}
/* Make sure the timezone on Database server and PHP server is same */
$sql=$dbh->prepare("SELECT * FROM chatters");
$sql->execute();
while($r=$sql->fetch()){
 $curtime=strtotime(date("Y-m-d H:i:s",strtotime('-25 seconds', time())));
 if(strtotime($r['seen']) < $curtime){
  $kql=$dbh->prepare("DELETE FROM chatters WHERE name=?");
  $kql->execute(array($r['name']));
 }
}
?>

logout.php

If the user can log in,.there should be a log out option. Here is the file that will destroy the session and redirects to the main page or as you call it "logout" :
<?
session_start();
include("config.php");
$sql=$dbh->prepare("DELETE FROM chatters WHERE name=?");
$sql->execute(array($_SESSION['user']));
session_destroy();
header("Location: index.php");
?>

Client Side

It's time to move to the client side, where we will design our chatbox with CSS and add the jQuery code to make it easy.

chat.css

The chatbox, online users and other styling is in here :
.chat .users, .chat .chatbox{
display:inline-block;
vertical-align:top;
height:350px;
padding:0px 15px;
position:relative;
}
.chat .users{
background:#CCC;
color:white;
width:98px;
overflow-y:auto;
}
.chat .chatbox{
background:#fff;
color:black;
margin-left:4px;
width:330px;
}
.chat .chatbox .msgs{
border-top:1px solid black;
border-bottom:1px solid black;
overflow-y:auto;
height:260px;
}
.chat .chatbox #msg_form{
padding-top:1.5px;
}
.chat .error{color:red;}
.chat .success{color:green;}
.chat .msgs .msg, .chat .users .user{border-bottom:1px solid black;padding:4px 0px;white-space:pre-line;word-break:break-word;}
The elements we added are all wrapped in the .chat container, so the chat.css won't mess up any other styles of your site.

chat.js

The jQuery code is the content of this file. Note that you should add a script[src] that links to the jQuery library source (code.jquery.com/jquery-latest.js).
function scTop(){
 $(".msgs").animate({scrollTop:$(".msgs")[0].scrollHeight});
}
function load_new_stuff(){
 localStorage['lpid']=$(".msgs .msg:last").attr("title");
 $(".msgs").load("msgs.php",function(){
  if(localStorage['lpid']!=$(".msgs .msg:last").attr("title")){
   scTop();
  }
 });
 $(".users").load("users.php");
}
$(document).ready(function(){
 scTop();
 $("#msg_form").on("submit",function(){
  t=$(this);
  val=$(this).find("input[type=text]").val();
  if(val!=""){
   t.after("<span id='send_status'>Sending.....</span>");
   $.post("send.php",{msg:val},function(){
    load_new_stuff();
    $("#send_status").remove();
    t[0].reset();
   });
  }
  return false;
 });
});
setInterval(function(){
 load_new_stuff();
},5000);
I think that's all the files. I made it to these much files to make the tutorial easy. Hope you like it. Be open source, share this with your developer friends. I'm sure they would love to see this. If you have any problems / suggestions, please say it out in the comments, I would love to hear it from you and I will reply if there isn't any stupid school projects.

.

Popular Posts

Powered by Blogger.