Archive for the ‘PHP’ Category

Migrating from CakePHP 1.2.7 to 1.3

Friday, May 21st, 2010 by Phillip Napieralski

I decided to migrate one of my in-progress projects to the latest release of CakePHP (1.3). My experience was quick and painless, and perhaps this writing will benefit someone.

Copy/Overwrite

Copy/overwrite app/config/core.php, in this file change the following

  • Change cipherSeed value to something other than the default
  • Change the salt value to the same value you had before

Copy/overwrite app/webroot/index.php

Lastly, completely copy over the latest cake, plugins and vendors folders. I didn’t need to do any other modifications to the app folder other than those stated above.

Add to Code

Add ‘Session’ to the helpers AND components array in app/app_controller.php like so

<?php
class AppController extends Controller {
	var $components = array( ..., 'Session');    // Components used in controllers
	var $helpers = array( ..., 'Session' );         // Helpers used in views
}
?>

Also,
If using the Cookie Component anywhere, change any reference to del() to delete()

Lastly, $session->flash() no longer auto echoes (this code is probably in your layout). I had to change it like so in my code:

<?php
echo $session->flash();
echo $session->flash('auth');
?>

Debug Information

The variable $cakeDebug no longer exists. Instead, use the following code

<?php echo $this->element('sql_dump'); ?>

That’s all I had to do! Everything I mentioned here can be found in the CakePHP migration guide.

Spam-proof Contact Form with PHP/Akismet

Sunday, May 9th, 2010 by Phillip Napieralski

Check out the demo

This is in addition to my previous post about creating a simple contact form. This post will utilize the Akismet service to classify some messages as spam.

The Prerequisites

To follow this tutorial, I assume you already have a simple contact form in place.

Akismet PHP Library

The folks at Aching Brain created a nifty PHP class to make utilizing the Akismet service easier. Download this library then put it in the same directory as your contact form file (Download mirror: AkismetPHPClass).

There are implementations available in other languages as well.

The PHP

This code will completely replace the PHP code from the previous post. However, I assume the HTML used is unchanged.

First, include the Akismet library:

<?php
require "Akismet.class.php";

Now, create a more generic function for sending an email:

function send_mail( $name, $email, $website, $ip, $is_spam, $message)
  {
  		$subject = '';
  		if( $spam == true )
  			$subject = "[SPAM?]"; 
  		$subject .= "[Your_site.com] E-mail received from ".$author_name."//".$author_email."//".$ip;
  		
  		mail( "send_to_this_address@to.com", $subject, 
		$author_name.", ".$author_email.", ".$author_website. ".\r\n\r\n".$message);
}

If the $is_spam parameter is set to true, we simply prepend “[SPAM?]” at the beginning of the subject line. This allows us to see right away what it is in our inbox. Further, you could create an e-mail filter to automatically put these messages in a different e-mail folder.

Now, we have to set-up the Akismet class. This will require a WordPress API Key. If you don’t have one, it’s easy and free to get one.

	if(isset($_POST['action']))
	{
		$wp_key = 'xXxXxXxXxXxX';
		$our_url = 'http://www.your_website.com';
		
		$name = $_POST['name'];
		$email = $_POST['email'];
		$website = $_POST['website'];
		$message = $_POST['message'];
		$ip = $_SERVER['REMOTE_ADDR'];
		
		$akismet = new Akismet($our_url, $wp_key);
		$akismet->setCommentAuthor($name);
		$akismet->setCommentAuthorEmail($email);
		$akismet->setCommentAuthorURL($website);
		$akismet->setCommentContent($message);
		$akismet->setUserIP($ip);
		
		send_mail( $name, $email, $website, $ip, $akismet->isCommentSpam(), $message);
	}
?>

That’s it! Now if a line of spammers hits your contact form, you have a safe guard against it. If you have questions about this piece of code or anything else, feel free to leave a comment!

