<?php

/**
  * Nucleus: PHP/MySQL Weblog CMS (http://nucleuscms.org/) 
  * Copyright (C) 2002-2004 The Nucleus Group
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
  * as published by the Free Software Foundation; either version 2
  * of the License, or (at your option) any later version.
  * (see nucleus/documentation/index.html#license for more info)
  *
  * Plugin responsible for creating and checking captcha images
  *
  * Captcha image generation based on:
  *   - hn_captcha by Horst Nogajski
  *     (http://hn273.users.phpclasses.org/browse/package/1569.html)
  *   - ocr_captcha by Julien Pachet
  *     (http://new21.mirrors.phpclasses.org/browse/package/1538.html)
  *
  * The Dustismo font was created by Dustin Norlander
  *	(http://www.dustismo.com/)
  *
  * $Id: NP_Captcha.php,v 1.2 2004/11/25 01:46:52 rado2 Exp $
  */
  
/**
  * This class generates a picture to use in forms that perform CAPTCHA test
  * (Completely Automated Public Turing to tell Computers from Humans Apart).
  * After the test form is submitted a key entered by the user in a text field
  * is compared by the class to determine whether it matches the text in the picture.
  * 
  * The class is a fork of the original released at www.phpclasses.org
  * by Julien Pachet with the name ocr_captcha.
  * 
  * How the class works
  *
  * 1. generateKey()
  *		- creates a new entry in the nucleus_captcha table (marked as inactive)
  *		- returns the key for that entry
  *
  * 2. including in a form -> generateImgHtml($key, $width, $height) / generateImgUrl($key, $width, $height)
  *		- add a hidden field containing the key, as generated by generateId
  *		- add an image action.php?action=plugin&name=Captcha&type=captcha&key={key here}
  * 
  * 3. action.php?...key={key here} -> generateImage($key)
  *    action.php?...key={key here}&width={width}&height={height} -> generateImage($key, $w, $h)
  *		- checks if key exists
  *		- chooses a random solution, and stores an md5 hash of it in nucleus_captcha
  *		- mark the entry in nucleus_captcha as active
  *		- generates a captcha image, and returns it to the client
  *
  * 4. check($key, $solution)
  *		- check if key exists, and if the captcha has been activated 
  *		- check if md5($solution) corresponds to the database entry.
  *		- delete entry in database (implies that only one try is allowed. when
  *       the answer is incorrect, a new captcha needs to be generated)
  *
  * Other plugins can generate/include/check captchas using the methods listed
  * above 
  *
  * e.g.
  *    global $manager
  *	   $npcaptcha =& $manager->getPlugin('NP_Captcha');
  *    $key = $npcaptcha->generateKey();
  *    $imgHtml = $npcatcha->getImgHtml($key);
  *    ...output imgHtml, hidden field with key, anwser field...
  *    ...
  *    $key = postVar('keyfield');
  *	   $sol = postVar('solutionfield');
  *    if ($npcaptcha->check($key, $sol)) ...
  *   
  * NP_Captcha version history
  *		v1.1 [2004-08-13 dekarma]
  *			- moved the generated HTML and error message into plugin options
  *		v1.0 [2004-08-12 dekarma]
  *			- added captcha image to account activation form
  *		v0.9 [2004-08-08 dekarma]
  *			- switched to jpeg images (about 3KB for an image compared to 10KB for png)
  *			- added plugin options:
  *				- jpeg quality
  *				- min number of chars
  *				- grid or noise?
  *				- websafe colors?
  *				- font(s)
  *				- cleanup time
  *			- added comments
  *			- when no GD library available, the plugin does nothing
  *			- instead of dying on errors, an image is generated with the error message.
  *			  (captcha is not activated when such an error occurs)
  *		v0.1 [2004-08-07 dekarma]
  *			- initial version. png images
  *
  **/

class NP_Captcha extends NucleusPlugin {

