<?php
/*
+--------------------------------------------------------------------------
|   TanyaCMS - Question and Answer Sofware
|   -----------------------------------------------------------------------
|   by fbschool dev team
|   (c) 2011 - 2012 FBSCHOOL
|   http://www.fbschool.com/tanyacms
|   -----------------------------------------------------------------------
|   This is NOT a freeware, use is subject to license terms
|   Support: 13455n@gmail.com
+--------------------------------------------------------------------------
*/

class core_captcha
{
	/**
     * Verification code
     *  char:  Characters
     *  angle: Character offset angle (-30 <= angle <= 30)
     *  color: Character color
     * 
     * @var     array
     * @access  private
     */
	public $code = array();
	
	/**
     * Font information
     *  space: Character interval (px)
     *  size:  Font size (px)
     *  left:  From image to the left of the first character in pixels (px)
     *  top:   Pixels which are the characters from the image above (px)
     *  file:  The path to the font file
     * 
     * @var     array
     * @access  private
     */
	public $font = array();
	
	/**
     * Image information
     *  type:   Image type
     *  mime:   MIME Type
     *  width:  Image width (px)
     *  height: Image height (px)
     *  func:   Methods for creating images
     * 
     * @var     array
     * @access  private
     */
	public $image = array();
	
	/**
     * Interference information
     *  type:    Interference types (false Means do not use the)
     *  density: Density of interference
     * 
     * @var     array
     * @access  private
     */
	public $molestation = array();
	
	/**
     * Background color (RGB)
     *  r: Red (0 - 255)
     *  g: Green (0 - 255)
     *  b: Blue (0 - 255)
     * 
     * @var     array
     * @access  private
     */
	public $bg_color = array();
	
	/**
     * The default foreground color (RGB)
     *  r: Red (0 - 255)
     *  g: Green (0 - 255)
     *  b: Blue (0 - 255)
     * 
     * @var     array
     * @access  private
     */
	public $fg_color = array();
	
	/**
     * Session Variable name
     * 
     * @var     string
     * @access  private
     */
	public static $session = 'ask_captcha';
	
	public $the_code;

	/**
     * Set code
     * 
     * @access  public
     * @param   array   Character information
     * characters    string  The allowed characters
     * length        int     Code length
     * deflect       boolean Characters are deflected
     * multicolor    boolean Character color
     * @return  void
     */
	function setCode($code)
	{
		if (is_array($code))
		{
			if (! isset($code['characters']) || ! is_string($code['characters']))
			{
				$code['characters'] = 'A-H,J-X,3-9';
			}
			if (! (is_integer($code['length']) || $code['length'] <= 0))
			{
				$code['length'] = 4;
			}
			if (! is_bool($code['deflect']))
			{
				$code['deflect'] = true;
			}
			if (! is_bool($code['multicolor']))
			{
				$code['multicolor'] = true;
			}
		}
		else
		{
			$code = array(
				'characters' => 'A-Z,2-9', 
				'length' => 4, 
				'deflect' => true, 
				'multicolor' => true
			);
		}
		
		$this->code = $code;
	}

	/**
     * Settings session Variable name
     * 
     * @access  public
     * @param   string  session Variable name
     * @return  void
     */
	function setSession($session)
	{
		if (isset($session) && ! empty($session))
		{
			self::$session = $session;
		}
	}

	/**
     * Set background color
     * 
     * @access  public
     * @param   array   RGB Color
     * @return  void
     */
	function setBgColor($color)
	{
		if (is_array($color) && is_integer($color['r']) && is_integer($color['g']) && is_integer($color['b']) && ($color['r'] >= 0 && $color['r'] <= 255) && ($color['g'] >= 0 && $color['g'] <= 255) && ($color['b'] >= 0 && $color['b'] <= 255))
		{
			$this->bg_color = $color;
		}
		else
		{
			$this->bg_color = array(
				'r' => 255, 
				'g' => 255, 
				'b' => 255
			);
		}
		
		// Sets the default foreground color, Contrast with background color
		$fg_color = array(
			'r' => 255 - $this->bg_color['r'], 
			'g' => 255 - $this->bg_color['g'], 
			'b' => 255 - $this->bg_color['b']
		);
		
		$this->setFgColor($fg_color);
	}

