PageRenderTime 49ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/modules/captcha/classes/captcha.php

https://bitbucket.org/seyar/parshin.local
PHP | 468 lines | 237 code | 64 blank | 167 comment | 24 complexity | e3822fa802766b03a687071b76873c18 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1
  1. <?php defined('SYSPATH') OR die('No direct access.');
  2. /**
  3. * Captcha abstract class.
  4. *
  5. * @package Captcha
  6. * @author Michael Lavers
  7. * @author Kohana Team
  8. * @copyright (c) 2008-2010 Kohana Team
  9. * @license http://kohanaphp.com/license.html
  10. */
  11. abstract class Captcha
  12. {
  13. /**
  14. * @var object Captcha singleton
  15. */
  16. public static $instance;
  17. /**
  18. * @var string Style-dependent Captcha driver
  19. */
  20. protected $driver;
  21. /**
  22. * @var array Default config values
  23. */
  24. public static $config = array
  25. (
  26. 'style' => 'basic',
  27. 'class' => '',
  28. 'width' => 150,
  29. 'height' => 50,
  30. 'complexity' => 4,
  31. 'background' => '',
  32. 'fontpath' => '',
  33. 'fonts' => array(),
  34. 'promote' => FALSE,
  35. 'session_type' => 'native',
  36. );
  37. /**
  38. * @var string The correct Captcha challenge answer
  39. */
  40. protected $response;
  41. /**
  42. * @var string Image resource identifier
  43. */
  44. protected $image;
  45. /**
  46. * @var string Image type ("png", "gif" or "jpeg")
  47. */
  48. protected $image_type = 'png';
  49. /**
  50. * Singleton instance of Captcha.
  51. *
  52. * @param string $group Config group name
  53. * @return object
  54. */
  55. public static function instance($group = 'default')
  56. {
  57. if ( ! isset(Captcha::$instance))
  58. {
  59. // Load the configuration for this group
  60. $config = Kohana::config('captcha')->get($group);
  61. // Set the captcha driver class name
  62. $class = 'Captcha_'.ucfirst($config['style']);
  63. // Create a new captcha instance
  64. Captcha::$instance = $captcha = new $class($group);
  65. // Save captcha response at shutdown
  66. register_shutdown_function(array($captcha, 'update_response_session'));
  67. }
  68. return Captcha::$instance;
  69. }
  70. /**
  71. * Constructs a new Captcha object.
  72. *
  73. * @throws Kohana_Exception
  74. * @param string Config group name
  75. * @return void
  76. */
  77. public function __construct($group = NULL)
  78. {
  79. // Create a singleton instance once
  80. empty(Captcha::$instance) and Captcha::$instance = $this;
  81. // No config group name given
  82. if ( ! is_string($group))
  83. {
  84. $group = 'default';
  85. }
  86. // Load and validate config group
  87. if ( ! is_array($config = Kohana::config('captcha')->get($group)))
  88. throw new Kohana_Exception('Captcha group not defined in :group configuration',
  89. array(':group' => $group));
  90. // All captcha config groups inherit default config group
  91. if ($group !== 'default')
  92. {
  93. // Load and validate default config group
  94. if ( ! is_array($default = Kohana::config('captcha')->get('default')))
  95. throw new Kohana_Exception('Captcha group not defined in :group configuration',
  96. array(':group' => 'default'));
  97. // Merge config group with default config group
  98. $config += $default;
  99. }
  100. // Assign config values to the object
  101. foreach ($config as $key => $value)
  102. {
  103. if (array_key_exists($key, Captcha::$config))
  104. {
  105. Captcha::$config[$key] = $value;
  106. }
  107. }
  108. // Store the config group name as well, so the drivers can access it
  109. Captcha::$config['group'] = $group;
  110. // If using a background image, check if it exists
  111. if ( ! empty($config['background']))
  112. {
  113. Captcha::$config['background'] = str_replace('\\', '/', realpath($config['background']));
  114. if ( ! is_file(Captcha::$config['background']))
  115. throw new Kohana_Exception('The specified file, :file, was not found.',
  116. array(':file' => Captcha::$config['background']));
  117. }
  118. // If using any fonts, check if they exist
  119. if ( ! empty($config['fonts']))
  120. {
  121. Captcha::$config['fontpath'] = str_replace('\\', '/', realpath($config['fontpath'])).'/';
  122. foreach ($config['fonts'] as $font)
  123. {
  124. if ( ! is_file(Captcha::$config['fontpath'].$font))
  125. throw new Kohana_Exception('The specified file, :file, was not found.',
  126. array(':file' => Captcha::$config['fontpath'].$font));
  127. }
  128. }
  129. // Generate a new challenge
  130. $this->response = $this->generate_challenge();
  131. }
  132. /**
  133. * Update captcha response session variable.
  134. *
  135. * @return void
  136. */
  137. public function update_response_session()
  138. {
  139. // Store the correct Captcha response in a session
  140. Session::instance(Captcha::$config['session_type'])->set('captcha_response', sha1(strtoupper($this->response)));
  141. }
  142. /**
  143. * Validates user's Captcha response and updates response counter.
  144. *
  145. * @staticvar integer $counted Captcha attempts counter
  146. * @param string $response User's captcha response
  147. * @return boolean
  148. */
  149. public static function valid($response)
  150. {
  151. // Maximum one count per page load
  152. static $counted;
  153. // User has been promoted, always TRUE and don't count anymore
  154. if (Captcha::instance()->promoted())
  155. return TRUE;
  156. // Challenge result
  157. $result = (bool) (sha1(strtoupper($response)) === Session::instance(Captcha::$config['session_type'])->get('captcha_response'));
  158. // Increment response counter
  159. if ($counted !== TRUE)
  160. {
  161. $counted = TRUE;
  162. // Valid response
  163. if ($result === TRUE)
  164. {
  165. Captcha::instance()->valid_count(Session::instance(Captcha::$config['session_type'])->get('captcha_valid_count') + 1);
  166. }
  167. // Invalid response
  168. else
  169. {
  170. Captcha::instance()->invalid_count(Session::instance(Captcha::$config['session_type'])->get('captcha_invalid_count') + 1);
  171. }
  172. }
  173. return $result;
  174. }
  175. /**
  176. * Gets or sets the number of valid Captcha responses for this session.
  177. *
  178. * @param integer $new_count New counter value
  179. * @param boolean $invalid Trigger invalid counter (for internal use only)
  180. * @return integer Counter value
  181. */
  182. public function valid_count($new_count = NULL, $invalid = FALSE)
  183. {
  184. // Pick the right session to use
  185. $session = ($invalid === TRUE) ? 'captcha_invalid_count' : 'captcha_valid_count';
  186. // Update counter
  187. if ($new_count !== NULL)
  188. {
  189. $new_count = (int) $new_count;
  190. // Reset counter = delete session
  191. if ($new_count < 1)
  192. {
  193. Session::instance(Captcha::$config['session_type'])->delete($session);
  194. }
  195. // Set counter to new value
  196. else
  197. {
  198. Session::instance(Captcha::$config['session_type'])->set($session, (int) $new_count);
  199. }
  200. // Return new count
  201. return (int) $new_count;
  202. }
  203. // Return current count
  204. return (int) Session::instance(Captcha::$config['session_type'])->get($session);
  205. }
  206. /**
  207. * Gets or sets the number of invalid Captcha responses for this session.
  208. *
  209. * @param integer $new_count New counter value
  210. * @return integer Counter value
  211. */
  212. public function invalid_count($new_count = NULL)
  213. {
  214. return $this->valid_count($new_count, TRUE);
  215. }
  216. /**
  217. * Resets the Captcha response counters and removes the count sessions.
  218. *
  219. * @return void
  220. */
  221. public function reset_count()
  222. {
  223. $this->valid_count(0);
  224. $this->valid_count(0, TRUE);
  225. }
  226. /**
  227. * Checks whether user has been promoted after having given enough valid responses.
  228. *
  229. * @param integer $threshold Valid response count threshold
  230. * @return boolean
  231. */
  232. public function promoted($threshold = NULL)
  233. {
  234. // Promotion has been disabled
  235. if (Captcha::$config['promote'] === FALSE)
  236. return FALSE;
  237. // Use the config threshold
  238. if ($threshold === NULL)
  239. {
  240. $threshold = Captcha::$config['promote'];
  241. }
  242. // Compare the valid response count to the threshold
  243. return ($this->valid_count() >= $threshold);
  244. }
  245. /**
  246. * Magically outputs the Captcha challenge.
  247. *
  248. * @return mixed
  249. */
  250. public function __toString()
  251. {
  252. return $this->render(TRUE);
  253. }
  254. /**
  255. * Returns the image type.
  256. *
  257. * @param string $filename Filename
  258. * @return string|boolean Image type ("png", "gif" or "jpeg")
  259. */
  260. public function image_type($filename)
  261. {
  262. switch (strtolower(substr(strrchr($filename, '.'), 1)))
  263. {
  264. case 'png':
  265. return 'png';
  266. case 'gif':
  267. return 'gif';
  268. case 'jpg':
  269. case 'jpeg':
  270. // Return "jpeg" and not "jpg" because of the GD2 function names
  271. return 'jpeg';
  272. default:
  273. return FALSE;
  274. }
  275. }
  276. /**
  277. * Creates an image resource with the dimensions specified in config.
  278. * If a background image is supplied, the image dimensions are used.
  279. *
  280. * @throws Kohana_Exception If no GD2 support
  281. * @param string $background Path to the background image file
  282. * @return void
  283. */
  284. public function image_create($background = NULL)
  285. {
  286. // Check for GD2 support
  287. if ( ! function_exists('imagegd2'))
  288. throw new Kohana_Exception('captcha.requires_GD2');
  289. // Create a new image (black)
  290. $this->image = imagecreatetruecolor(Captcha::$config['width'], Captcha::$config['height']);
  291. // Use a background image
  292. if ( ! empty($background))
  293. {
  294. // Create the image using the right function for the filetype
  295. $function = 'imagecreatefrom'.$this->image_type($background);
  296. $this->background_image = $function($background);
  297. // Resize the image if needed
  298. if (imagesx($this->background_image) !== Captcha::$config['width']
  299. or imagesy($this->background_image) !== Captcha::$config['height'])
  300. {
  301. imagecopyresampled
  302. (
  303. $this->image, $this->background_image, 0, 0, 0, 0,
  304. Captcha::$config['width'], Captcha::$config['height'],
  305. imagesx($this->background_image), imagesy($this->background_image)
  306. );
  307. }
  308. // Free up resources
  309. imagedestroy($this->background_image);
  310. }
  311. }
  312. /**
  313. * Fills the background with a gradient.
  314. *
  315. * @param resource $color1 GD image color identifier for start color
  316. * @param resource $color2 GD image color identifier for end color
  317. * @param string $direction Direction: 'horizontal' or 'vertical', 'random' by default
  318. * @return void
  319. */
  320. public function image_gradient($color1, $color2, $direction = NULL)
  321. {
  322. $directions = array('horizontal', 'vertical');
  323. // Pick a random direction if needed
  324. if ( ! in_array($direction, $directions))
  325. {
  326. $direction = $directions[array_rand($directions)];
  327. // Switch colors
  328. if (mt_rand(0, 1) === 1)
  329. {
  330. $temp = $color1;
  331. $color1 = $color2;
  332. $color2 = $temp;
  333. }
  334. }
  335. // Extract RGB values
  336. $color1 = imagecolorsforindex($this->image, $color1);
  337. $color2 = imagecolorsforindex($this->image, $color2);
  338. // Preparations for the gradient loop
  339. $steps = ($direction === 'horizontal') ? Captcha::$config['width'] : Captcha::$config['height'];
  340. $r1 = ($color1['red'] - $color2['red']) / $steps;
  341. $g1 = ($color1['green'] - $color2['green']) / $steps;
  342. $b1 = ($color1['blue'] - $color2['blue']) / $steps;
  343. if ($direction === 'horizontal')
  344. {
  345. $x1 =& $i;
  346. $y1 = 0;
  347. $x2 =& $i;
  348. $y2 = Captcha::$config['height'];
  349. }
  350. else
  351. {
  352. $x1 = 0;
  353. $y1 =& $i;
  354. $x2 = Captcha::$config['width'];
  355. $y2 =& $i;
  356. }
  357. // Execute the gradient loop
  358. for ($i = 0; $i <= $steps; $i++)
  359. {
  360. $r2 = $color1['red'] - floor($i * $r1);
  361. $g2 = $color1['green'] - floor($i * $g1);
  362. $b2 = $color1['blue'] - floor($i * $b1);
  363. $color = imagecolorallocate($this->image, $r2, $g2, $b2);
  364. imageline($this->image, $x1, $y1, $x2, $y2, $color);
  365. }
  366. }
  367. /**
  368. * Returns the img html element or outputs the image to the browser.
  369. *
  370. * @param boolean $html Output as HTML
  371. * @return mixed HTML, string or void
  372. */
  373. public function image_render($html)
  374. {
  375. // Output html element
  376. if ($html)
  377. return '<img src="'.url::site('captcha/'.Captcha::$config['group']).'" width="'.Captcha::$config['width'].'" height="'.Captcha::$config['height'].'" alt="Captcha" class="captcha '.Captcha::$config['class'].'" />';
  378. // Send the correct HTTP header
  379. $this->request->headers['Cache-Control'] = 'no-cache, must-revalidate';
  380. $this->request->headers['Expires'] = 'Sun, 30 Jul 1989 19:30:00 GMT';
  381. $this->request->headers['Content-Type'] = 'image/'.$this->image_type;
  382. // Pick the correct output function
  383. $function = 'image'.$this->image_type;
  384. $function($this->image);
  385. // Free up resources
  386. imagedestroy($this->image);
  387. }
  388. /* DRIVER METHODS */
  389. /**
  390. * Generate a new Captcha challenge.
  391. *
  392. * @return string The challenge answer
  393. */
  394. abstract public function generate_challenge();
  395. /**
  396. * Output the Captcha challenge.
  397. *
  398. * @param boolean $html Render output as HTML
  399. * @return mixed
  400. */
  401. abstract public function render($html = TRUE);
  402. } // End Captcha Class