	function getName() {       return 'Captcha';	}
	function getAuthor()  {    return 'Nucleus Group; mod by Radek Hulan';	}
	function getURL(){         return 'http://plugins.nucleuscms.org/';	}
	function getVersion() {    return '1.2'; }
	function getMinNucleusVersion() { return 310; }
	function getMinNucleusPatchLevel() { return 1; }
	function getEventList() {  return array('FormExtra', 'ValidateForm'); }
	function getTableList() {  return array( sql_table('plug_captcha') ); }
	function getDescription() 
	{
		return 'Adds captcha images to anonymous comment and registration forms, to prevent robots from spamming. This plugin is NOT compatible wit CommentPreview plugin, it MUST be uninstalled!';
	}
	

	function supportsFeature($what) 
	{
		switch ($what)
		{
			case 'SqlTablePrefix':
			case 'HelpPage':
				return 1;
			default:
				return 0;
		}
	}
	
	function init() {
    
	}
	
	/**
	 * The captcha plugin uses a table to keep track of which captcha images were created at which
	 * time, and of their solutions. Checking the validity of a captcha deletes the entry
	 * in the table, which means only one attempt to answer is allowed per captcha. Also, old 
	 * entries in the table are deleted after a certain period of time.
	 */
	function install() {
		global $SQL_TYPE;
		switch ($SQL_TYPE){
			case 0:
			case 1:
				sql_query(
				  'CREATE TABLE ' . sql_table('plug_captcha') 
				. ' ('
				. '  ckey varchar(40) NOT NULL default \'\','
				. '  time datetime NOT NULL default \'0000-00-00 00:00:00\','
				. '  solution varchar(40) NOT NULL default \'\','
				. '  active tinyint(2) NOT NULL default \'0\','
				. '  PRIMARY KEY  (ckey)'
				. ' ) TYPE=MyISAM COMMENT=\'Plugin: Captcha\';'
				);
			break;
			case 2:
				sql_query(
				  'CREATE TABLE ' . sql_table('plug_captcha') 
				. ' ('
				. '  ckey varchar(40), '
				. '  time datetime, '
				. '  solution varchar(40),'
				. '  active tinyint(2)'
				. ' );'
				);
				sql_query("create index idx_plug_captcha on ".sql_table('plug_captcha')." (ckey)");
			break;
		}
        
        
		$activationHtml = 
			'</tr><tr>' . "\n"
			.'	<td>' . "\n"
			.'		<label for="nucleus_cf_verif"><%imgHtml%></label>' . "\n"
			.'	</td>' . "\n"
			.'	<td>' . "\n"
			.'		<input name="ver_sol" size="6" maxlength="6" value="" class="formfield" id="nucleus_cf_verif" />' . "\n"
			.'		<input name="ver_key" type="hidden" value="<%key%>" />' . "\n"
			.'		(<label for="nucleus_cf_verif">Enter the string of characters in the picture</label>)' . "\n"
			.'	</td>';
		$commentFormHtml = 
			'<fieldset><legend>Spam control</legend>' . "\n"
			.'<label for="nucleus_cf_verif"><%imgHtml%></label>' . "\n"
			.'<br />' . "\n"
			.'<label for="nucleus_cf_verif">Enter the string of characters appearing in the picture:</label> <input name="ver_sol" size="6" maxlength="6" value="" class="formfield" id="nucleus_cf_verif" />' . "\n"
			.'<input name="ver_key" type="hidden" value="<%key%>" />' . "\n"
			.'</fieldset>';
		$mailFormHtml = $commentFormHtml;
        
		$this->createOption('JpegQuality', 'Quality of images (100=full, 0=close to none)', 'text', '75', 'numerical=true');
		$this->createOption('MinChars', 'Minimum characters in image', 'text', '6', 'numerical=true');
		$this->createOption('TTFFonts', 'Fonts to use (separate with ; if multiple fonts are to be uses)', 'text', 'Dustismo.ttf', '');
		$this->createOption('WebSafe', 'Web Safe colors only?', 'yesno', 'no', '');
		$this->createOption('Background', 'Type of background?', 'select', 'noise', 'Noise Characters|noise|Grid|grid');	
		$this->createOption('TTL', 'Cleanup entries older than X minutes', 'text', '30', 'numerical=true');
		$this->createOption('FailedMsg', 'Error message', 'text', 'Captcha challenge failed. Are you man or machine?', '');
		$this->createOption('ActivationHtml', 'Activation Form Template', 'textarea', $activationHtml, '');
		$this->createOption('CommentFormHtml', 'Comment Form Template', 'textarea', $commentFormHtml, '');
		$this->createOption('MemberMailHtml', 'Mail Form Template', 'textarea', $mailFormHtml, '');
	}
	
