3b3c94eb42bf43aab0040cff91a6e5bc5bba4e77
[pear] / Text / CAPTCHA / Driver / Image.php
1 <?php
2 /**
3  * Require Image_Text class for generating the text.
4  */
5 require_once 'Text/CAPTCHA.php';
6 require_once 'Image/Text.php';
7
8 /**
9  * Text_CAPTCHA_Driver_Image - Text_CAPTCHA driver graphical CAPTCHAs
10  *
11  * Class to create a graphical Turing test 
12  * 
13  * @author  Christian Wenz <wenz@php.net>
14  * @license BSD License
15  * @todo refine the obfuscation algorithm :-)
16  * @todo consider removing Image_Text dependency
17  */
18
19 class Text_CAPTCHA_Driver_Image extends Text_CAPTCHA
20 {
21
22     /**
23      * Image object
24      *
25      * @access private
26      * @var resource
27      */
28     var $_im;
29
30     /**
31      * Image_Text object
32      *
33      * @access private
34      * @var resource
35      */
36     var $_imt;
37
38     /**
39      * Width of CAPTCHA
40      *
41      * @access private
42      * @var int
43      */
44     var $_width;
45
46     /**
47      * Height of CAPTCHA
48      *
49      * @access private
50      * @var int
51      */
52     var $_height;
53
54     /**
55      * CAPTCHA output format
56      *
57      * @access private
58      * @var string
59      */
60     var $_output;
61
62     /**
63      * Further options (here: for Image_Text)
64      *
65      * @access private
66      * @var array
67      */
68     var $_imageOptions = array(
69         'font_size'        => 24,
70         'font_path'        => './',
71         'font_file'        => 'COUR.TTF',
72         'text_color'       => '#000000',
73         'lines_color'      => '#CACACA',
74         'background_color' => '#555555',
75         'antialias'        => false);
76         
77     /**
78      * Whether the immage resource has been created
79      *
80      * @access private
81      * @var boolean
82      */
83     var $_created = false;
84
85     /**
86      * Last error
87      *
88      * @access protected
89      * @var PEAR_Error
90      */
91     var $_error = null;
92
93     /**
94      * init function
95      *
96      * Initializes the new Text_CAPTCHA_Driver_Image object and creates a GD image
97      *
98      * @param array $options CAPTCHA options
99      *
100      * @access public
101      * @return mixed true upon success, PEAR error otherwise
102      */
103     function init($options = array())
104     {
105         if (!is_array($options)) {
106             // Compatibility mode ... in future versions, these two
107             // lines of code will be used: 
108             // $this->_error = PEAR::raiseError('You need to provide a set of CAPTCHA options!');
109             // return $this->_error;                  
110             $o = array();
111             $args = func_get_args();
112             if (isset($args[0])) {
113                 $o['width'] = $args[0];
114             }    
115             if (isset($args[1])) {
116                 $o['height'] = $args[1];
117             }    
118             if (isset($args[2]) && $args[2] != null) {
119                 $o['phrase'] = $args[2];
120             }
121             if (isset($args[3]) && is_array($args[3])) {
122                 $o['imageOptions'] = $args[3];
123             }
124             $options = $o;
125         }
126         if (is_array($options)) { 
127             if (isset($options['width']) && is_int($options['width'])) {
128                 $this->_width = $options['width'];
129             } else {
130                 $this->_width = 200; 
131             }
132             if (isset($options['height']) && is_int($options['height'])) {
133                 $this->_height = $options['height'];
134             } else {
135                 $this->_height = 80; 
136             }
137             if (!isset($options['phrase']) || empty($options['phrase'])) {
138                 $phraseoptions = (isset($options['phraseOptions']) && is_array($options['phraseOptions'])) ? $options['phraseOptions'] : array();
139                 $this->_createPhrase($phraseoptions);
140             } else {
141                 $this->_phrase = $options['phrase'];
142             }
143             if (!isset($options['output']) || empty($options['output'])) {
144                 $this->_output = 'resource';
145             } else {
146                 $this->_output = $options['output'];
147             } 
148             if (isset($options['imageOptions']) && is_array($options['imageOptions']) && count($options['imageOptions']) > 0) {
149                 $this->_imageOptions = array_merge($this->_imageOptions, $options['imageOptions']); 
150             }
151             return true;
152         }
153     }
154
155     /**
156      * Create random CAPTCHA phrase, Image edition (with size check)
157      *
158      * This method creates a random phrase, maximum 8 characters or width / 25, whatever is smaller
159      *
160      * @param array $options Optionally supply advanced options for the phrase creation
161      * 
162      * @access private
163      * @return void
164      */
165     function _createPhrase($options = array())
166     {
167         $len = intval(min(8, $this->_width / 25));
168         if (!is_array($options) || count($options) === 0) {
169             $this->_phrase = Text_Password::create($len);
170         } else {
171             if (count($options) === 1) {
172                 $this->_phrase = Text_Password::create($len, $options[0]);
173             } else {
174                 $this->_phrase = Text_Password::create($len, $options[0], $options[1]);
175             }
176         }
177         $this->_created = false;
178     }
179
180     /**
181      * Create CAPTCHA image
182      *
183      * This method creates a CAPTCHA image
184      *
185      * @access  private
186      * @return  void   PEAR_Error on error
187      */
188     function _createCAPTCHA()
189     {
190         if ($this->_error) {
191             return $this->_error;
192         }
193         if ($this->_created) {
194             return;
195         }
196         $options['canvas'] = array(
197             'width' => $this->_width,
198             'height' => $this->_height
199         ); 
200         $options['width'] = $this->_width - 20;
201         $options['height'] = $this->_height - 20; 
202         $options['cx'] = ceil(($this->_width) / 2 + 10);
203         $options['cy'] = ceil(($this->_height) / 2 + 10); 
204         $options['angle'] = rand(0, 30) - 15;
205         $options['font_size'] = $this->_imageOptions['font_size'];
206         $options['font_path'] = $this->_imageOptions['font_path'];
207         $options['font_file'] = $this->_imageOptions['font_file'];
208         $options['color'] = array($this->_imageOptions['text_color']);
209         $options['background_color'] = $this->_imageOptions['background_color'];
210         $options['max_lines'] = 1;
211         $options['mode'] = 'auto';
212         do {
213             $this->_imt = new Image_Text( 
214                 $this->_phrase,
215                 $options
216             );
217             if (PEAR::isError($e = $this->_imt->init())) {
218                 $this->_error = PEAR::staticRaiseError(
219                     sprintf('Error initializing Image_Text (%s)', $e->getMessage()));
220                 return $this->_error;
221             } else {
222                 $this->_created = true; 
223             }
224             $result = $this->_imt->measurize();
225         } while ($result === false && --$options['font_size'] > 0);
226         if ($result === false) {
227             $this->_error = PEAR::raiseError('The text provided does not fit in the image dimensions');
228             return $this->_error;
229         }
230         $this->_imt->render();
231         $this->_im =& $this->_imt->getImg(); 
232         
233         if (isset($this->_imageOptions['antialias']) && $this->_imageOptions['antialias'] === true && function_exists('imageantialias')) {
234             imageantialias($this->_im, true);
235         }
236         
237         $colors = $this->_imt->_convertString2RGB($this->_imageOptions['lines_color']);
238         $lines_color = imagecolorallocate($this->_im, $colors['r'], $colors['g'], $colors['b']);
239         //some obfuscation
240         for ($i = 0; $i < 3; $i++) {
241             $x1 = rand(0, $this->_width - 1);
242             $y1 = rand(0, round($this->_height / 10, 0));
243             $x2 = rand(0, round($this->_width / 10, 0));
244             $y2 = rand(0, $this->_height - 1);
245             imageline($this->_im, $x1, $y1, $x2, $y2, $lines_color);
246             $x1 = rand(0, $this->_width - 1);
247             $y1 = $this->_height - rand(1, round($this->_height / 10, 0));
248             $x2 = $this->_width - rand(1, round($this->_width / 10, 0));
249             $y2 = rand(0, $this->_height - 1);
250             imageline($this->_im, $x1, $y1, $x2, $y2, $lines_color);
251             $cx = rand(0, $this->_width - 50) + 25;
252             $cy = rand(0, $this->_height - 50) + 25;
253             $w = rand(1, 24);
254             imagearc($this->_im, $cx, $cy, $w, $w, 0, 360, $lines_color);
255         }
256     }
257
258     /**
259      * Return CAPTCHA as image resource
260      *
261      * This method returns the CAPTCHA depending on the output format
262      *
263      * @access  public
264      * @return  mixed        image resource or PEAR error
265      */
266     function getCAPTCHA()
267     {
268         $retval = $this->_createCAPTCHA();
269         if (PEAR::isError($retval)) {
270             return PEAR::staticRaiseError($retval->getMessage());
271         }
272         
273         if ($this->_output == 'gif' && !function_exists('imagegif')) {
274             $this->_output = 'png';
275         }
276
277         switch ($this->_output) {
278             case 'png':
279                 return $this->getCAPTCHAAsPNG();
280                 break;
281             case 'jpg': 
282             case 'jpeg':
283                 return $this->getCAPTCHAAsJPEG();
284                 break;
285             case 'gif':
286                 return $this->getCAPTCHAAsGIF();
287                 break;
288             case 'resource':
289             default:
290                 return $this->_im;
291         }
292     }
293
294     /**
295      * Return CAPTCHA as PNG
296      *
297      * This method returns the CAPTCHA as PNG
298      *
299      * @access  public
300      * @return  mixed        image contents or PEAR error
301      */
302     function getCAPTCHAAsPNG()
303     {
304         $retval = $this->_createCAPTCHA();
305         if (PEAR::isError($retval)) {
306             return PEAR::staticRaiseError($retval->getMessage());
307         }
308
309         if (is_resource($this->_im)) {
310             ob_start();
311             imagepng($this->_im);
312             $data = ob_get_contents();
313             ob_end_clean();
314             return $data;
315         } else {
316             $this->_error = PEAR::raiseError('Error creating CAPTCHA image (font missing?!)');
317             return $this->_error;
318         }
319     }
320
321     /**
322      * Return CAPTCHA as JPEG
323      *
324      * This method returns the CAPTCHA as JPEG
325      *
326      * @access  public
327      * @return  mixed        image contents or PEAR error
328      */
329     function getCAPTCHAAsJPEG()
330     {
331         $retval = $this->_createCAPTCHA();
332         if (PEAR::isError($retval)) {
333             return PEAR::raiseError($retval->getMessage());
334         }
335
336         if (is_resource($this->_im)) {
337             ob_start();
338             imagejpeg($this->_im);
339             $data = ob_get_contents();
340             ob_end_clean();
341             return $data;
342         } else {
343             $this->_error = PEAR::raiseError('Error creating CAPTCHA image (font missing?!)');
344             return $this->_error;
345         }
346     }
347
348     /**
349      * Return CAPTCHA as GIF
350      *
351      * This method returns the CAPTCHA as GIF
352      *
353      * @access  public
354      * @return  mixed        image contents or PEAR error
355      */
356     function getCAPTCHAAsGIF()
357     {
358         $retval = $this->_createCAPTCHA();
359         if (PEAR::isError($retval)) {
360             return PEAR::raiseError($retval->getMessage());
361         }
362
363         if (is_resource($this->_im)) {
364             ob_start();
365             imagegif($this->_im);
366             $data = ob_get_contents();
367             ob_end_clean();
368             return $data;
369         } else {
370             $this->_error = PEAR::raiseError('Error creating CAPTCHA image (font missing?!)');
371             return $this->_error;
372         }
373     }
374
375     /**
376      * __wakeup method (PHP 5 only)
377      *
378      * @return void
379      */
380     function __wakeup()
381     {
382         $this->_created = false;
383     } 
384 }