In conlusion, Akismet is the best!

Simple Contact Form with PHP

Thursday, April 8th, 2010 by Phillip Napieralski

This is something that every website should have, including my personal portfolio!

The HTML

			<form id="contact_form" method="post" action="contact.php"> 
				<input id="name" type="text" name="name" tabindex="1"/> <label for="name">Name</label>
				<input id="email" type="text" name="email" tabindex="2"/> <label for="email">E-mail</label>
				<input id="website" type="text" name="website" tabindex="3" value="http://" /> <label for="website">Website</label>
				<textarea id="message" tabindex="4" rows="10" cols="60" name="message"></textarea>
				<input type="submit" value="Send E-mail" tabindex="5" />
			</form>

This is just a simple form. I included two things that increase usability quite a bit: Usage of the label tag and usage of the tabindex attribute of the various form inputs.

The label tag allows you to click the text next to an input box and have your cursor go into the textbox. You do this by setting the label’s “for” attribute equal to the “id” attribute of your input box. Find more information at w3schools.

The PHP

<?php
if( isset($_POST['action']) )
{
		echo 'Thanks for the E-mail!';
		
		$name = $_POST['name'];
		$email = $_POST['email'];
		$website = $_POST['website'];

		mail( 'send_to_this_address@to.com', "E-mail received from $name, $email, $website", $_POST['message'] );
}
?>

Now, simply put the php code followed by the HTML code into the same php file (I called mine contact.php), and upload it to your webserver!

The php is pretty simple. We grab all the form variables and put them into php’s mail function. If this function does not work, you probably need to install/configure postfix.

Discussion

The code is really simple. You can find more information in the php manual online. A big thing that I did NOT talk about, however, is how to prevent spammers from attacking your contact form. In fact, for a large site it’s a big problem (my blog gets 10+ spam messages per day).

Luckily! There is a great service available to detect and reject spam messages. It’s called Akismet. Expect a blog about this and other ways of protecting against spam in the near future.

User Registration and Login With CakePHP 1.2.x

Saturday, February 20th, 2010 by Phillip Napieralski

I’m going to create a simple user registration form with a confirm password field!

Features of this implementation:

  • Uses AuthComponent
  • Confirm password field
  • Ensures unique usernames
  • User feedback if any errors occur
  • Remember me feature (cookies)

Prerequisites

I assume you have a database structure similar to the following:

Users and Groups tables

Note: CakePHP automagically updates the created and modified fields.

App Controller

Place the following code in /app/controllers/app_controller.php

<?php

class AppController extends Controller {
	var $components = array( 'Auth', 'RememberMe' );

	function beforeFilter() {
		$this->Auth->logoutRedirect =  '/';
		$this->Auth->loginRedirect = '/';
		$this->RememberMe->check();
	}
}
?>

The two lines $this->Auth->log*** determine where the user is directed after they login. Another option (instead of ‘/’), is $this->referer(). That will bring the user back to the page they were just at (sweet)!

What’s this remember me crap though?

Remember Me

This guy wrote an excellent component that you can simply pop in your /app/controllers/components folder and you’re ready to go.

The Form

Place the following code in /app/views/users/register.ctp

<?php
	echo $form->create('User', array( 'url' => array( 'action' => 'register' ) ) );
	echo $form->input('username',
					 	array('after' => $form->error('username_unique', 'The username is taken. Do try again!' ) ) );
	echo $form->input('password');
	echo $form->input('confirm_password', array( 'type' => 'password' ) );
	echo $form->input('email');
	echo $form->end('Sign-up');
?>

The only tricky piece right now should be in the username piece. In the second parameter, I specified ‘after’ that associates with a $form->error(…). The username_unique property is something I will define in the User Model shortly.

Users Controller

Place the following code in /app/controllers/users_controller.php inside your UsersController class.

	function beforeFilter()
	{
		parent::beforeFilter();

		// This allows our login() function to execute
		$this->Auth->autoRedirect = false;
	}

	// Note, this function is called AFTER Auth has handled it, but BEFORE redirecting to the afterLogin redirect specified (because of the flag we set in beforeFilter)
	// Basically, this means the password field is already encrypted and the session was already created. Easy! =)
	function login()
	{
		// Check if Auth got a hold of it and created a session
		if( $this->Auth->user() )
		{
			// If form data was sent, set the remember_me cookie IF they checked the box
			if( isset( $this->data ) )
			{
				if( empty( $this->data['User']['remember_me'] ) || $this->data['User']['remember_me'] == 0 )
				{
					$this->RememberMe->delete();
				}
				else
				{
					$this->RememberMe->remember(
								$this->data['User']['username'],
								$this->data['User']['password'] );
				}

				$this->Session->setFlash('You are logged in!');
			}

			// Redirect to our loginRedirect page specified
			$this->redirect($this->Auth->redirect());
		}
	}

	function logout(){
		// Set logout message and call auth component's logout
		$this->Session->setFlash('You are logged out! Good bye!');

		// Delete the cookie
		$this->RememberMe->delete();

		// Call Auth to delete our session
		$this->redirect($this->Auth->logout());
	}

	function register() {
		if ( isset($this->data) )
		{
			// Set group to regular user
			//    NOTE: This may be different for you
			$this->data['User']['group_id'] = 2;

			$this->User->create();
			if ($this->User->save($this->data))
			{
				$this->Session->setFlash( 'Thank you for registering!' );
				$this->redirect(array('action'=>'index'));
			}
			else
			{
				// Make the password fields blank
				unset($this->data['User']['password']);
				unset($this->data['User']['confirm_password']);

				$this->Session->setFlash('An error occurred, try again!');
			}
		}
	}

Excellent! Now we just have one more step… let’s validate the user’s input.

The User Model

Place the following code in /app/models/user.php

<?php
class User extends AppModel {

	var $name = 'User';

	// Prevent the input from having an id
	// Username: MUST be alphanumeric
	// Password: We specify a function that returns a bool value
	var $validate = array(
		'id' => array(
							'rule' => 'blank',
							'on' => 'create'
					 ),
		'username' => array(
							'rule' => 'alphanumeric',
							'message' => 'Please enter a valid username',
							'required' => true
						),
		'password' => array(
							'rule' => array('confirmPassword', 'password'),
							'message' => 'Passwords do not match',
							'required' => 'true'
						),
		'confirm_password' => array(
							'rule' => 'alphanumeric',
							'required' => 'true'
						),
		'email' => array( 'email',
							array( 'rule' => array('email'),
									'message' => 'Please enter a valid email!' )
						),
		'group_id' => array( 'numeric' )
	);

	function confirmPassword($data)
	{
		// We must manually hash the second piece in the same way the AuthComponent would
		// if they match, return true!
		if ($data['password'] == Security::hash(Configure::read('Security.salt') . $this->data['User']['confirm_password'])) {
			return true;
		}

		// hashed passwords did NOT match
		return false;
	}

	// Check if the username already exists by doing SELECT COUNT(*) FROM users WHERE username = 'your_username'
	function beforeValidate()
	{
		if( !$this->id )
		{
			if( $this->findCount( array('User.username' => $this->data['User']['username'] ) ) > 0 )
			{
				// If any rows are found, send an error and call it 'username_unique'
				// In our view, we can check for this by doing $form->error('username_unique','Not Unique Username!!!')
				//   As specified in the view code I placed above
				$this->invalidate('username_unique');
				return false;
			}
		}
		return true;
	}

	// Associations
	var $belongsTo = 'Group';
}
?>

Great!

Conclusion

There you have it. Something that may take you places in your projects (hopefully). Any questions?

CakePHP Cheatsheets

Friday, February 19th, 2010 by Phillip Napieralski