	/**
     * Set the interference information
     * 
     * @access  public
     * @param   array   Interference information
     *  type    string  Interference types (Options: false, 'point', 'line')
     *  density string  Density of interference (Options: 'normal', 'muchness', 'fewness')
     * @return  void
     */
	function setMolestation($molestation)
	{
		if (is_array($molestation))
		{
			if (! isset($molestation['type']) || ($molestation['type'] != 'point' && $molestation['type'] != 'line' && $molestation['type'] != 'both'))
			{
				$molestation['type'] = 'point';
			}
			if (! is_string($molestation['density']))
			{
				$molestation['density'] = 'normal';
			}
			$this->molestation = $molestation;
		}
		else
		{
			$this->molestation = array(
				'type' => 'point', 
				'density' => 'normal'
			);
		}
	}

	/**
     * Set the font information
     * 
     * @access  public
     * @param   array   Font information
     *   space  int     Character interval (px)
     *   size   int     Font size (px)
     *   left   int     From image to the left of the first character in pixels (px)
     *   top    int     Pixels which are the characters from the image above (px)
     *   file   string  The path to the font file
     * @return  void
     */
	function setFont($font)
	{
		if (is_array($font))
		{
			if (!is_integer($font['space']) || $font['space'] < 0)
			{
				$font['space'] = 5;
			}
			if (! is_integer($font['size']) || $font['size'] < 0)
			{
				$font['size'] = 12;
			}
			if (! is_integer($font['left']) || $font['left'] < 0 || $font['left'] > $this->image['width'])
			{
				$font['left'] = 5;
			}
			if (! is_integer($font['top']) || $font['top'] < 0 || $font['top'] > $this->image['height'])
			{
				$font['top'] = $this->image['height'] - 5;
			}
			
			$this->font = $font;
		}
		else
		{
			$this->font = array(
				'space' => 5, 
				'size' => 5, 
				'left' => 5, 
				'top' => ($this->image['height'] - 5), 
			);
		}
	}

	/**
     * Set the image information
     * 
     * @access  public
     * @param   array   Image information
     *   type   string  Image type (Options: 'png', 'gif', 'wbmp', 'jpg')
     *   width  int     Image width (px)
     *   height int     Image height (px)
     * @return  void
     */
	function setImage($image)
	{
		if (is_array($image))
		{
			if (! is_integer($image['width']) || $image['width'] <= 0)
			{
				$image['width'] = 50;
			}
			if (! is_integer($image['height']) || $image['height'] <= 0)
			{
				$image['height'] = 25;
			}
			$this->image = $image;
			$information = $this->getImageType($image['type']);
			if (is_array($information))
			{
				$this->image['mime'] = $information['mime'];
				$this->image['func'] = $information['func'];
			}
			else
			{
				$this->image['type'] = 'png';
				$information = $this->getImageType('png');
				$this->image['mime'] = $information['mime'];
				$this->image['func'] = $information['func'];
			}
		}
		else
		{
			$information = $this->getImageType('png');
			$this->image = array(
				'type' => 'png', 
				'mime' => $information['mime'], 
				'func' => $information['func'], 
				'width' => 50, 
				'height' => 25
			);
		}
	}

