Default Settings

This plugin is designed to help overcome automated form submission by requiring a "real person" to identify text made up of dots. The entered value is compared on the server with the generated value to determine whether processing should continue.

The real person functionality can easily be added to an input field with appropriate default settings.
You can also remove the real person functionality if it is no longer required.

Default real person:      

 

$('#defaultReal').realperson();

$('#disableReal').click(function() {
	var disable = $(this).text() === 'Disable';
	$(this).text(disable ? 'Enable' : 'Disable');
	$('#defaultReal').realperson(disable ? 'disable' : 'enable');
});

$('#removeReal').click(function() {
	var destroy = $(this).text() === 'Remove';
	$(this).text(destroy ? 'Re-attach' : 'Remove');
	$('#defaultReal').realperson(destroy ? 'destroy' : {});
});

You can override the defaults globally as shown below:

$.realperson.setDefaults({length: 5});

Processed fields are marked with a class of is_realperson and are not re-processed if targeted a second time.

Options

Customise the real person functionality through additional settings.

Different length:

$('#lengthReal').realperson({length: 8});

Include numbers:

$('#numberReal').realperson({chars: $.realperson.alphanumeric});

Regenerate instructions:

$('#instructReal').realperson({regenerate: 'Try another'});

Change dots:

$('#dotsReal').realperson({dot: 'o', dots:
	[['   *   ', '  ***  ', '  ***  ', ' **  * ', ' ***** ', '**    *', '**    *'],
	['****** ', '**    *', '**    *', '****** ', '**    *', '**    *', '****** '],
	[' ***** ', '**    *', '**     ', '**     ', '**     ', '**    *', ' ***** '],
	['****** ', '**    *', '**    *', '**    *', '**    *', '**    *', '****** '],
	['*******', '**     ', '**     ', '****   ', '**     ', '**     ', '*******'],
	['*******', '**     ', '**     ', '****   ', '**     ', '**     ', '**     '],
	[' ***** ', '**    *', '**     ', '**     ', '**  ***', '**    *', ' ***** '],
	['**    *', '**    *', '**    *', '*******', '**    *', '**    *', '**    *'],
	['*******', '  **   ', '  **   ', '  **   ', '  **   ', '  **   ', '*******'],
	['     **', '     **', '     **', '     **', '     **', '*    **', ' ***** '],
	['**    *', '**  ** ', '****   ', '**     ', '****   ', '**  ** ', '**    *'],
	['**     ', '**     ', '**     ', '**     ', '**     ', '**     ', '*******'],
	['*     *', '**   **', '*** * *', '** *  *', '**    *', '**    *', '**    *'],
	['*     *', '**    *', '***   *', '** *  *', '**  * *', '**   **', '**    *'],
	[' ***** ', '**    *', '**    *', '**    *', '**    *', '**    *', ' ***** '],
	['****** ', '**    *', '**    *', '****** ', '**     ', '**     ', '**     '],
	[' ***** ', '**    *', '**    *', '**    *', '**  * *', '**   * ', ' **** *'],
	['****** ', '**    *', '**    *', '****** ', '**  *  ', '**   * ', '**    *'],
	[' ***** ', '**    *', '**     ', ' ***** ', '     **', '*    **', ' ***** '],
	['*******', '  **   ', '  **   ', '  **   ', '  **   ', '  **   ', '  **   '],
	['**    *', '**    *', '**    *', '**    *', '**    *', '**    *', ' ***** '],
	['**    *', '**    *', ' **  * ', ' **  * ', '  ***  ', '  ***  ', '   *   '],
	['**    *', '**    *', '**    *', '** *  *', '*** * *', '**   **', '*     *'],
	['**    *', ' **  * ', '  ***  ', '   *   ', '  ***  ', ' **  * ', '**    *'],
	['**    *', ' **  * ', '  ***  ', '  **   ', '  **   ', '  **   ', '  **   '],
	['*******', '    ** ', '   **  ', '  **   ', ' **    ', '**     ', '*******'],
	['  ***  ', ' *   * ', '*   * *', '*  *  *', '* *   *', ' *   * ', '  ***  '],
	['   *   ', '  **   ', ' * *   ', '   *   ', '   *   ', '   *   ', '*******'],
	[' ***** ', '*     *', '      *', '     * ', '   **  ', ' **    ', '*******'],
	[' ***** ', '*     *', '      *', '    ** ', '      *', '*     *', ' ***** '],
	['    *  ', '   **  ', '  * *  ', ' *  *  ', '*******', '    *  ', '    *  '],
	['*******', '*      ', '****** ', '      *', '      *', '*     *', ' ***** '],
	['  **** ', ' *     ', '*      ', '****** ', '*     *', '*     *', ' ***** '],
	['*******', '     * ', '    *  ', '   *   ', '  *    ', ' *     ', '*      '],
	[' ***** ', '*     *', '*     *', ' ***** ', '*     *', '*     *', ' ***** '],
	[' ***** ', '*     *', '*     *', ' ******', '      *', '     * ', ' ****  ']]});
Server-side Implementations

To complete the form processing on the server, you compare the hash value computed from the text entered by the user with the hash value generated on the client. If the two match, then you have a "real person" submitting the form and can continue.

The following server side implementations of the hash algorithm are available:

PHP

function rpHash($value) {
	$hash = 5381;
	$value = strtoupper($value);
	for($i = 0; $i < strlen($value); $i++) {
		$hash = (($hash << 5) + $hash) + ord(substr($value, $i));
	}
	return $hash;
}

