1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10:
11:
12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36:
37: class CCaptchaAction extends CAction
38: {
39: 40: 41:
42: const REFRESH_GET_VAR='refresh';
43: 44: 45:
46: const SESSION_VAR_PREFIX='Yii.CCaptchaAction.';
47: 48: 49: 50:
51: public $testLimit = 3;
52: 53: 54:
55: public $width = 120;
56: 57: 58:
59: public $height = 50;
60: 61: 62:
63: public $padding = 2;
64: 65: 66: 67:
68: public $backColor = 0xFFFFFF;
69: 70: 71:
72: public $foreColor = 0x2040A0;
73: 74: 75:
76: public $transparent = false;
77: 78: 79:
80: public $minLength = 6;
81: 82: 83:
84: public $maxLength = 7;
85: 86: 87: 88: 89:
90: public $offset = -2;
91: 92: 93: 94:
95: public $fontFile;
96: 97: 98: 99: 100: 101: 102: 103:
104: public $fixedVerifyCode;
105: 106: 107: 108: 109: 110:
111: public $backend;
112:
113: 114: 115:
116: public function run()
117: {
118: if(isset($_GET[self::REFRESH_GET_VAR]))
119: {
120: $code=$this->getVerifyCode(true);
121: echo CJSON::encode(array(
122: 'hash1'=>$this->generateValidationHash($code),
123: 'hash2'=>$this->generateValidationHash(strtolower($code)),
124:
125:
126: 'url'=>$this->getController()->createUrl($this->getId(),array('v' => uniqid())),
127: ));
128: }
129: else
130: $this->renderImage($this->getVerifyCode());
131: Yii::app()->end();
132: }
133:
134: 135: 136: 137: 138: 139:
140: public function generateValidationHash($code)
141: {
142: for($h=0,$i=strlen($code)-1;$i>=0;--$i)
143: $h+=ord($code[$i]);
144: return $h;
145: }
146:
147: 148: 149: 150: 151:
152: public function getVerifyCode($regenerate=false)
153: {
154: if($this->fixedVerifyCode !== null)
155: return $this->fixedVerifyCode;
156:
157: $session = Yii::app()->session;
158: $session->open();
159: $name = $this->getSessionKey();
160: if($session[$name] === null || $regenerate)
161: {
162: $session[$name] = $this->generateVerifyCode();
163: $session[$name . 'count'] = 1;
164: }
165: return $session[$name];
166: }
167:
168: 169: 170: 171: 172: 173:
174: public function validate($input,$caseSensitive)
175: {
176: $code = $this->getVerifyCode();
177: $valid = $caseSensitive ? ($input === $code) : strcasecmp($input,$code)===0;
178: $session = Yii::app()->session;
179: $session->open();
180: $name = $this->getSessionKey() . 'count';
181: $session[$name] = $session[$name] + 1;
182: if($session[$name] > $this->testLimit && $this->testLimit > 0)
183: $this->getVerifyCode(true);
184: return $valid;
185: }
186:
187: 188: 189: 190:
191: protected function generateVerifyCode()
192: {
193: if($this->minLength > $this->maxLength)
194: $this->maxLength = $this->minLength;
195: if($this->minLength < 3)
196: $this->minLength = 3;
197: if($this->maxLength > 20)
198: $this->maxLength = 20;
199: $length = mt_rand($this->minLength,$this->maxLength);
200:
201: $letters = 'bcdfghjklmnpqrstvwxyz';
202: $vowels = 'aeiou';
203: $code = '';
204: for($i = 0; $i < $length; ++$i)
205: {
206: if($i % 2 && mt_rand(0,10) > 2 || !($i % 2) && mt_rand(0,10) > 9)
207: $code.=$vowels[mt_rand(0,4)];
208: else
209: $code.=$letters[mt_rand(0,20)];
210: }
211:
212: return $code;
213: }
214:
215: 216: 217: 218:
219: protected function getSessionKey()
220: {
221: return self::SESSION_VAR_PREFIX . Yii::app()->getId() . '.' . $this->getController()->getUniqueId() . '.' . $this->getId();
222: }
223:
224: 225: 226: 227:
228: protected function renderImage($code)
229: {
230: if($this->backend===null && CCaptcha::checkRequirements('imagick') || $this->backend==='imagick')
231: $this->renderImageImagick($code);
232: else if($this->backend===null && CCaptcha::checkRequirements('gd') || $this->backend==='gd')
233: $this->renderImageGD($code);
234: }
235:
236: 237: 238: 239: 240:
241: protected function renderImageGD($code)
242: {
243: $image = imagecreatetruecolor($this->width,$this->height);
244:
245: $backColor = imagecolorallocate($image,
246: (int)($this->backColor % 0x1000000 / 0x10000),
247: (int)($this->backColor % 0x10000 / 0x100),
248: $this->backColor % 0x100);
249: imagefilledrectangle($image,0,0,$this->width,$this->height,$backColor);
250: imagecolordeallocate($image,$backColor);
251:
252: if($this->transparent)
253: imagecolortransparent($image,$backColor);
254:
255: $foreColor = imagecolorallocate($image,
256: (int)($this->foreColor % 0x1000000 / 0x10000),
257: (int)($this->foreColor % 0x10000 / 0x100),
258: $this->foreColor % 0x100);
259:
260: if($this->fontFile === null)
261: $this->fontFile = dirname(__FILE__).DIRECTORY_SEPARATOR.'SpicyRice.ttf';
262:
263: $length = strlen($code);
264: $box = imagettfbbox(30,0,$this->fontFile,$code);
265: $w = $box[4] - $box[0] + $this->offset * ($length - 1);
266: $h = $box[1] - $box[5];
267: $scale = min(($this->width - $this->padding * 2) / $w,($this->height - $this->padding * 2) / $h);
268: $x = 10;
269: $y = round($this->height * 27 / 40);
270: for($i = 0; $i < $length; ++$i)
271: {
272: $fontSize = (int)(rand(26,32) * $scale * 0.8);
273: $angle = rand(-10,10);
274: $letter = $code[$i];
275: $box = imagettftext($image,$fontSize,$angle,$x,$y,$foreColor,$this->fontFile,$letter);
276: $x = $box[2] + $this->offset;
277: }
278:
279: imagecolordeallocate($image,$foreColor);
280:
281: header('Pragma: public');
282: header('Expires: 0');
283: header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
284: header('Content-Transfer-Encoding: binary');
285: header("Content-Type: image/png");
286: imagepng($image);
287: imagedestroy($image);
288: }
289:
290: 291: 292: 293: 294:
295: protected function renderImageImagick($code)
296: {
297: $backColor=$this->transparent ? new ImagickPixel('transparent') : new ImagickPixel(sprintf('#%06x',$this->backColor));
298: $foreColor=new ImagickPixel(sprintf('#%06x',$this->foreColor));
299:
300: $image=new Imagick();
301: $image->newImage($this->width,$this->height,$backColor);
302:
303: if($this->fontFile===null)
304: $this->fontFile=dirname(__FILE__).DIRECTORY_SEPARATOR.'SpicyRice.ttf';
305:
306: $draw=new ImagickDraw();
307: $draw->setFont($this->fontFile);
308: $draw->setFontSize(30);
309: $fontMetrics=$image->queryFontMetrics($draw,$code);
310:
311: $length=strlen($code);
312: $w=(int)($fontMetrics['textWidth'])-8+$this->offset*($length-1);
313: $h=(int)($fontMetrics['textHeight'])-8;
314: $scale=min(($this->width-$this->padding*2)/$w,($this->height-$this->padding*2)/$h);
315: $x=10;
316: $y=round($this->height*27/40);
317: for($i=0; $i<$length; ++$i)
318: {
319: $draw=new ImagickDraw();
320: $draw->setFont($this->fontFile);
321: $draw->setFontSize((int)(rand(26,32)*$scale*0.8));
322: $draw->setFillColor($foreColor);
323: $image->annotateImage($draw,$x,$y,rand(-10,10),$code[$i]);
324: $fontMetrics=$image->queryFontMetrics($draw,$code[$i]);
325: $x+=(int)($fontMetrics['textWidth'])+$this->offset;
326: }
327:
328: header('Pragma: public');
329: header('Expires: 0');
330: header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
331: header('Content-Transfer-Encoding: binary');
332: header("Content-Type: image/png");
333: $image->setImageFormat('png');
334: echo $image->getImageBlob();
335: }
336: }
337: