Posts Tagged ‘Happy Polack’

User Account Class in PHP 2

Wednesday, February 10th, 2010 by Phillip Napieralski

I started up an account class in my previous post, but I left out a way for the user to authenticate! Let’s fix that…

Authenticate

	public function authenticate($group_id = 0) {
		$dbconn = @Database::grab();

		// They must have ALL three cookies set from the login function, otherwise something went wrong!
		if( !isset($_COOKIE['id']) && !isset($_COOKIE['cookie']) && !isset($_COOKIE['name']) )
		{
			return false;
		}

		// Sanitize
		$id = safe($dbconn, $_COOKIE['id']);
		$cookie = safe($dbconn, $_COOKIE['cookie']);
		$username = safe($dbconn, $_COOKIE['name']);

		// Make sure they have a valid cookie value first! (64 characters long in our case)
		if( strlen($cookie) > 63 )
		{
			// Check the values in the DB with a quick query
			$query = "SELECT id, username, cookie, group_id FROM users WHERE
					id = ".$id." AND
					cookie = '".$cookie."' AND username = '".$username."' AND group_id >= $group_id LIMIT 1";
			$queryData = $dbconn->query($query);
		}
		if( $queryData )
		{
			if( $queryData->num_rows > 0) {
				$data = $queryData->fetch_object();
				$this->group_id = $data->group_id;
				$this->user_id = $data->id;
				$this->username = $data->username;
				return true;
			}
		}

		// They failed to authenticate! Wrong username/pass?
		$this->logout();
		return false;
	}

There you have it. Basically, check to make sure they have all the cookies you set in your login function. If any are missing, fail. Then, we write a query to check the credentials from the cookies to see if they match what’s in the database. If not, clean up their cookies with $this->logout() and return false. That reminds me, here’s the code for logout:

Logout

	public function logout() {
		$dbconn = @Database::grab();
		
		if(isset($_COOKIE["id"]) && isset($_COOKIE["cookie"]))
		{
			$id = safe($dbconn, $_COOKIE['id']);
			$cookie = safe($dbconn, $_COOKIE['cookie']);

			// Set the cookie to null in the database
			$query = "UPDATE users SET cookie = 0 WHERE id = ".$id." AND cookie = '".$cookie."'";
			$dbconn->query($query);
		}
		// Expire our cookies by setting the time to 1 second after the EPOCH
		setcookie("hpname", "", 1, "/" );
		setcookie("hpid", "", 1,"/" );
		setcookie("hpcookie", "", 1,"/" );
		
		// Unset them immediately
		unset($_COOKIE['hpname']);
		unset($_COOKIE['hpid']);
		unset($_COOKIE['hpcookie']);
	}

There you go. What’s missing now? We just need a forgot password function and we’re good to go.

Lost on this tutorial? Try going back to part 1

User Account Class in PHP 1

Wednesday, February 3rd, 2010 by Phillip Napieralski

I’m going to describe the class I created to handle signup/login/authentication at happypolack.com.

If you’d like a demo, create an account at Happy Polack 🙂.

The Database Structure

First, what is it going to look like in the database? Well, here’s what I used:

Generated using MySQL Workbench

Take note of the indexes used. I have a foreign key relating the users and the groups table (1-1 for now). I also have a unique index on the username to prevent the same username. Lastly, I have a regular index on the email address to make searching that field faster (useful if they forgot their password).

The other thing to note is that both password and cookie have a datatype of CHAR(64). This is because I encrypted the password using a SHA256 algorithm, which is WAY stronger than md5, before placing it in the database (this is PHPs hash(“sha256”,$unenc_password) function). For the cookie field, I use that algorithm on the uniq_id() function built into PHP. I’ll get to that in a minute.

The Class Structure

class AccountController {
        // Initialize variables to something arbitrary
	private $group_id = -1;
	private $user_id = -1;
	private $username = '';

	public function signup($username, $password, $email){...}
	public function login($username, $password){...}
	public function logout(){...}
	public function authenticate($group_id = 0){...}
	public function is_logged_in(){...}
	public function is_admin(){...}
}

Excellent! We got the basic functionality out of the way. We are able to signup/login/logout/authenticate. We can even pass a group_id to authenticate to make sure they are part of a specific group.

Signup

public function signup($username, $password, $email) {
	$dbconn = @Database::grab(); // This WILL be different for you. I use a simple MySQLi wrapper.

	$origpassword =  $password; // store original password to send to them
	$password = hash('sha256', $password);
	if(!$dbconn->query($query))
		return false;
	else
	{
		// Send a nice email
		$message = '<b>Account Information:</b>';
		$message .= '<p>Username: '.$username;
		$message .= '</p><p>Password: '.$origpassword;
		$message .= '</p><p>Your profile: http://happypolack.com/user/'.$username.'</p>;

		send_mail( $email, "Welcome to Happy Polack!", "Hello and Welcome to Happy Polack!", $message );
	}
	return true;
}

There you have it! Now we can signup, well… except for the send_mail piece, let me give you the code for that:

function send_mail($to, $subject, $title, $msg)
{
	$headers = 'From: Happy Polack <you@example.com>'. "\r\n".
			'Reply-To: you@example.com' . "\r\n" .
			'X-Mailer: PHP/' .phpversion(). "\r\n";

	$headers .= 'MIME-Version: 1.0' . "\r\n";
	$headers .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n";

	$message =  '<h1>'. $title.'</h1>';
	$message .= $msg;

	mail( $to, $subject, $message, $headers);
}

Basically, this nifty function automatically sets up the headers so we can use HTML in our e-mails and it automatically formats various features (this implementation could be spruced up with some neat css too).

Login

public function login($username, $password) {
	$dbconn = @Database::grab();

	$epassword = hash("sha256",$password); // Encrypt using SHA256. We could also "salt" the password before hashing for added security.

	$query = "SELECT id, group_id FROM users WHERE username = '$username' AND password = '$epassword' LIMIT 1";
	$qData = $dbconn->query($query) ;
	if( $qData->num_rows > 0 )
	{
		$userData = $qData->fetch_array();
		$cookie = generate_token();
		$userID = $userData['id'];

		$query = "UPDATE users SET cookie = '$cookie' WHERE username = '$username' AND password = '$epassword'";

		if($dbconn->query($Query))
		{
			$this->user_id = $userID;
			$this->group_id = $userData['group_id'];
			$expire_time = 60*60*24*7+time(); // About one week
			// When we create the cookies, we pass the cookie's name, the value, the expire time and what piece of the site the cookie is valid on. I specify "/" to allow it to be usable throughout the whole website.
			setcookie("id", $userID, $expire_time, "/");
			setcookie("cookie", $cookie, $expire_time, "/");
			setcookie("username", $username, $expire_time, "/");

			// This piece is so that the COOKIE values are set immediately. When I left this piece out, I noticed it would take an extra page load for the cookies to register. This is because the cookies are generally sent with everyday headers and *it seems* that they are not processed right away.
			$_COOKIE['id'] = $userID;
			$_COOKIE['cookie'] = $cookie;
			$_COOKIE['username'] = $username;

			return true; // SUCCESS, now logged in!
		}
	}
	return false; // Bummer!
}

What’s happening here? Well, let’s get a pretty picture to clarify the order a little bit:

Generated with yUML - Does this help?

In easier terms: Check if the user is in the database -> if he is, put a random value into his cookie field for later authentication and set his cookies.

That reminds me, here is one last function, generate_token(), that is pretty essential, and simple, to get a random string of length 64.

// Return a random string of length 64
function generate_token() {
	return hash('sha256', uniqid(mt_rand(), true));
}

Next Time

Wow! Progress has been made. But there’s more to be had. We still need an authenticate function as well as a way to logout!

Phew.

Inline AJAX Editing

Saturday, January 23rd, 2010 by Phillip Napieralski

Happypolack.com is a joke site I created as a side project a little bit ago and it’s continually being enhanced. The latest feature I added was a way for admins to edit any joke (eg, fix typos), quickly and easily.

An Example

This weirdo typed “Your Mommy” instead of “Yo Mama.” How distracting! Let’s write some code so we can fix this.

