<?php
class AKB_CAPTCHA
{
	var $secret = '';
	var $type = '';
	var $bg = '';
	var $maxLoads = 2;
	var $imgDir = '';
	var $urlPrefix = '';
	var $imgAlign = 'left';
	var $fontFile = '';
	var $fontSize = 22;
	var $textColor = 'black';
	var $bgCol1 = '';
	var $bgCol2 = '';
	var $bgFill = 'solid';

	/**
	* Constructor
	* Setups up some variables for later
	*
	* @return void
	*/
	function AKB_CAPTCHA()
	{
		if (function_exists('imageCreateFromPNG')
		&& function_exists('imagettftext')) {
			$this->type= 'dynamic';
			$this->bg = 'imagecode_bg.png';
			$this->maxLoads = 2;
		} else {
			$this->type = 'static';
		}

		// If we are running php less than 4.2.0 we have to manually seed
		// mt_rand otherwise php does it for us
		if (version_compare(PHP_VERSION, '4.2.0', '<')) {
			mt_srand($this->MakeSeed());
		}
		if (isset($GLOBALS['rightToLeft']) && $GLOBALS['rightToLeft']) {
			if ($this->imgAlign == 'left') {
				$this->imgAlign = 'right';
			} else {
				$this->imgAlign = 'right';
			}
		}

	}

	/**
	* generateSecret
	* Generate a random string $len chars long and set it to $this->secret
	*
	* @param string $len Length of string to generate (default 6)
	*
	* @return void
	*/
	function generateSecret($len=6)
	{
		$bannedChars = array (
			'o', 'O', '0',
			'i', 'l', '1', 'I',
			'g', '9',
			'5', 's',
		);

		$secret = '';
		while (strlen($secret) < $len) {
			$nextChar = mt_rand(0, 35); // 10 digits + 26 lowercase = 36 chars
			if (($nextChar >=10) && ($nextChar < 36)) { // lowercase letters
				$nextChar -= 10; // bases the number at 0 instead of 10
				$nextChar = chr($nextChar + 97); // ord('a') == 97
			} else { // 0-9
				$nextChar = chr($nextChar + 48); // ord('0') == 48
			}
			if (!in_array($nextChar, $bannedChars)) {
				$secret .= $nextChar;
			}
		}
		$this->secret = $secret;
		$_SESSION['captchaSecret'] = $secret;
		$_SESSION['captchaLoads'] = 0;
	}

	/**
	* loadSecret
	* Load a secret from the session into the $this->secret var
	*
	* @return void
	*/
	function loadSecret()
	{
		if (!isset($_SESSION['captchaSecret'])) {
			return;
		}

		if ($this->type == 'static' ) {
			$this->maxLoads = strlen($_SESSION['captchaSecret'])+1;
		}

		if ($_SESSION['captchaLoads'] > $this->maxLoads) {
			return;
		}

		if ($_SESSION['captchaLoads'] < 0) {
			return;
		}

		$this->secret = $_SESSION['captchaSecret'];
		$_SESSION['captchaLoads']++;


	}

	/**
	* generateDynamicImg
	* Create a CAPTCHA image using gd
	*
	* @return bool Returns false if the imageCreateFromPNG function isn't
	* available
	*/
	function generateDynamicImg()
	{
		// Why generate images if we can't ?
		if (!function_exists('imageCreateFromPNG')) {
			return false;
		}

		if (!is_file($this->bg)) {
			$img_handle = imageCreate($this->fontSize * strlen($this->secret),$this->fontSize * 2);
			// sets background to red
			// the first call to imagecolorallocate sets our bg for us
			// $red = imagecolorallocate($img_handle, 255, 0, 0);
		} else {
			$img_handle = imageCreateFromPNG($this->bg);
		}

		$col = $this->hex2rgb($this->textColor);

		$this->gd_gradient_fill($img_handle, $this->bgFillStyle, $this->bgCol1, $this->bgCol2);

		if ($col) {
			$text_color = ImageColorAllocate ($img_handle, $col['r'], $col['g'], $col['b']);
		} else {
			$text_color = ImageColorAllocate ($img_handle, 0, 0, 0);
		}

		if (file_exists($this->fontFile)) {
			$x = 0;
			for ($i=0; $i < strlen($this->secret); $i++) {
				imagettftext($img_handle, $this->fontSize, rand(-4, 3), $x, 30+rand(-1, 1), $text_color, $this->fontFile, $this->secret{$i});
				$x += $i + 0.75 * $this->fontSize;
			}
		} else {
			ImageString ($img_handle, 5, 20, 13, $this->secret, $text_color);
		}
		ImagePng ($img_handle);
		ImageDestroy ($img_handle);
	}