	/**
     * Draw the image
     * 
     * @access  public
     * @param   string  File name, Blank specifies that output to the browser
     * @return  void
     */
	function paint($filename = null)
	{		
		// Create image
		$im = imagecreatetruecolor($this->image['width'], $this->image['height']);
		
		// Set the background image
		$bg_color = imagecolorallocate($im, $this->bg_color['r'], $this->bg_color['g'], $this->bg_color['b']);
		imagefilledrectangle($im, 0, 0, $this->image['width'], $this->image['height'], $bg_color);
		
		// Generate code Related information
		$code = $this->generateCode();
		
		// Generate code:
		$the_code = '';
		
		// Writes characters to the image
		$num = count($code);
		$current_left = $this->font['left'];
		$current_top = $this->font['top'];
		
		for ($i = 0; $i < $num; $i ++)
		{
			$font_size = rand(($this->font['size'] / 2), $this->font['size']); // Random font size
			
			if (is_array($this->font['file']))
			{
				$font_file = $this->font['file'][rand(0, (sizeof($this->font['file']) - 1))];
			}
			else
			{
				$font_file = $this->font['file'];
			}
			
			$font_color = imagecolorallocate($im, $code[$i]['color']['r'], $code[$i]['color']['g'], $code[$i]['color']['b']);
			
			imagettftext($im, $font_size, $code[$i]['angle'], $current_left, $current_top, $font_color, $font_file, $code[$i]['char']);
			
			$current_left += $font_size + $this->font['space'];
			
			$the_code .= $code[$i]['char'];
		}
		
		$this->the_code = $the_code;
		
		// Initialize session
		// If session Is not enabled, Then open it
		//if (empty($_SESSION['SID'])) @session_start();
		

		//Use md5() Passwords are encrypted, Write session
		$_SESSION[self::$session] = md5($the_code);
		$_SESSION[self::$session . "_none_case"] = md5(strtolower($the_code));
		
		// Drask an image interference
		$this->paintMolestation($im);
		
		// Output
		if (isset($filename) && $filename != '')
		{
			$f_name = $this->image['func'];
			
			$f_name($im, $filename . $this->image['type']);
		}
		else
		{
			header("Cache-Control: no-cache, must-revalidate");
			header("Content-type: " . $this->image['mime']);
			
			$f_name = $this->image['func'];
			
			$f_name($im);
		}
		
		imagedestroy($im);
	}

	/**
     * Validate user input validation code
     * 
     * @param   string  The string entered by the user
     * @param   boolean Are case-sensitive
     * @return  boolean Properly return true
     */
	static function validate($input, $is_match_case = true)
	{
		if ($is_match_case)
		{
			return (strcmp($_SESSION[self::$session], md5($input)) == 0);
		}
		else
		{
			return (strcmp($_SESSION[self::$session . '_none_case'], md5(strtolower($input))) == 0);
		}
	}

	/**
     * Clear SESSION Records
     * 
     */
	static function clear()
	{
		unset($_SESSION[self::$session]);
		unset($_SESSION[self::$session . '_none_case']);
	}

	/**
     * Set the foreground color
     * 
     * @access  private
     * @param   array   RGB Color
     * @return  void
     */
	function setFgColor($color)
	{
		if (is_array($color) && is_integer($color['r']) && is_integer($color['g']) && is_integer($color['b']) && ($color['r'] >= 0 && $color['r'] <= 255) && ($color['g'] >= 0 && $color['g'] <= 255) && ($color['b'] >= 0 && $color['b'] <= 255))
		{
			$this->fg_color = $color;
		}
		else
		{
			$this->fg_color = array(
				'r' => 0, 
				'g' => 0, 
				'b' => 0
			);
		}
	}

	/**
     * Generate random CAPTCHA
     * 
     * @access  private
     * @return  array   Generate code:
     */
	function generateCode()
	{
		// Create strings that allow
		$characters = explode(',', $this->code['characters']);
		
		$num = count($characters);
		
		for ($i = 0; $i < $num; $i ++)
		{
			if (substr_count($characters[$i], '-') > 0)
			{
				$character_range = explode('-', $characters[$i]);
				for ($j = ord($character_range[0]); $j <= ord($character_range[1]); $j ++)
				{
					$array_allow[] = chr($j);
				}
			}
			else
			{
				$array_allow[] = $array_allow[$i];
			}
		}
		
		$index = 0;
		
		while (list($key, $val) = each($array_allow))
		{
			$array_allow_tmp[$index] = $val;
			$index ++;
		}
		
		$array_allow = $array_allow_tmp;
		
		// Generate a random string
		mt_srand((double)microtime() * 1000000);
		
		$code = array();
		$index = 0;
		$i = 0;
		
		while ($i < $this->code['length'])
		{
			$index = mt_rand(0, count($array_allow) - 1);
			$code[$i]['char'] = $array_allow[$index];
			if ($this->code['deflect'])
			{
				$code[$i]['angle'] = mt_rand(- 10, 10);
			}
			else
			{
				$code[$i]['angle'] = 0;
			}
			if ($this->code['multicolor'])
			{
				$code[$i]['color']['r'] = mt_rand(0, 255);
				$code[$i]['color']['g'] = mt_rand(0, 255);
				$code[$i]['color']['b'] = mt_rand(0, 255);
			}
			else
			{
				$code[$i]['color']['r'] = $this->fg_color['r'];
				$code[$i]['color']['g'] = $this->fg_color['g'];
				$code[$i]['color']['b'] = $this->fg_color['b'];
			}
			$i ++;
		}
		
		return $code;
	}

