Alpha captcha class.
Class declared in MODPATH/captcha/classes/captcha/alpha.php on line 12.
array $configDefault config values
array(8) ( "style" => string(5) "basic" "width" => integer 150 "height" => integer 50 "complexity" => integer 4 "background" => string(0) "" "fontpath" => string(0) "" "fonts" => array(0) "promote" => bool FALSE )
object $instanceCaptcha singleton
NULL
string $driverStyle-dependent Captcha driver
string $imageImage resource identifier
string $image_typeImage type ("png", "gif" or "jpeg")
string $responseThe correct Captcha challenge answer
Generates a new Captcha challenge.
string - The challenge answerpublic function generate_challenge()
{
// Complexity setting is used as character count
$text = text::random('distinct', max(1, Captcha::$config['complexity']));
// Complexity setting is used as character count
return $text;
}
Outputs the Captcha image.
boolean
$html
= bool TRUE - Html outputmixedpublic function render($html = TRUE)
{
// Creates $this->image
$this->image_create(Captcha::$config['background']);
// Add a random gradient
if (empty(Captcha::$config['background']))
{
$color1 = imagecolorallocate($this->image, mt_rand(0, 100), mt_rand(0, 100), mt_rand(0, 100));
$color2 = imagecolorallocate($this->image, mt_rand(0, 100), mt_rand(0, 100), mt_rand(0, 100));
$this->image_gradient($color1, $color2);
}
// Add a few random circles
for ($i = 0, $count = mt_rand(10, Captcha::$config['complexity'] * 3); $i < $count; $i++)
{
$color = imagecolorallocatealpha($this->image, mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255), mt_rand(80, 120));
$size = mt_rand(5, Captcha::$config['height'] / 3);
imagefilledellipse($this->image, mt_rand(0, Captcha::$config['width']), mt_rand(0, Captcha::$config['height']), $size, $size, $color);
}
// Calculate character font-size and spacing
$default_size = min(Captcha::$config['width'], Captcha::$config['height'] * 2) / strlen($this->response);
$spacing = (int) (Captcha::$config['width'] * 0.9 / strlen($this->response));
// Background alphabetic character attributes
$color_limit = mt_rand(96, 160);
$chars = 'ABEFGJKLPQRTVY';
// Draw each Captcha character with varying attributes
for ($i = 0, $strlen = strlen($this->response); $i < $strlen; $i++)
{
// Use different fonts if available
$font = Captcha::$config['fontpath'].Captcha::$config['fonts'][array_rand(Captcha::$config['fonts'])];
$angle = mt_rand(-40, 20);
// Scale the character size on image height
$size = $default_size / 10 * mt_rand(8, 12);
$box = imageftbbox($size, $angle, $font, $this->response[$i]);
// Calculate character starting coordinates
$x = $spacing / 4 + $i * $spacing;
$y = Captcha::$config['height'] / 2 + ($box[2] - $box[5]) / 4;
// Draw captcha text character
// Allocate random color, size and rotation attributes to text
$color = imagecolorallocate($this->image, mt_rand(150, 255), mt_rand(200, 255), mt_rand(0, 255));
// Write text character to image
imagefttext($this->image, $size, $angle, $x, $y, $color, $font, $this->response[$i]);
// Draw "ghost" alphabetic character
$text_color = imagecolorallocatealpha($this->image, mt_rand($color_limit + 8, 255), mt_rand($color_limit + 8, 255), mt_rand($color_limit + 8, 255), mt_rand(70, 120));
$char = $chars[mt_rand(0, 13)];
imagettftext($this->image, $size * 2, mt_rand(-45, 45), ($x - (mt_rand(5, 10))), ($y + (mt_rand(5, 10))), $text_color, $font, $char);
}
// Output
return $this->image_render($html);
}
Constructs a new Captcha object.
string
$group
= NULL - Config group namevoidpublic function __construct($group = NULL)
{
// Create a singleton instance once
empty(Captcha::$instance) and Captcha::$instance = $this;
// No config group name given
if ( ! is_string($group))
{
$group = 'default';
}
// Load and validate config group
if ( ! is_array($config = Kohana::$config->load('captcha')->get($group)))
throw new Kohana_Exception('Captcha group not defined in :group configuration',
array(':group' => $group));
// All captcha config groups inherit default config group
if ($group !== 'default')
{
// Load and validate default config group
if ( ! is_array($default = Kohana::$config->load('captcha')->get('default')))
throw new Kohana_Exception('Captcha group not defined in :group configuration',
array(':group' => 'default'));
// Merge config group with default config group
$config += $default;
}
// Assign config values to the object
foreach ($config as $key => $value)
{
if (array_key_exists($key, Captcha::$config))
{
Captcha::$config[$key] = $value;
}
}
// Store the config group name as well, so the drivers can access it
Captcha::$config['group'] = $group;
// If using a background image, check if it exists
if ( ! empty($config['background']))
{
Captcha::$config['background'] = str_replace('\\', '/', realpath($config['background']));
if ( ! is_file(Captcha::$config['background']))
throw new Kohana_Exception('The specified file, :file, was not found.',
array(':file' => Captcha::$config['background']));
}
// If using any fonts, check if they exist
if ( ! empty($config['fonts']))
{
Captcha::$config['fontpath'] = str_replace('\\', '/', realpath($config['fontpath'])).'/';
foreach ($config['fonts'] as $font)
{
if ( ! is_file(Captcha::$config['fontpath'].$font))
throw new Kohana_Exception('The specified file, :file, was not found.',
array(':file' => Captcha::$config['fontpath'].$font));
}
}
// Generate a new challenge
$this->response = $this->generate_challenge();
Kohana::$log->add(Log::DEBUG, 'Captcha Library initialized');
}
Magically outputs the Captcha challenge.
mixedpublic function __toString()
{
return $this->render(TRUE);
}
Creates an image resource with the dimensions specified in config. If a background image is supplied, the image dimensions are used.
string
$background
= NULL - Path to the background image filevoidpublic function image_create($background = NULL)
{
// Check for GD2 support
if ( ! function_exists('imagegd2'))
throw new Kohana_Exception('captcha.requires_GD2');
// Create a new image (black)
$this->image = imagecreatetruecolor(Captcha::$config['width'], Captcha::$config['height']);
// Use a background image
if ( ! empty($background))
{
// Create the image using the right function for the filetype
$function = 'imagecreatefrom'.$this->image_type($background);
$this->background_image = $function($background);
// Resize the image if needed
if (imagesx($this->background_image) !== Captcha::$config['width']
or imagesy($this->background_image) !== Captcha::$config['height'])
{
imagecopyresampled
(
$this->image, $this->background_image, 0, 0, 0, 0,
Captcha::$config['width'], Captcha::$config['height'],
imagesx($this->background_image), imagesy($this->background_image)
);
}
// Free up resources
imagedestroy($this->background_image);
}
}
Fills the background with a gradient.
resource
$color1
required - GD image color identifier for start colorresource
$color2
required - GD image color identifier for end colorstring
$direction
= NULL - Direction: 'horizontal' or 'vertical', 'random' by defaultvoidpublic function image_gradient($color1, $color2, $direction = NULL)
{
$directions = array('horizontal', 'vertical');
// Pick a random direction if needed
if ( ! in_array($direction, $directions))
{
$direction = $directions[array_rand($directions)];
// Switch colors
if (mt_rand(0, 1) === 1)
{
$temp = $color1;
$color1 = $color2;
$color2 = $temp;
}
}
// Extract RGB values
$color1 = imagecolorsforindex($this->image, $color1);
$color2 = imagecolorsforindex($this->image, $color2);
// Preparations for the gradient loop
$steps = ($direction === 'horizontal') ? Captcha::$config['width'] : Captcha::$config['height'];
$r1 = ($color1['red'] - $color2['red']) / $steps;
$g1 = ($color1['green'] - $color2['green']) / $steps;
$b1 = ($color1['blue'] - $color2['blue']) / $steps;
if ($direction === 'horizontal')
{
$x1 =& $i;
$y1 = 0;
$x2 =& $i;
$y2 = Captcha::$config['height'];
}
else
{
$x1 = 0;
$y1 =& $i;
$x2 = Captcha::$config['width'];
$y2 =& $i;
}
// Execute the gradient loop
for ($i = 0; $i <= $steps; $i++)
{
$r2 = $color1['red'] - floor($i * $r1);
$g2 = $color1['green'] - floor($i * $g1);
$b2 = $color1['blue'] - floor($i * $b1);
$color = imagecolorallocate($this->image, $r2, $g2, $b2);
imageline($this->image, $x1, $y1, $x2, $y2, $color);
}
}
Returns the img html element or outputs the image to the browser.
boolean
$html
required - Output as HTMLmixed - HTML, string or voidpublic function image_render($html)
{
// Output html element
if ($html === TRUE)
return '<img src="'.URL::site('captcha/'.Captcha::$config['group']).'" width="'.Captcha::$config['width'].'" height="'.Captcha::$config['height'].'" alt="Captcha" class="captcha" />';
// Send the correct HTTP header
Request::current()->response()
->headers('Content-Type', 'image/'.$this->image_type)
->headers('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0')
->headers('Pragma', 'no-cache')
->headers('Connection', 'close');
// Pick the correct output function
$function = 'image'.$this->image_type;
$function($this->image);
// Free up resources
imagedestroy($this->image);
}
Returns the image type.
string
$filename
required - Filenamestring|boolean - Image type ("png", "gif" or "jpeg")public function image_type($filename)
{
switch (strtolower(substr(strrchr($filename, '.'), 1)))
{
case 'png':
return 'png';
case 'gif':
return 'gif';
case 'jpg':
case 'jpeg':
// Return "jpeg" and not "jpg" because of the GD2 function names
return 'jpeg';
default:
return FALSE;
}
}
Singleton instance of Captcha.
string
$group
= string(7) "default" - Config group nameobjectpublic static function instance($group = 'default')
{
if ( ! isset(Captcha::$instance))
{
// Load the configuration for this group
$config = Kohana::$config->load('captcha.'.$group);
// Set the captcha driver class name
$class = 'Captcha_'.ucfirst($config['style']);
// Create a new captcha instance
Captcha::$instance = $captcha = new $class($group);
// Save captcha response at shutdown
//register_shutdown_function(array($captcha, 'update_response_session'));
}
return Captcha::$instance;
}
Gets or sets the number of invalid Captcha responses for this session.
integer
$new_count
= NULL - New counter valueinteger - Counter valuepublic function invalid_count($new_count = NULL)
{
return $this->valid_count($new_count, TRUE);
}
Checks whether user has been promoted after having given enough valid responses.
integer
$threshold
= NULL - Valid response count thresholdbooleanpublic function promoted($threshold = NULL)
{
// Promotion has been disabled
if (Captcha::$config['promote'] === FALSE)
return FALSE;
// Use the config threshold
if ($threshold === NULL)
{
$threshold = Captcha::$config['promote'];
}
// Compare the valid response count to the threshold
return ($this->valid_count() >= $threshold);
}
Resets the Captcha response counters and removes the count sessions.
voidpublic function reset_count()
{
$this->valid_count(0);
$this->valid_count(0, TRUE);
}
Update captcha response session variable.
voidpublic function update_response_session()
{
// Store the correct Captcha response in a session
Session::instance()->set('captcha_response', sha1(strtoupper($this->response)));
}
Validates user's Captcha response and updates response counter.
string
$response
required - User's captcha responsebooleanpublic static function valid($response)
{
// Maximum one count per page load
static $counted;
// User has been promoted, always TRUE and don't count anymore
if (Captcha::instance()->promoted())
return TRUE;
// Challenge result
$result = (bool) (sha1(strtoupper($response)) === Session::instance()->get('captcha_response'));
// Increment response counter
if ($counted !== TRUE)
{
$counted = TRUE;
// Valid response
if ($result === TRUE)
{
Captcha::instance()->valid_count(Session::instance()->get('captcha_valid_count') + 1);
}
// Invalid response
else
{
Captcha::instance()->invalid_count(Session::instance()->get('captcha_invalid_count') + 1);
}
}
return $result;
}
Gets or sets the number of valid Captcha responses for this session.
integer
$new_count
= NULL - New counter valueboolean
$invalid
= bool FALSE - Trigger invalid counter (for internal use only)integer - Counter valuepublic function valid_count($new_count = NULL, $invalid = FALSE)
{
// Pick the right session to use
$session = ($invalid === TRUE) ? 'captcha_invalid_count' : 'captcha_valid_count';
// Update counter
if ($new_count !== NULL)
{
$new_count = (int) $new_count;
// Reset counter = delete session
if ($new_count < 1)
{
Session::instance()->delete($session);
}
// Set counter to new value
else
{
Session::instance()->set($session, (int) $new_count);
}
// Return new count
return (int) $new_count;
}
// Return current count
return (int) Session::instance()->get($session);
}