<?php defined('SYSPATH') or die('No direct script access.');
/**
 * Captcha driver class.
 *
 * $Id: Captcha.php 3237 2008-07-30 12:10:20Z Geert $
 *
 * @package    Captcha
 * @author     Kohana Team
 * @copyright  (c) 2007-2008 Kohana Team
 * @license    http://kohanaphp.com/license.html
 */
abstract class Captcha_Driver {

	// The correct Captcha challenge answer
	protected $response;

	// Image resource identifier and type ("png", "gif" or "jpeg")
	protected $image;
	protected $image_type = 'png';

	/**
	 * Constructs a new challenge.
	 *
	 * @return  void
	 */
	public function __construct()
	{
		// Generate a new challenge
		$this->response = $this->generate_challenge();

		// Store the correct Captcha response in a session
		Event::add('system.post_controller', array($this, 'update_response_session'));
	}

	/**
	 * Generate a new Captcha challenge.
	 *
	 * @return  string  the challenge answer
	 */
	abstract public function generate_challenge();

	/**
	 * Output the Captcha challenge.
	 *
	 * @param   boolean  html output
	 * @return  mixed    the rendered Captcha (e.g. an image, riddle, etc.)
	 */
	abstract public function render($html);

	/**
	 * Stores the response for the current Captcha challenge in a session so it is available
	 * on the next page load for Captcha::valid(). This method is called after controller
	 * execution (in the system.post_controller event) in order not to overwrite itself too soon.
	 *
	 * @return  void
	 */
	public function update_response_session()
	{
		Session::instance()->set('captcha_response', sha1(strtoupper($this->response)));
	}

	/**
	 * Validates a Captcha response from a user.
	 *
	 * @param   string   captcha response
	 * @return  boolean
	 */
	public function valid($response)
	{
		return (sha1(strtoupper($response)) === Session::instance()->get('captcha_response'));
	}

	/**
	 * Returns the image type.
	 *
	 * @param   string        filename
	 * @return  string|FALSE  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;
		}
	}

	/**
	 * Creates an image resource with the dimensions specified in config.
	 * If a background image is supplied, the image dimensions are used.
	 *
	 * @throws  Kohana_Exception  if no GD2 support
	 * @param   string  path to the background image file
	 * @return  void
	 */
	public 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($filename);
			$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.
	 *
	 * @param   resource  gd image color identifier for start color
	 * @param   resource  gd image color identifier for end color
	 * @param   string    direction: 'horizontal' or 'vertical', 'random' by default
	 * @return  void
	 */
	public 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.
	 *
	 * @param   boolean  html output
	 * @return  mixed    html string or void
	 */
	public function image_render($html)
	{
		// Output html element
		if ($html)
			return '<img alt="Captcha" src="'.url::site('captcha/'.Captcha::$config['group']).'" width="'.Captcha::$config['width'].'" height="'.Captcha::$config['height'].'" />';

		// Send the correct HTTP header
		header('Content-Type: image/'.$this->image_type);

		// Pick the correct output function
		$function = 'image'.$this->image_type;
		$function($this->image);

		// Free up resources
		imagedestroy($this->image);
	}

} // End Captcha Driver