	/**
     * Get the image type
     * 
     * @access  private
     * @param   string  Extension
     * @return  [mixed] When an error is returned false
     */
	function getImageType($extension)
	{
		switch (strtolower($extension))
		{
			case 'png' :
				$information['mime'] = image_type_to_mime_type(IMAGETYPE_PNG);
				$information['func'] = 'imagepng';
				break;
			case 'gif' :
				$information['mime'] = image_type_to_mime_type(IMAGETYPE_GIF);
				$information['func'] = 'imagegif';
				break;
			case 'wbmp' :
				$information['mime'] = image_type_to_mime_type(IMAGETYPE_WBMP);
				$information['func'] = 'imagewbmp';
				break;
			case 'jpg' :
			case 'jpeg' :
			case 'jpe' :
				$information['mime'] = image_type_to_mime_type(IMAGETYPE_JPEG);
				$information['func'] = 'imagejpeg';
				break;
			default :
				$information = false;
		}
		return $information;
	}

	/**
     * Drask an image interference
     * 
     * @access  private
     * @param   resource Image resources
     * @return  void
     */
	function paintMolestation(&$im)
	{
		// Total pixels
		$num_of_pels = ceil($this->image['width'] * $this->image['height'] / 5);
		switch ($this->molestation['density'])
		{
			case 'fewness' :
				$density = ceil($num_of_pels / 3);
				break;
			case 'muchness' :
				$density = ceil($num_of_pels / 3 * 2);
				break;
			case 'normal' :
				$density = ceil($num_of_pels / 2);
			default :
		}
		
		switch ($this->molestation['type'])
		{
			case 'point' :
				$this->paintPoints($im, $density);
				break;
			case 'line' :
				$density = ceil($density / 30);
				$this->paintLines($im, $density);
				break;
			case 'both' :
				$density = ceil($density / 2);
				$this->paintPoints($im, $density);
				$density = ceil($density / 30);
				$this->paintLines($im, $density);
				break;
			default :
				break;
		}
	}

	/**
     * Draw something
     * 
     * @access  private
     * @param   resource Image resources
     * @param   int      Image resources
     * @return  void
     */
	function paintPoints(&$im, $quantity)
	{
		mt_srand((double)microtime() * 1000000);
		
		for ($i = 0; $i < $quantity; $i ++)
		{
			$randcolor = imagecolorallocate($im, mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255));
			imagesetpixel($im, mt_rand(0, $this->image['width']), mt_rand(0, $this->image['height']), $randcolor);
		}
	}

	/**
     * Drawing a line
     * 
     * @access  private
     * @param   resource Image resources
     * @param   int      Image resources
     * @return  void
     */
	function paintLines(&$im, $quantity)
	{
		mt_srand((double)microtime() * 1000000);
		
		for ($i = 0; $i < $quantity; $i ++)
		{
			$randcolor = imagecolorallocate($im, mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255));
			imageline($im, mt_rand(0, $this->image['width']), mt_rand(0, $this->image['height']), mt_rand(0, $this->image['width']), mt_rand(0, $this->image['height']), $randcolor);
		}
	}
}