This is a list of cheatsheets and links for things that I commonly use (and forget the syntax to) in CakePHP.

Very Simple User Authentication with CakePHP

Monday, February 15th, 2010 by Phillip Napieralski

DISCLAIMER: I created this super simple user authentication post for CakePHP version 1.2.7. The latest versions of CakePHP (v2+) have been heavily enhanced. As a side effect, the code in this post will no longer work out-of-the-box. However, I’ll continue to leave this post up as a learning tool. Happy coding!

I’m working with my Software Engineering II class to build an online survey for United Way. I was left in charge to create a simple way to login and authenticate.

The following implementation uses sessions exclusively. I built it so you MUST log-in before accessing any part of the site/survey. You could easily build off this, or even switch to Cake’s built-in ACL and Auth features.

Preliminaries

The database structure presented is the same as in my previous post about a user account class. Here it is again however:

The Login Form View

The login form is extremely simple using Cake’s built in form helper. Here’s how we do it:

<?php
// Pass the model name into the Create function, also pass where the data will be sent
echo $form->create('User', array( 'controller' => 'users', 'action' => 'login' ) );

// Cake automatically knows, based on the input, to create input fields for these two. Cool eh?
echo $form->input('username');
echo $form->input('password');

// Create the submit button
echo $form->end('Login');
?>

That’s much less code, and way clearer once you get used to it. I placed this file in /app/views/users/login_form.ctp

The User Model

<?php
class User extends AppModel {
	function validateLogin($data)
	{
		// Search our database where the 'username' field is equal to our form input.
		// Same with the password (this example uses PLAIN TEXT passwords, you should encrypt yours!)
		// The second parameter tells us which fields to return from the database
		// Here is the corresponding query:
		// "SELECT id, username FROM users WHERE username = 'xxx' AND password = 'yyy'"
		$user = $this->find(array('username' => $data['username'], 'password' => $data['password']), array('id', 'username'));

		if( empty($user) == false )
		{
			return $user;
		}

		return false;
	}
}
?>

This code should go in /app/models/user.php

The Users Controller

<?php
class UsersController extends AppController {
	var $name = 'Users';
	var $helpers = array('Form');

	// Placeholder for login_form, required by CakePHP to see the login_form view
	function login_form() { }

	function login() {
		// Check if they went here after submitting the form
		// Note that all our form data is preceded by the model name ['User']
		if(empty($this->data['User']['username']) == false)
		{
			// Here we validate the user by calling that method from the User model
			if(($user = $this->User->validateLogin($this->data['User'])) != false)
			{
				// Write some Session variables and redirect to our next page!
				$this->Session->setFlash('Thank you for logging in!');
				$this->Session->write('User', $user);

				// Go to our first destination!
				$this->Redirect(array('controller' => 'Controller_name', 'action' => 'Action_name'));
				exit();
			}
			else
			{
				$this->Session->setFlash('Incorrect username/password!', true);
				$this->Redirect(array('action' => 'login_form'));
				exit();
			}
		}
	}

	function logout() {

		$this->Session->destroy();
		$this->Session->setFlash('You have been logged out!');

		// Go home!
		$this->Redirect('/');
		exit();
	}
}
?>

Place this code in /app/controllers/users_controller.php

The AppController – Validate on Every Page

class AppController extends Controller {

	// Check if they are logged in
	function authenticate()
	{
		// Check if the session variable User exists, redirect to loginform if not
		if(!$this->Session->check('User'))
		{
			$this->redirect(array('controller' => 'users', 'action' => 'login_form'));
			exit();
		}
	}

	// Authenticate on every action, except the login form
	function afterFilter()
	{
		if( $this->action != 'login_form' )
		{
			$this->authenticate();
		}
	}
}

Now simply place this code in /app/app_controller.php

What does this do? Well, now no matter where anyone goes on your website, they will be redirected to the login page UNLESS they are logged in! Excellent.

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.