	/**
	 * It's safe to drop the table. All info in it is of temporary nature.
	 */
	function unInstall() {
		sql_query(
			'DROP TABLE ' . sql_table('plug_captcha')
		);
	}

	/**
	 * The 'captcha' type action is used to generate the captcha image dynamically.
	 * <img src="action.php?action=plugin&name=Captcha&type=captcha&key=xxxxxx">
	 */
	function doAction($actionType) {
		if ($actionType != 'captcha')
			return 'invalid action';
			
		// initialize on first call
		if (!$this->inited)
			$this->init_captcha();
			
		$key 	= getVar('key');
		$width	= intGetVar('width');
		$height	= intGetVar('height');

		if ($width < 200) $width = -1;
		if ($height < 25) $height = -1;
		
		$this->generateImage($key, $width, $height);
	}
	
	/**
	 * Called from e.g. the commentform-notloggedin.template file, at the time
	 * a comment form is included. We'll add HTML code to insert the captcha image
	 */
	function event_FormExtra(&$data) {
		
		switch ($data['type'])
		{
			case 'commentform-notloggedin':			// anonymous comments
				// NP_CommentPreview plugin installed?
				global $manager;
				$plugin =& $manager->getPlugin('NP_CommentPreview');
				// NP_CommentPreview plugin not installed
				if (!$plugin) return false;
				// installed and enabled - leave Catcha only for preview 
				if ($plugin->getOption('enable') =='yes' ) return;
				break;
			case 'membermailform-notloggedin':		// anonymous message to site member
			case 'activation':						// activation or re-activation of member account
				break;
			case 'commentpreview-notloggedin':		// anonymous comments previews
				break;
			default:
				return;
		}
		// initialize on first call
		if (!$this->inited)
			$this->init_captcha();
			
		// don't do anything when no GD libraries are available
		if (!$this->isAvailable())
			return;

		// create captcha key. This key is required to 
		//
		// 1. create the captcha image
		// 2. check the validity of the entered solution
		$key = $this->generateKey();
		
		$aVars = array(
			'imgHtml' => $this->generateImgHtml($key),
			'key' => htmlspecialchars($key)
		);
		
		switch ($data['type'])
		{
			case 'activation':
				echo TEMPLATE::fill($this->getOption('ActivationHtml'), $aVars);
				break;
			case 'commentform-notloggedin':
			case 'commentpreview-notloggedin':
				echo TEMPLATE::fill($this->getOption('CommentFormHtml'), $aVars);
				break;
			case 'membermailform-notloggedin':
				echo TEMPLATE::fill($this->getOption('MemberMailHtml'), $aVars);
				break;				
		}
	}
	