	/**
	* generateDynamicImgHtml
	* Generate the html for displaying the dynamic CAPTCHA img
	*
	* @return string The html
	*/
	function generateDynamicImgHtml()
	{
		$align = (empty($this->imgAlign)) ? '' : ' id="captchaImg" align="'.$this->imgAlign.'"';
		return '<img src="'.$this->urlPrefix.'"'.$align.' alt="" />';
	}

	/**
	* generateStaticImg
	* generate the image for a static (non gd) captcha at a given offset in the
	* secret
	*
	* @param int $offset
	*
	* @return void
	*/
	function generateStaticImg($offset)
	{
		$filename = $this->imgDir.'/'.substr($_SESSION['captchaSecret'], $offset, 1).'.png';
		if (!is_dir($this->imgDir)) {
			return false;
		}
		if (!is_file($filename)) {
			return false;
		}
		readfile($filename);
	}

	/**
	* generateStaticImgHtml
	* Generate the html for displaying the static (non gd) CAPTCHA img
	*
	* @return string The html
	*/
	function generateStaticImgHtml()
	{
		$align = (empty($this->imgAlign)) ? '' : ' align="'.$this->imgAlign.'"';
		$html = '';
		if ($this->imgAlign == 'right') {
			// We need a special case for align=right because the images will
			// end up being displayed in REVERSE ORDER
			for ($i = strlen($this->secret)-1; $i >= 0; $i--) {
				if ($GLOBALS['cleanUrls']) {
					$url = $this->urlPrefix.'/'.$i;
				} else {
					$url = $this->urlPrefix.'&amp;offset='.$i.'&amp;rand='.md5(uniqid(rand(), true));
				}
				$html .= '<img src="'.$url.'" id="captchaImg" alt=""'.$align.' />';
			}
		} else {
			for ($i = 0; $i < strlen($this->secret); $i++) {
				if ($GLOBALS['cleanUrls']) {
					$url = $this->urlPrefix.'/'.$i;
				} else {
					$url = $this->urlPrefix.'&amp;offset='.$i.'&amp;rand='.md5(uniqid(rand(), true));
				}
				$html .= '<img src="'.$url.'" id="captchaImg" alt=""'.$align.' />';
			}
		}
		return $html;
	}

	/**
	* generate
	* Generate the html to display the CAPTCHA
	*
	* @return string The html to display the CAPTCHA
	*/
	function generateHtml()
	{
		switch ($this->type) {
			case 'dynamic':
				return $this->generateDynamicImgHtml();
			break;
			case 'static':
			default:
				return $this->generateStaticImgHtml();
		}
	}

	/**
	* showImg
	* Show a captcha image at a given offset
	*
	* @param int $offset The offset in the secret of character to display
	*
	* @return bool Returns false if the secret hasn't been generated
	*/
	function showImg($offset)
	{
		if (!$this->secret) {
			return false;
		}
		if ($this->type == 'static') {
			$this->generateStaticImg($offset);
		} else {
			$this->generateDynamicImg();
		}
	}

	/**
	* Take a hex colour code and convert it to rgb
	*
	* @param string $hex the colour code in hex
	*
	* @return array an array of rgb colours or an empty array if $hex is invalid
	*/
	function hex2rgb($hex)
	{
		// If the first char is a # strip it off
		if (substr($hex, 0, 1) == '#') {
			$hex = substr($hex, 1);
		}

		// If the string isnt the right length return false
		if (strlen($hex) != 6) {
			return false;
		}

		$vals = array();
		$vals[]  = hexdec(substr($hex, 0, 2));
		$vals[]  = hexdec(substr($hex, 2, 2));
		$vals[]  = hexdec(substr($hex, 4, 2));
		$vals['r'] = $vals[0];
		$vals['g'] = $vals[1];
		$vals['b'] = $vals[2];
		return $vals;
	}

