468 lines
14 KiB
PHP
468 lines
14 KiB
PHP
|
|
<?php
|
||
|
|
/**
|
||
|
|
* iNewS Project
|
||
|
|
*
|
||
|
|
* LICENSE
|
||
|
|
*
|
||
|
|
* http://www.inews.com.cn/license/inews
|
||
|
|
*
|
||
|
|
* @category iNewS
|
||
|
|
* @package ChangeMe
|
||
|
|
* @subpackage ChangeMe
|
||
|
|
* @copyright Copyright (c) 2008 Zeed Technologies PRC Inc. (http://www.inews.com.cn)
|
||
|
|
* @author xSharp ( GTalk: xSharp@gmail.com )
|
||
|
|
* @since May 7, 2010
|
||
|
|
* @version SVN: $Id$
|
||
|
|
*/
|
||
|
|
|
||
|
|
class Zeed_Captcha_Image3
|
||
|
|
{
|
||
|
|
protected $_options = array(
|
||
|
|
'width' => 200,
|
||
|
|
'height' => 70,
|
||
|
|
'font' => null,
|
||
|
|
'fontColor' => array(0x1B4EB5),
|
||
|
|
'wordLen' => 6,
|
||
|
|
'wordUseNumbers' => true,
|
||
|
|
'backgroundColor' => 0xFFFFFF,
|
||
|
|
'wordsfile' => '',
|
||
|
|
'lineColors' => array(0x1B4EB5,0xFFFFFF, 0x1B4EB5));
|
||
|
|
|
||
|
|
protected $_word;
|
||
|
|
protected $_id;
|
||
|
|
|
||
|
|
static $V = array("a", "e", "i", "o", "u", "y");
|
||
|
|
static $VN = array("a", "e", "i", "o", "u", "y", "2", "3", "4", "5", "6", "7", "8", "9");
|
||
|
|
static $C = array(
|
||
|
|
"b",
|
||
|
|
"c",
|
||
|
|
"d",
|
||
|
|
"f",
|
||
|
|
"g",
|
||
|
|
"h",
|
||
|
|
"j",
|
||
|
|
"k",
|
||
|
|
"m",
|
||
|
|
"n",
|
||
|
|
"p",
|
||
|
|
"q",
|
||
|
|
"r",
|
||
|
|
"s",
|
||
|
|
"t",
|
||
|
|
"u",
|
||
|
|
"v",
|
||
|
|
"w",
|
||
|
|
"x",
|
||
|
|
"z");
|
||
|
|
static $CN = array(
|
||
|
|
"b",
|
||
|
|
"c",
|
||
|
|
"d",
|
||
|
|
"f",
|
||
|
|
"g",
|
||
|
|
"h",
|
||
|
|
"j",
|
||
|
|
"k",
|
||
|
|
"m",
|
||
|
|
"n",
|
||
|
|
"p",
|
||
|
|
"q",
|
||
|
|
"r",
|
||
|
|
"s",
|
||
|
|
"t",
|
||
|
|
"u",
|
||
|
|
"v",
|
||
|
|
"w",
|
||
|
|
"x",
|
||
|
|
"z",
|
||
|
|
"2",
|
||
|
|
"3",
|
||
|
|
"4",
|
||
|
|
"5",
|
||
|
|
"6",
|
||
|
|
"7",
|
||
|
|
"8",
|
||
|
|
"9");
|
||
|
|
|
||
|
|
public function __construct($config = null)
|
||
|
|
{
|
||
|
|
if (is_array($config)) {
|
||
|
|
foreach ($config as $k => $v) {
|
||
|
|
if (isset($this->_options[$k])) {
|
||
|
|
$this->_options[$k] = $v;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
public function generate()
|
||
|
|
{
|
||
|
|
$id = $this->_generateRandomId();
|
||
|
|
$this->_word = $this->_generateWord();
|
||
|
|
self::setValidWord($id, $this->_word);
|
||
|
|
|
||
|
|
return $id;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function display()
|
||
|
|
{
|
||
|
|
$word = ! empty($this->_word) ? $this->_word : $this->_generateWord();
|
||
|
|
$this->createImage($word);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
protected function _generateWord()
|
||
|
|
{
|
||
|
|
if(is_file($this->_options['wordsfile'])) {
|
||
|
|
$fsize = filesize($this->_options['wordsfile']);
|
||
|
|
$fp = fopen($this->_options['wordsfile'], 'r');
|
||
|
|
$word = '';
|
||
|
|
while (mb_strlen($word) <= 2) {
|
||
|
|
$pos = rand(0, $fsize - 100);
|
||
|
|
fseek($fp, $pos);
|
||
|
|
while ("\n" != ($c = fgetc($fp))) {
|
||
|
|
$pos ++;
|
||
|
|
}
|
||
|
|
$word .= trim(fgets($fp));
|
||
|
|
}
|
||
|
|
if (mb_strlen($word) > 4) {
|
||
|
|
$word = mb_substr($word, -4);
|
||
|
|
}
|
||
|
|
return $word;
|
||
|
|
}
|
||
|
|
|
||
|
|
$word = '';
|
||
|
|
$wordLen = $this->_options['wordLen'];
|
||
|
|
if ($this->_options['wordUseNumbers']) {
|
||
|
|
$vowels = $this->_options['wordUseNumbers'] ? self::$VN : self::$V;
|
||
|
|
$consonants = $this->_options['wordUseNumbers'] ? self::$CN : self::$C;
|
||
|
|
} else {
|
||
|
|
$vowels = $this->_options['wordUseNumbers'] ? self::$VN : self::$V;
|
||
|
|
$consonants = $this->_options['wordUseNumbers'] ? self::$CN : self::$C;
|
||
|
|
}
|
||
|
|
|
||
|
|
for ($i = 0; $i < $wordLen; $i = $i + 2) {
|
||
|
|
$consonant = $consonants[array_rand($consonants)];
|
||
|
|
$vowel = $vowels[array_rand($vowels)];
|
||
|
|
$word .= $consonant . $vowel;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (strlen($word) > $wordLen) {
|
||
|
|
$word = substr($word, 0, $wordLen);
|
||
|
|
}
|
||
|
|
|
||
|
|
return $word;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
protected function _generateRandomId()
|
||
|
|
{
|
||
|
|
return md5(mt_rand(0, 1000) . microtime(true));
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
*
|
||
|
|
* @param string $id
|
||
|
|
* @param string $word
|
||
|
|
* @return boolean
|
||
|
|
*/
|
||
|
|
public static function isValid($id, $word)
|
||
|
|
{
|
||
|
|
$k = 'Zeed_Captcha_' . $id;
|
||
|
|
|
||
|
|
if (isset($_SESSION[$k]) && strtolower($word) == strtolower($_SESSION[$k])) {
|
||
|
|
unset($_SESSION[$k]);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
$_SESSION[$k] = null;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 获取指定ID正确的验证字串
|
||
|
|
*
|
||
|
|
* @param string $id
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
public static function getValidWord($id)
|
||
|
|
{
|
||
|
|
$k = 'Zeed_Captcha_' . $id;
|
||
|
|
return isset($_SESSION[$k]) ? $_SESSION[$k] : null;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @todo 改进存储, 增加失效时间等
|
||
|
|
* @param string $word
|
||
|
|
*/
|
||
|
|
public static function setValidWord($id, $word)
|
||
|
|
{
|
||
|
|
$k = 'Zeed_Captcha_' . $id;
|
||
|
|
$_SESSION[$k] = $word;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
*
|
||
|
|
* @param string|array $key
|
||
|
|
* @param mixed $val
|
||
|
|
* @return Zeed_Captcha_Image
|
||
|
|
*/
|
||
|
|
public function setParam($key, $val = null)
|
||
|
|
{
|
||
|
|
$this->_options[$key] = $val;
|
||
|
|
|
||
|
|
return $this;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function setWord($word = null)
|
||
|
|
{
|
||
|
|
if (empty($word)) {
|
||
|
|
$word = $this->_generateWord();
|
||
|
|
}
|
||
|
|
$this->_word = $word;
|
||
|
|
self::setValidWord($this->_id, $word);
|
||
|
|
|
||
|
|
return $this;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function getWord()
|
||
|
|
{
|
||
|
|
return $this->_word;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function setId($id)
|
||
|
|
{
|
||
|
|
$this->_id = $id;
|
||
|
|
|
||
|
|
return $this;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function getWordLen()
|
||
|
|
{
|
||
|
|
return $this->_options['wordLen'];
|
||
|
|
}
|
||
|
|
|
||
|
|
public function setWordlen($wordlen)
|
||
|
|
{
|
||
|
|
$this->_options['wordLen'] = $wordlen;
|
||
|
|
return $this;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function createImage($text = null)
|
||
|
|
{
|
||
|
|
if (! $this->_options['width'] || ! is_int($this->_options['width']))
|
||
|
|
trigger_error("\$config['width'] not int > 0", E_USER_ERROR);
|
||
|
|
|
||
|
|
if (! $this->_options['height'] || ! is_int($this->_options['height']))
|
||
|
|
trigger_error("\$config['height'] not int > 0", E_USER_ERROR);
|
||
|
|
|
||
|
|
if (! $this->_options['font'])
|
||
|
|
trigger_error("\$config['font'] not defined", E_USER_ERROR);
|
||
|
|
|
||
|
|
if (! $text) {
|
||
|
|
$text = substr(md5(microtime()), 0, 6);
|
||
|
|
}
|
||
|
|
|
||
|
|
$fontSize = (int) $this->_options['height'] * 1;
|
||
|
|
|
||
|
|
$height = $this->_options['height'];
|
||
|
|
$width = $this->_options['width'];
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 根据字体计算文本框图形大小
|
||
|
|
*/
|
||
|
|
$angle = rand(- 12, 12);
|
||
|
|
$temp_info = imageftbbox($fontSize, $angle, $this->_options['font'], $text);
|
||
|
|
$temp_width1 = abs($temp_info[0]) + abs($temp_info[4]);
|
||
|
|
$temp_height1 = abs($temp_info[1]) + abs($temp_info[5]);
|
||
|
|
$temp_width2 = abs($temp_info[6]) + abs($temp_info[2]);
|
||
|
|
$temp_height2 = abs($temp_info[3]) + abs($temp_info[7]);
|
||
|
|
$temp_width = 3 + ($temp_width1 > $temp_width2 ? $temp_width1 : $temp_width2);
|
||
|
|
$temp_height = 3 + ($temp_height1 > $temp_height2 ? $temp_height1 : $temp_height2);
|
||
|
|
$xcoord = $temp_info[0] > $temp_info[6] ? $temp_info[0] : $temp_info[6];
|
||
|
|
$ycoord = $temp_info[7] > $temp_info[5] ? - $temp_info[5] : - $temp_info[7];
|
||
|
|
|
||
|
|
|
||
|
|
// 创建一个文本图
|
||
|
|
$textImage = imagecreatetruecolor($temp_width, $temp_height);
|
||
|
|
imagefilledrectangle($textImage, 0, 0, $temp_width - 1, $temp_height - 1, $this->_options['backgroundColor']);
|
||
|
|
$fontColor = $this->_options['fontColor'][array_rand($this->_options['fontColor'], 1)];
|
||
|
|
|
||
|
|
$t_xcoord = $xcoord + 10;
|
||
|
|
$t_ycoord = $ycoord + 2;
|
||
|
|
$t_fontsize = ($fontSize * rand(90, 95) / 100);
|
||
|
|
$t_angle = $angle + rand(-10, 10);
|
||
|
|
for ($t_i = 0; $t_i < mb_strlen($text); $t_i++) {
|
||
|
|
$c = mb_substr($text, $t_i, 1);
|
||
|
|
imagefttext($textImage, $t_fontsize, $t_angle, $t_xcoord, $t_ycoord, $fontColor, $this->_options['font'], $c);
|
||
|
|
$t_xcoord = $t_xcoord + $t_fontsize + rand($t_fontsize*0.06, $t_fontsize*0.10);
|
||
|
|
$t_fontsize = ($fontSize * rand(90, 97) / 100);
|
||
|
|
$t_angle = $angle + rand(-10, 10);
|
||
|
|
}
|
||
|
|
|
||
|
|
//imagefttext($textImage, $fontSize * 0.95, $angle, $xcoord + 10, $ycoord + 2, $fontColor, $this->_options['font'], $text);
|
||
|
|
|
||
|
|
// 根据设定的大小缩小文本图
|
||
|
|
$image_final = imagecreatetruecolor($width, $height);
|
||
|
|
imagefilledrectangle($image_final, 0, 0, $width - 1, $height - 1, $this->_options['backgroundColor']);
|
||
|
|
imagecopyresized($image_final, $textImage, 0, 0, 0, 0, $width, $height, $temp_width, $temp_height);
|
||
|
|
// 变形, 增加干扰
|
||
|
|
self::_distortion($image_final, $this->_options['backgroundColor'], $width, $height);
|
||
|
|
self::_drawLine($image_final, $this->_options['lineColors']);
|
||
|
|
|
||
|
|
header("Cache-Control: max-age=1, s-maxage=1, no-cache, must-revalidate");
|
||
|
|
header("Content-type: image/jpeg");
|
||
|
|
imagejpeg($image_final, null, 80);
|
||
|
|
imagedestroy($textImage);
|
||
|
|
imagedestroy($image_final);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function _drawLine(&$image, $lineColors)
|
||
|
|
{
|
||
|
|
$width = imagesx($image);
|
||
|
|
$height = imagesy($image);
|
||
|
|
$image2 = imagecreatetruecolor($width, $height);
|
||
|
|
$white = imagecolorallocate($image2, 00, 00, 00);
|
||
|
|
imagefilledrectangle($image2, 0, 0, $width, $height, $white);
|
||
|
|
|
||
|
|
for ($j = 0; $j < 1; $j++) {
|
||
|
|
$lineColor = $lineColors[array_rand($lineColors)];
|
||
|
|
$centerX = rand(-10, $width);
|
||
|
|
$centerY = rand(-10, $height);
|
||
|
|
$eWidth = rand($width*0.9, $width*1.5);
|
||
|
|
$eHeight = rand($height*0.9, $height*1.5);
|
||
|
|
$weight = rand(2, 3);
|
||
|
|
for ($i = 0; $i < $weight; $i++) {
|
||
|
|
imageellipse($image2, $centerX, $centerY, $eWidth, $eHeight, $lineColor);
|
||
|
|
$eWidth -= 2;
|
||
|
|
$eHeight -= 2;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
$scale = 1;
|
||
|
|
$Xperiod = 8;
|
||
|
|
$Xamplitude = 2;
|
||
|
|
$Yperiod = 15;
|
||
|
|
$Yamplitude = 2;
|
||
|
|
// X-axis wave generation
|
||
|
|
$xp = $scale * $Xperiod * rand(1, 3);
|
||
|
|
$k = rand(0, 100);
|
||
|
|
for ($i = 0; $i < ($width); $i ++) {
|
||
|
|
imagecopy($image2, $image2, $i - 1, sin($k + $i / $xp) * ($scale * $Xamplitude), $i, 0, 1, $height);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Y-axis wave generation
|
||
|
|
$k = rand(0, 100);
|
||
|
|
$yp = $scale * $Yperiod * rand(1, 3);
|
||
|
|
for ($i = 0; $i < ($height); $i ++) {
|
||
|
|
imagecopy($image2, $image2, sin($k + $i / $yp) * ($scale * $Yamplitude), $i - 1, 0, $i, $width, 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
$x = rand(5, 15)*$scale;
|
||
|
|
$y = rand(5, 15)*$scale;
|
||
|
|
$w = $width - rand(5, 15)*$scale;
|
||
|
|
$h = $height - rand(5, 15)*$scale;
|
||
|
|
|
||
|
|
for($i = $x; $i < $w; $i++) {
|
||
|
|
for ($j = $y; $j < $h; $j++) {
|
||
|
|
if (imagecolorat($image2, $i, $j) == 0x000000) continue;
|
||
|
|
imagecopy($image, $image2, $i, $j, $i, $j, 1, 1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
imagedestroy($image2);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
*
|
||
|
|
*
|
||
|
|
* @param resource $image
|
||
|
|
* @param string $background
|
||
|
|
* @param integer $width
|
||
|
|
* @param integer $height
|
||
|
|
*/
|
||
|
|
private static function _distortion(&$image, $background, $width = 0, $height = 0)
|
||
|
|
{
|
||
|
|
if (! $width)
|
||
|
|
$width = imagesx($image);
|
||
|
|
if (! $height)
|
||
|
|
$height = imagesy($image);
|
||
|
|
|
||
|
|
$orig = imagecreatetruecolor($width, $height);
|
||
|
|
imagecopy($orig, $image, 0, 0, 0, 0, $width, $height);
|
||
|
|
imagefilledrectangle($image, 0, 0, $width, $height, $background);
|
||
|
|
|
||
|
|
$v_f1 = rand(200, 250) / 100; // od 2 do 3 za fju
|
||
|
|
$v_w1 = rand(0, $width * 100) / 100; // od 0 do width
|
||
|
|
|
||
|
|
|
||
|
|
$v_f2 = rand(40, 60) / 100; // od 0.1 do 1 za prigusenje
|
||
|
|
$v_w2 = rand(0, $width * 100) / 100; // od 0 do width
|
||
|
|
|
||
|
|
|
||
|
|
$v_f3 = rand(40, 60) / 100; // od 0.1 do 1 za prigusenje dolje
|
||
|
|
$v_w3 = rand(0, $width * 100) / 100; // od 0 do width
|
||
|
|
|
||
|
|
|
||
|
|
$y4_max = 0;
|
||
|
|
$y5_max = 0;
|
||
|
|
|
||
|
|
$an = array();
|
||
|
|
$as = array();
|
||
|
|
|
||
|
|
for ($x = 0; $x < $width; $x ++) {
|
||
|
|
if ($x % 9 == 0) {
|
||
|
|
//$x += rand(0, 1);
|
||
|
|
//continue;
|
||
|
|
}
|
||
|
|
$y1 = self::_sin($x, $v_f1, $v_w1, $width, $height);
|
||
|
|
$y2 = self::_sin($x, $v_f2, $v_w2, $width, $height);
|
||
|
|
$y3 = self::_sin($x, $v_f3, $v_w3, $width, $height);
|
||
|
|
|
||
|
|
$y4 = $y1 * $y2 / $height / 3;
|
||
|
|
$y5 = $y1 * $y3 / $height / 3;
|
||
|
|
|
||
|
|
$an[$x] = $y4;
|
||
|
|
$as[$x] = $y5;
|
||
|
|
|
||
|
|
if ($y4 > $y4_max)
|
||
|
|
$y4_max = $y4;
|
||
|
|
if ($y5 > $y5_max)
|
||
|
|
$y5_max = $y5;
|
||
|
|
}
|
||
|
|
|
||
|
|
for ($x = 0; $x < $width; $x ++) {
|
||
|
|
@$as[$x] = $height - $y5_max - 1 + $as[$x];
|
||
|
|
if ($x % 20 == 0) {
|
||
|
|
//$x += rand(0, 1);
|
||
|
|
//continue;
|
||
|
|
}
|
||
|
|
for ($y = 0; $y < $height; $y ++) {
|
||
|
|
if ($y % 15 == 0) {
|
||
|
|
//$y += rand(0, 1);
|
||
|
|
//continue;
|
||
|
|
}
|
||
|
|
@imagesetpixel($image, $x, self::_y($y, $an[$x], $as[$x], $height), imagecolorat($orig, $x, $y));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static function _sin($x, $f, $w, $width, $height)
|
||
|
|
{
|
||
|
|
return (int) ($height / 2) * (1 - sin($f * 2 * M_PI * ($x + $w) / $width));
|
||
|
|
}
|
||
|
|
|
||
|
|
private static function _y($y, $b1, $b2, $height)
|
||
|
|
{
|
||
|
|
return (int) $b1 + ($y / $height * ($b2 - $b1));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// End ^ LF ^ encoding
|