	/**
	 * Called when a comment or member mail message is validated. We'll check if the 
	 * provided captcha solution is correct here. If not, we'll return an error.
	 */
	function event_ValidateForm(&$data) {
	
		switch ($data['type'])
		{
			case 'comment':
				if ( isset($data['preview']) &&  $data['preview'] == 'preview') 
					break; // always validate comment preview
				// NP_CommentPreview plugin installed?
				global $manager;
				$plugin =& $manager->getPlugin('NP_CommentPreview');
				if (!$plugin) 
					break; // NP_CommentPreview plugin not installed, thus validate
				// installed and enabled - leave Catcha only for preview 
				if ($plugin->getOption('enable') =='yes' ) 
					return;
				break;
			case 'membermail':
			case 'activation':
				break;
			default:
				return;
		}
		
		// initialize on first call
		if (!$this->inited)
			$this->init_captcha();
		
		// don't do anything when no GD libraries are available
		if (!$this->isAvailable())
			return;
		
		global $member;
		
		// captchas are not used for registered members
		if ($member->isLoggedIn())
			return;
	
		// get key and attempted solution from request
		$ver_key = postVar('ver_key');
		$ver_sol = postVar('ver_sol');
	
		// check if the solution matches what is in the database
		if (!$this->check($ver_key, $ver_sol)) {
			$data['error'] = $this->getOption('FailedMsg');
		} else {
		  // delete captcha key
		  $this->_deleteKey($key);
    }

	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// Captcha generating/checking methods are below.
	////////////////////////////////////////////////////////////////////////////////////////////////////

	/**
	 * Returns the URL of the captcha image. The image will be created upon first call
	 * of the URL. When requested multiple times, all but the first request will fail.
	 */
	function generateImgUrl($key, $width = -1, $height = -1)
	{
		global $CONF;
		
		$imgUrl = $CONF['ActionURL'] . '?action=plugin&name=Captcha&type=captcha&key=' . $key;	
		
		if (($width != -1) && ($height != -1))
			$imgUrl .= '&width=' . intval($width) . '&height=' . intval($height);
			
		return $imgUrl;
	}
	
	/**
	 * Returns an <img src=...> tag that includes the captcha image in the output.
	 */
	function generateImgHtml($key, $width = -1, $height = -1)
	{
		$imgUrl  = $this->generateImgUrl($key, $width, $height);
		if ($width == -1)		$width = $this->lx;
		if ($height == -1)		$height = $this->ly;
		$imgHtml = '<img src="' . htmlspecialchars($imgUrl) . '"  width="' . intval($width). '" height="' . intval($height) . ' " alt="captcha image, to tell computers and humans apart." title="Please enter the string of characters appearing in this picture in the input field below." />';
		return $imgHtml;
	}
	
	/**
	 * Returns true only when GD libraries are available. If these libraries
	 * are not installed, it's impossible to generate captcha images
	 */
	function isAvailable() {
		return ($this->gd_version > 0);
	}

	/**
	 * Generates a random identifier string that will be used to identify
	 * a captcha. This ID will get included as hidden variable in the form
	 * together with the captcha image. Once the form is submitted back to
	 * the server, the ID is used to verify the captcha (solution to the
	 * captcha will be in the database, linked to the ID)
	 */
	function generateKey()
	{
		// initialize on first call
		if (!$this->inited)
			$this->init_captcha();
	
		$ok = false;
		
		while (!$ok)
		{
      // generate a random token
			srand((double)microtime()*1000000);
			$key = md5(uniqid(rand(), true));
		  $query=sql_query('select ckey from '.$this->table.' where ckey="'.sql_escape($key).'"');
		  if (sql_num_rows($query)==0) {
			   // add in database as non-active
			   $query = 'INSERT INTO ' . $this->table . ' (ckey, time, solution, active) ';
			   $query .= 'VALUES (\'' . sql_escape($key). '\', \'' . date('Y-m-d H:i:s',time()) . '\', \'\', \'0\')';
			   sql_query($query);
			   $ok = true;
	    }
		}
		
		return $key;
	}
	
	/**
	 * Checks if a given solution is the correct one for a given key. For each active key, this 
	 * method can only be called once. After the call, the entry is removed from the database.
	 */
	function check($key, $solution)
	{
		// initialize on first call
		if (!$this->inited)
			$this->init_captcha();
	
		// cleanup old captchas 
		$this->_removeOldEntries();
	
		// check if key exists
		if (!$this->_existsKey($key))
			return false;
		
		// get info
		$res = sql_query('SELECT * FROM ' . $this->table . ' WHERE ckey=\'' . sql_escape($key) . '\'');
		if ($res) 
			$o = sql_fetch_object($res);
			
		if (!$res || !$o)
			return false;

		// check if captcha entry is active
		if ($o->active != 1)
			return false;
			
		// check solution
		if (md5(strtoupper($solution)) != $o->solution)
			return false;
			
		// correct solution for captcha challenge 
		return true;
	}
	
	/**
	 * Generates a captcha image and outputs it to standard output (image/png)
	 */
	function generateImage($key, $width = -1, $height = -1)
	{
		// initialize on first call
		if (!$this->inited)
			$this->init_captcha();
			
		// re-calculate settings when explicit width/height is specified
		if (($width != -1) && ($height != -1) && ($width >= 200) && ($height >= 25))
		{
			// generate appropriate settings for the new width
			$this->lx = $width;
			$this->ly = $height;
			$this->maxsize = (int)($this->ly / 2.4);
			if ($this->maxsize < $this->minsize)
				$this->minsize = $this->maxsize;
			$this->chars = (int) ($this->lx / (int)(($this->maxsize + $this->minsize) / 1.5)) - 1;			
		}
		
		// cleanup old captchas (older than 10 minutes)
		$this->_removeOldEntries();
	
		// cannot create an image if there is no gd library
		if ($this->gd_version == 0)
			die('no gd library');

		if(count($this->TTF_RANGE) < 1)
			$this->_error_img($this->lx, $this->ly, 'no font available.');

		// make sure it's not possible to create a captcha with only 1 or even no characters
		// by messing with the width and height
		if ($this->chars < $this->minchars)
			$this->_error_img($this->lx, $this->ly, 'unsafe to create.');

		// check if key exists
		if (!$this->_existsKey($key))
			$this->_error_img($this->lx, $this->ly, 'invalid key.');

		// captcha must be inactive
		if ($this->_isActive($key))
			$this->_error_img($this->lx, $this->ly, 'already activated.');

		// generate solution (random string of $this->chars characters)
		$private_key = $this->_generateSolution();		
		
		// store key in database & mark as activated
		$query = 'UPDATE ' . $this->table . ' SET active=1, solution=\'' . sql_escape(md5($private_key)) . '\' WHERE ckey=\'' . sql_escape($key) . '\'';
		sql_query($query);

		// create Image and set the apropriate function depending on GD-Version & websafecolor-value
		if($this->gd_version >= 2 && !$this->websafecolors)
		{
			$func_createImg = 'imagecreatetruecolor';
			$func_color = 'imagecolorallocate';
		}
		else
		{
			$func_createImg = 'imageCreate';
			$func_color = 'imagecolorclosest';
		}
		$image = call_user_func($func_createImg, $this->lx, $this->ly);
		if($this->debug) echo "\n<br>-Captcha-Debug: Generate ImageStream with: ($func_createImg())";
		if($this->debug) echo "\n<br>-Captcha-Debug: For colordefinitions we use: ($func_color())";
		
		// select first TrueTypeFont
		$this->_change_TTF();
		if($this->debug) echo "\n<br>-Captcha-Debug: Set current TrueType-File: (".$this->TTF_file.")";

		// Set Backgroundcolor
		$this->_random_color(224, 255);
		$back =  @imagecolorallocate($image, $this->r, $this->g, $this->b);
		@imagefilledrectangle($image,0,0,$this->lx,$this->ly,$back);
		if($this->debug) echo "\n<br>-Captcha-Debug: We allocate one color for Background: (".$this->r."-".$this->g."-".$this->b.")";

		// allocates the 216 websafe color palette to the image
		if($this->gd_version < 2 || $this->websafecolors) $this->_makeWebsafeColors($image);

		// fill with noise or grid
		if($this->nb_noise > 0)
		{
			// random characters in background with random position, angle, color
			if($this->debug) echo "\n<br>-Captcha-Debug: Fill background with noise: (".$this->nb_noise.")";
			for($i=0; $i < $this->nb_noise; $i++)
			{
				srand((double)microtime()*1000000);
				$size	= intval(rand((int)($this->minsize / 2.3), (int)($this->maxsize / 1.7)));
				srand((double)microtime()*1000000);
				$angle	= intval(rand(0, 360));
				srand((double)microtime()*1000000);
				$x		= intval(rand(0, $this->lx));
				srand((double)microtime()*1000000);
				$y		= intval(rand(0, (int)($this->ly - ($size / 5))));
				$this->_random_color(160, 224);
				$color	= call_user_func($func_color, $image, $this->r, $this->g, $this->b);
				srand((double)microtime()*1000000);
				$text	= chr(intval(rand(45,250)));
				@imagettftext($image, $size, $angle, $x, $y, $color, $this->_change_TTF(), $text);
			}
		}
		else
		{
			// generate grid
			if($this->debug) echo "\n<br>-Captcha-Debug: Fill background with x-gridlines: (".(int)($this->lx / (int)($this->minsize / 1.5)).")";
			for($i=0; $i < $this->lx; $i += (int)($this->minsize / 1.5))
			{
				$this->_random_color(160, 224);
				$color	= call_user_func($func_color, $image, $this->r, $this->g, $this->b);
				@imageline($image, $i, 0, $i, $this->ly, $color);
			}
			if($this->debug) echo "\n<br>-Captcha-Debug: Fill background with y-gridlines: (".(int)($this->ly / (int)(($this->minsize / 1.8))).")";
			for($i=0 ; $i < $this->ly; $i += (int)($this->minsize / 1.8))
			{
				$this->_random_color(160, 224);
				$color	= call_user_func($func_color, $image, $this->r, $this->g, $this->b);
				@imageline($image, 0, $i, $this->lx, $i, $color);
			}
		}

		// generate Text
		if($this->debug) echo "\n<br>-Captcha-Debug: Fill forground with chars and shadows: (".$this->chars.")";
		for($i=0, $x = intval(rand($this->minsize,$this->maxsize)); $i < $this->chars; $i++)
		{
			$text	= strtoupper(substr($private_key, $i, 1));
			srand((double)microtime()*1000000);
			$angle	= intval(rand(($this->maxrotation * -1), $this->maxrotation));
			srand((double)microtime()*1000000);
			$size	= intval(rand($this->minsize, $this->maxsize));
			srand((double)microtime()*1000000);
			$y		= intval(rand((int)($size * 1.5), (int)($this->ly - ($size / 7))));
			$this->_random_color(0, 127);
			$color	=  call_user_func($func_color, $image, $this->r, $this->g, $this->b);
			$this->_random_color(0, 127);
			$shadow = call_user_func($func_color, $image, $this->r + 127, $this->g + 127, $this->b + 127);
			@imagettftext($image, $size, $angle, $x + (int)($size / 15), $y, $shadow, $this->_change_TTF(), $text);
			@imagettftext($image, $size, $angle, $x, $y - (int)($size / 15), $color, $this->TTF_file, $text);
			$x += (int)($size + ($this->minsize / 5));
		}

		header('Content-Type: image/jpeg');
		@imagejpeg($image, '', $this->jpeg_quality);
		@imagedestroy($image);

	}
	
////////////////////////////////
//
//	PUBLIC PARAMS
//

	/**
	  * @shortdesc Absolute path to folder with TrueTypeFonts (with trailing slash!). This must be readable by PHP.
	  * @type string
	  * @public
	  *
	  **/
	var $TTF_folder;

	/**
	  * @shortdesc A List with available TrueTypeFonts for random char-creation.
	  * @type mixed[array|string]
	  * @public
	  *
	  **/
	var $TTF_RANGE  = array('Dustismo.ttf');

	/**
	  * @shortdesc How many chars the generated text should have
	  * @type integer
	  * @public
	  *
	  **/
	var $chars		= 6;

	/**
	  * @shortdesc The minimum size a Char should have
	  * @type integer
	  * @public
	  *
	  **/
	var $minsize	= 20;

	/**
	  * @shortdesc The maximum size a Char can have
	  * @type integer
	  * @public
	  *
	  **/
	var $maxsize	= 30;

	/**
	  * @shortdesc The maximum degrees a Char should be rotated. Set it to 30 means a random rotation between -30 and 30.
	  * @type integer
	  * @public
	  *
	  **/
	var $maxrotation = 30;

	/**
	  * @shortdesc Background noise On/Off (if is Off, a grid will be created)
	  * @type boolean
	  * @public
	  *
	  **/
	var $noise		= TRUE;

	/**
	  * @shortdesc This will only use the 216 websafe color pallette for the image.
	  * @type boolean
	  * @public
	  *
	  **/
	var $websafecolors = FALSE;

	/**
	  * @shortdesc Outputs configuration values for testing
	  * @type boolean
	  * @public
	  *
	  **/
	var $debug = FALSE;
	
	/**
	  * @shortdesc JPEG quality (100 = best, 0 = clos to none)
	  * @type int
	  * @public
	  *
	  **/
	var $jpeg_quality = 75;
	
	
	/**
	  * @shortdesc don't generate captchas with less than $minchars chars 
	  * @type int
	  * @public
	  *
	  **/
	var $minchars = 6;		

	/**
	  * @shortdesc captchas live no longer than $ttl minutes
	  * @type int
	  * @public
	  *
	  **/
	var $ttl = 30;			
	



////////////////////////////////
//
//	PRIVATE PARAMS
//
	/** @private **/
	var $table;				// MySQL table with captcha entries
	/** @private **/
	var $inited = 0;		// indicates that init_captcha has been called once
	/** @private **/
	var $lx;				// width of picture
	/** @private **/
	var $ly;				// height of picture
	/** @private **/
	var $noisefactor = 9;	// this will multiplyed with number of chars
	/** @private **/
	var $nb_noise;			// number of background-noise-characters
	/** @private **/
	var $TTF_file;			// holds the current selected TrueTypeFont
	/** @private **/
	var $gd_version;		// holds the Version Number of GD-Library
	/** @private **/
	var $r;
	/** @private **/
	var $g;
	/** @private **/
	var $b;
	
	// private functions
	
	/**
	 * Initializes and checks internal variables
	 */
	function init_captcha()
	{
		// read options
		$this->jpeg_quality  = $this->getOption('JpegQuality');
		$this->minchars      = $this->getOption('MinChars');
		$this->TTF_RANGE     = explode(';', $this->getOption('TTFFonts'));
		$this->websafecolors = ($this->getOption('WebSafe') == 'yes') ? TRUE : FALSE;
		$this->noise         = ($this->getOption('Background') == 'noise') ? TRUE : FALSE;	
		$this->ttl           = $this->getOption('TTL');		
	
		$this->table = sql_table('plug_captcha');
		
		$this->TTF_folder = $this->getDirectory();
		$this->debug = FALSE;
		
		// Test for GD-Library(-Version)
		$this->gd_version = $this->_get_gd_version();
		if($this->debug) echo "\n<br>-Captcha-Debug: The available GD-Library has major version ".$this->gd_version;

		// check settings		
		if($this->minsize > $this->maxsize)
		{
			$temp = $this->minsize;
			$this->minsize = $this->maxsize;
			$this->maxsize = $temp;
			if($this->debug) echo "<br>-Captcha-Debug: Arrghh! What do you think I mean with min and max? Switch minsize with maxsize.";
		}
		
		// check TrueTypeFonts
		if (!is_array($this->TTF_RANGE))
			$this->TTF_RANGE = array();

		if($this->debug) echo "\n<br>-Captcha-Debug: Check given TrueType-Array! (".count($this->TTF_RANGE).")";
		$temp = array();
		foreach($this->TTF_RANGE as $k=>$v)
		{
			if(is_readable($this->TTF_folder.$v)) $temp[] = $v;
		}
		$this->TTF_RANGE = $temp;
		if($this->debug) echo "\n<br>-Captcha-Debug: Valid TrueType-files: (".count($this->TTF_RANGE).")";

		// get number of noise-chars for background if is enabled
		$this->nb_noise = $this->noise ? ($this->chars * $this->noisefactor) : 0;
		if($this->debug) echo "\n<br>-Captcha-Debug: Set number of noise characters to: (".$this->nb_noise.")";


		// set (initial) dimension of image 
		$this->lx = ($this->chars + 1) * (int)(($this->maxsize + $this->minsize) / 1.5);
		$this->ly = (int)(2.4 * $this->maxsize);
		if($this->debug) echo "\n<br>-Captcha-Debug: Set image dimension to: (".$this->lx." x ".$this->ly.")";
		
		// make sure the method is called only once
		$this->inited = 1;
	}
	
	
	/** @private **/
	function _existsKey($key)
	{
		return (quickQuery('SELECT COUNT(*) AS result FROM ' . $this->table . ' WHERE ckey=\'' . sql_escape($key) . '\'') == 1);
	}

	/** @private **/
	function _isActive($key)
	{
		return (quickQuery('SELECT active AS result FROM ' . $this->table . ' WHERE ckey=\'' . sql_escape($key) . '\'') == 1);
	}
	
	/** @private **/
	function _deleteKey($key)
	{
		sql_query('DELETE FROM ' . $this->table . ' WHERE ckey=\''.sql_escape($key).'\'');
	}
	
	/** @private **/
	function _removeOldEntries()
	{
		$boundary = time() - $this->ttl * 60;	// no captcha lives for more than one hour 
		sql_query('DELETE FROM ' . $this->table . ' WHERE time < \'' . date('Y-m-d H:i:s',$boundary) . '\'');
	}
	
	/** @private **/
	function _get_gd_version()
	{
		if (function_exists('imagecreatetruecolor'))
			return 2;
		else
			return 0;	// no GD installed, or old version
	}
	
	/** @private **/
	function _change_TTF()
	{
		srand((float)microtime() * 10000000);
		$key = array_rand($this->TTF_RANGE);
		$this->TTF_file = $this->TTF_folder.$this->TTF_RANGE[$key];
		return $this->TTF_file;
	}
	
	/** @private **/
	function _random_color($min,$max)
	{
		srand((double)microtime() * 1000000);
		$this->r = intval(rand($min,$max));
		srand((double)microtime() * 1000000);
		$this->g = intval(rand($min,$max));
		srand((double)microtime() * 1000000);
		$this->b = intval(rand($min,$max));
	}

	/** @private **/
	function _makeWebsafeColors(&$image)
	{
		for($r = 0; $r <= 255; $r += 51)
		{
			for($g = 0; $g <= 255; $g += 51)
			{
				for($b = 0; $b <= 255; $b += 51)
				{
					$color = imagecolorallocate($image, $r, $g, $b);
				}
			}
		}
		if($this->debug) echo "\n<br>-Captcha-Debug: Allocate 216 websafe colors to image: (".imagecolorstotal($image).")";
	}
	
	/** @private **/
	function _generateSolution()
	{
		$letters = array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z');
		$numbers = array('1', '2', '3', '4', '5', '6', '7', '8', '9');

		$num_letters = count($letters);
		$num_numbers = count($numbers);

		$private_key = '';
		for($i = 0; $i < $this->chars / 2; $i++){
			srand((double)microtime()*1000000);
			$private_key = $letters[rand(0, $num_letters - 1)] . $private_key . $numbers[rand(0, $num_numbers - 1)];
		}
		return strtoupper(substr($private_key, 0, $this->chars));
	}
	
	/** @private **/
	function _error_img($w, $h, $error)
	{
		// create Image and set the apropriate function depending on GD-Version & websafecolor-value
		if($this->gd_version >= 2 && !$this->websafecolors)
		{
			$func_createImg = 'imagecreatetruecolor';
			$func_color = 'imagecolorallocate';
		}
		else
		{
			$func_createImg = 'imageCreate';
			$func_color = 'imagecolorclosest';
		}
		$image = call_user_func($func_createImg, $this->lx, $this->ly);

		// fill background in red
		$back =  @imagecolorallocate($image, 255, 128, 128);
		@imagefilledrectangle($image,0,0,$w,$h,$back);

		// add text
		$fore =  @imagecolorallocate($image, 255, 255, 255);
		imagestring($image, 3, 5, $this->ly/2 - 5, $error, $fore);

		// dump image
		header('Content-Type: image/jpeg');
		@imagejpeg($image, '', 90);
		@imagedestroy($image);
		
		exit;
	
	}
	

}
?>