if (rpHash($_POST['realPerson'].salt) == $_POST['realPersonHash']) {
	// Accepted

PHP - 64 bit

function rpHash($value) {
	$hash = 5381;
	$value = strtoupper($value);
	for($i = 0; $i < strlen($value); $i++) {
		$hash = (leftShift32($hash, 5) + $hash) + ord(substr($value, $i));
	}
	return $hash;
}

// Perform a 32bit left shift
function leftShift32($number, $steps) {
	// convert to binary (string)
	$binary = decbin($number);
	// left-pad with 0's if necessary
	$binary = str_pad($binary, 32, "0", STR_PAD_LEFT);
	// left shift manually
	$binary = $binary.str_repeat("0", $steps);
	// get the last 32 bits
	$binary = substr($binary, strlen($binary) - 32);
	// if it's a positive number return it
	// otherwise return the 2's complement
    return ($binary{0} == "0" ? bindec($binary) :
		-(pow(2, 31) - bindec(substr($binary, 1))));
}

if (rpHash($_POST['realPerson'].salt) == $_POST['realPersonHash']) {
	// Accepted

Java

private String rpHash(String value) {
	int hash = 5381;
	value = value.toUpperCase();
	for(int i = 0; i < value.length(); i++) {
		hash = ((hash << 5) + hash) + value.charAt(i);
	}
	return String.valueOf(hash);
}

if (rpHash(request.getParameter("realPerson") + salt).equals(
		request.getParameter("realPersonHash"))) {
	// Accepted

C#

private string rpHash(string value) {
	int hash = 5381;
	value = value.ToUpper();
	for (int i = 0; i < value.Length; i++) {
		hash = ((hash << 5) + hash) + value[i];
	}
	return hash.ToString();
}

if (rpHash(Request.Form["realPerson"] + salt) == Request.Form["realPersonHash"]) {
	// Accepted

Cold Fusion

Thanks to Emil Gudmundsson.

<cffunction name="rpHash" access="public" returntype="string"
output="false">
   <cfargument name="capText" type="string" required="true" />
		
   <cfset var theHash = 5381 />
   <cfset var theValue = UCase(ARGUMENTS.capText) />

   <cfloop from="1" to="#Len(Trim(theValue))#" index="i">
      <cfset theHash = (bitSHLN(theHash,5) + theHash) + Asc(Mid(theValue,i,1)) />
   </cfloop>
		
   <cfreturn theHash />
</cffunction>

<cfif rpHash(FORM.realPerson) EQ FORM.realPersonHash>
	Accepted
</cfif>

Rails

Thanks to S. Muellner.

class FormController < BaseController

	# POST /form
	# POST /form.json
	def create
		realPerson = params[:realPerson]
		realPersonHash = params[:realPersonHash]
		
	    if !realPerson.nil? && !realPersonHash.nil? && Integer(rpHash(realPerson)) == Integer(realPersonHash)
			# Accepted
		else 
			# Rejected
		end
	end

private 

	def rpHash (defaultReal)
		hash = 5381
		if !defaultReal.nil?
			defaultReal.upcase!
			defaultReal.length.times{ |i| hash = ((shift_32 hash, 5) + hash) + defaultReal[i].ord }
		end
		return hash
	end
	
	def shift_32 x, shift_amount
	  shift_amount &= 0x1F
	  x <<= shift_amount
	  x &= 0xFFFFFFFF 
	
	  if (x & (1<<31)).zero?
	   x
	  else
	   x - 2**32
	  end
	end

end

Python

Thanks to Sérgio H. Berlotto Jr.

import numpy as np
#------------------------------
def rpHash(person):
	hash = 5381
 
	value = person.upper()
	for caracter in value:
		hash = (( np.left_shift(hash, 5) + hash) + ord(caracter))
	hash = np.int32(hash)
#-----------------------------
 
if rpHash(request.form['realPerson']) == request.form['realPersonHash']:
	# Accepted
else:
	# Rejected
In the Wild

This tab highlights examples of this plugin in use "in the wild".

To add another example, please contact me (wood.keith{at}optusnet.com.au) and provide the plugin name, the URL of your site, its title, and a short description of its purpose and where/how the plugin is used.

Quick Reference

A full list of all possible settings is shown below. Note that not all would apply in all cases. For more detail see the documentation reference page.

$(selector).realperson({
	length: 6, // Number of characters to use
	regenerate: 'Click to change', // Instruction text to regenerate
	hashName: '{n}Hash', // Name of the hash value field to compare with,
		// use {n} to substitute with the original field name
	dot: '*', // The character to use for the dot patterns
	dots: $.realperson.defaultDots, // The dot patterns per letter in chars
	chars: $.realperson.alphabetic // The characters allowed */
});

$.salt // A salt value to add to the entered text, or the selector for its field

$.realperson.alphabetic // Set of alphabetic characters
$.realperson.alphanumeric // Set of alphabetic and numeric characters
$.realperson.defaultDots // The default set of dots that make up each character

$.realperson.setDefaults(settings) // Change settings for all instances

$(selector).realperson('option', settings) // Change the instance settings
$(selector).realperson('option', name, value) // Change a single instance setting
$(selector).realperson('option', name) // Retrieve an instance setting

$(selector).realperson('enable') // Enable the control
$(selector).realperson('disable') // Disable the control

$(selector).realperson('destroy') // Remove the real person functionality