	/**
	* Does a gradient fill on an image
	*
	* @param resource $im Image to fill
	* @param string $direction The pattern to fill
	* @param string $start the start colour in hex
	* @param string $end the colour in hex
	*
	* @return void
	*/
	function gd_gradient_fill($im,$direction,$start,$end)
	{
		switch ($direction) {
			case 'horizontal':
				$line_numbers = imagesx($im);
				$line_width = imagesy($im);
				list($r1,$g1,$b1) = $this->hex2rgb($start);
				list($r2,$g2,$b2) = $this->hex2rgb($end);
			break;
			case 'vertical':
				$line_numbers = imagesy($im);
				$line_width = imagesx($im);
				list($r1,$g1,$b1) = $this->hex2rgb($start);
				list($r2,$g2,$b2) = $this->hex2rgb($end);
			break;
			case 'ellipse':
			case 'circle':
				$line_numbers = sqrt(pow(imagesx($im),2)+pow(imagesy($im),2));
				$center_x = imagesx($im)/2;
				$center_y = imagesy($im)/2;
				list($r1,$g1,$b1) = $this->hex2rgb($end);
				list($r2,$g2,$b2) = $this->hex2rgb($start);
			break;
			case 'square':
			case 'rectangle':
				$width = imagesx($im);
				$height = imagesy($im);
				$line_numbers = max($width,$height)/2;
				list($r1,$g1,$b1) = $this->hex2rgb($end);
				list($r2,$g2,$b2) = $this->hex2rgb($start);
			break;
			case 'diamond':
				list($r1,$g1,$b1) = $this->hex2rgb($end);
				list($r2,$g2,$b2) = $this->hex2rgb($start);
				$width = imagesx($im);
				$height = imagesy($im);
				$rh=$height>$width?1:$width/$height;
				$rw=$width>$height?1:$height/$width;
				$line_numbers = min($width,$height);
			break;
			default:
				list($r,$g,$b) = $this->hex2rgb($start);
				$col = imagecolorallocate($im,$r,$g,$b);
				imagefill($im, 0, 0, $col);
				return true;
		}

		for ( $i = 0; $i < $line_numbers; $i=$i+1 ) {
			$r = ( $r2 - $r1 != 0 ) ? $r1 + ( $r2 - $r1 ) * ( $i / $line_numbers ) : $r1;
			$g = ( $g2 - $g1 != 0 ) ? $g1 + ( $g2 - $g1 ) * ( $i / $line_numbers ) : $g1;
			$b = ( $b2 - $b1 != 0 ) ? $b1 + ( $b2 - $b1 ) * ( $i / $line_numbers ) : $b1;
			$fill = imagecolorallocate( $im, $r, $g, $b );
			switch ($direction) {
				case 'vertical':
					imageline( $im, 0, $i, $line_width, $i, $fill );
				break;
				case 'horizontal':
					imageline( $im, $i, 0, $i, $line_width, $fill );
				break;
				case 'ellipse':
				case 'circle':
					imagefilledellipse ($im,$center_x, $center_y, $line_numbers-$i, $line_numbers-$i,$fill);
				break;
				case 'square':
				case 'rectangle':
					imagefilledrectangle ($im,$i*$width/$height,$i*$height/$width,$width-($i*$width/$height), $height-($i*$height/$width),$fill);
				break;
				case 'diamond':
					imagefilledpolygon($im, array (
						$width/2, $i*$rw-0.5*$height,
						$i*$rh-0.5*$width, $height/2,
						$width/2,1.5*$height-$i*$rw,
						1.5*$width-$i*$rh, $height/2 ), 4, $fill);
				break;
				default:
			}
		}
	}

	/**
	* MakeSeed
	* Older phps dont automatically seend rand so we have to do it
	* manually
	*
	* @return float
	*/
	function MakeSeed()
	{
		list($usec, $sec) = explode(' ', microtime());
		return (float) $sec + ((float) $usec * 100000);
	}

	/**
	* RandFillType
	*
	* Set the fill type to a random one
	*
	* @return void
	*/
	function RandFillType()
	{
		$fill_types = array(
			'square',
			'diamond',
			'vertical',
			'horizontal'
		);
		$this->bgFillStyle	= $fill_types[array_rand($fill_types)];
	}

	/**
	* PickRandomFile
	*
	* Choose a random file from the given directory
	*
	* @param string $dir The directory to look in
	* @param string $extension The lowercase version of the extension to look for

	* @return mixed False on any problem, the full path to the background file if
	* a file in the right directory with a png extension was found
	*/
	function PickRandomFile($dir, $extension)
	{
		$files = array();

		if (!is_dir($dir)) {
			return false;
		}

		if (empty($extension)) {
			return false;
		}

		$dh = opendir($dir);

		if ($dh === false) {
			return false;
		}

		while (($file = readdir($dh)) !== false) {
			// Skip checking the current and parent directory
			if ($file == '.' || $file == '..') {
				continue;
			}

			if (is_file($dir.'/'.$file)) {
				$parts = explode('.', $file);
				$ext = array_pop($parts);
				if (strtolower($ext) == $extension) {
					$files[] = $dir.'/'.$file;
				}
			}
		}

		if (empty($files)) {
			return false;
		}

		return $files[array_rand($files)];
	}
}
?>