If we click the edit link corresponding to the joke, we get something that looks like the following:

Great! We edited it, now we simply click submit and we can go on with reading jokes and being merry!

How it works

When the edit link is clicked, some simple javascript is called. Here is the PHP code for the edit hyperlink for any joke.

echo "<a id="joke&quot;.$joke_id.&quot;" href="javascript:edit_joke(&quot;.$joke_id.&quot;);">;edit</a>";

We start by assigning each link with a distinct ID (corresponding to the joke’s ID in the database) and, upon clicking, it calls the javascript function edit_joke.

The Javascript/jQuery Part

Inside the edit_joke function, I used jQuery selectors to find the link and text for the joke. I then used the wrapInner() method to place an editable <div> inside our joke text’s <p> tags (basically, we now have <p id=”123″><div>Your mommy joke hehe haha</div></p> instead of <p id=”joke123″>Your mommy joke hehe haha</p>). After that, change the edit link to submit/cancel using jQuery. Clicking submit will call the javascript function edit_joke_submit(..) and clicking cancel will call the javascript function edit_joke_cancel(..). I placed those links inside a <span> tag so I could easily reference them later. The magic in this code is the contenteditable=’true’ part of the div. Here’s the code:

function edit_joke(joke_id){
	this_link = $("a#joke"+joke_id);
	this_joke = $("p#joke"+joke_id);

	this_joke.wrapInner("<div contenteditable='true' class='editable_div'></div>");

	this_link.replaceWith("<span id='submitcancel'>"+
		"<a id='joke"+joke_id+"' href='javascript:edit_joke_submit("+joke_id+");'>submit</a>"+
		"<a id='joke"+joke_id+"' href='javascript:edit_joke_cancel("+joke_id+");'>/cancel</a>" );
}

The edit_joke_cancel function is relatively simple. I first found the corresponding submit/cancel link and the joke div using jQuery selectors. After that, I used the html() method to grab the new text that was typed in. I set the text inside the <p> tags equal to the new text so that it would look like <p id=”123″>Yo mama joke hehe haha<div>Yo mama joke hehe haha</div></p> instead of <p id=”joke123″>Yo mama joke hehe haha</p>. Then, simply remove the <div> tags (and, consequently, the text inside), and it’s back to normal.

function edit_joke_cancel(joke_id){
	this_link = $("span#submitcancel");
	this_joke = $("p#joke"+joke_id);
	this_joke_div = this_joke.find("div");

	joke_text = this_joke_div.html();
	this_joke.html(joke_text);
	this_joke_div.remove();

	this_link.replaceWith(
			"<a id='joke"+joke_id+"' href='javascript:edit_joke("+joke_id+");'>edit</a>"
		);
}

To submit the joke changes, we need only send a post request to a server side php script with the joke id and the new joke text. Here’s the code:

function edit_joke_submit(joke_id){
	var this_joke = $("p#joke"+joke_id);
	var joke_text = this_joke.find("div").html();

	$.post('/jokes/ajax-edit.php',
			{
				id: joke_id,
				text: joke_text
			},
			function(data){
				alert(data);
			}
		);

	edit_joke_cancel(joke_id); // remove edible div
}

The PHP Part

All we have to do now is send an update query to the database with the corresponding id and text. The only caveat: editable divs place <br>’s when you hit enter, so I added the str_replace(..) to turn those into newline characters capable of storage in the database. Check it out:

// Include needed files for account verification here.
// Get your database connection and store it in $dbconn HERE. I use mySQLi in my code.
<?php
if( $accounts->check_admin() )
{
	$id = $dbconn->escape_string($_POST['id']);    // Prevent sql injection
	$text = $dbconn->escape_string($_POST['text']);

	$text = str_replace("<br>","\r\n", $text);    // Swap's out <br>'s

	$query = "UPDATE joke_table SET text='".$text."' WHERE id = ".$id;
	$dbconn->query($query) or die(mysqli_error($dbconn));
	echo "The joke has been edited :)";
}
else
{
	echo 'Yea... okay buddy -_-';
}
?>