fix image text
[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             
106         if (!is_array($options)) {
107             // Compatibility mode ... in future versions, these two
108             // lines of code will be used: 
109             // $this->_error = PEAR::raiseError('You need to provide a set of CAPTCHA options!');
110             // return $this->_error;                  
111             $o = array();
112             $args = func_get_args();
113             if (isset($args[0])) {
114                 $o['width'] = $args[0];
115             }    
116             if (isset($args[1])) {
117                 $o['height'] = $args[1];
118             }    
119             if (isset($args[2]) && $args[2] != null) {
120                 $o['phrase'] = $args[2];
121             }
122             if (isset($args[3]) && is_array($args[3])) {
123                 $o['imageOptions'] = $args[3];
124             }
125             $options = $o;
126         }
127         if (is_array($options)) { 
128             if (isset($options['width']) && is_int($options['width'])) {
129                 $this->_width = $options['width'];
130             } else {
131                 $this->_width = 200; 
132             }
133             if (isset($options['height']) && is_int($options['height'])) {
134                 $this->_height = $options['height'];
135             } else {
136                 $this->_height = 80; 
137             }
138             if (!isset($options['phrase']) || empty($options['phrase'])) {
139                 $phraseoptions = (isset($options['phraseOptions']) && is_array($options['phraseOptions'])) ? $options['phraseOptions'] : array();
140                 $this->_createPhrase($phraseoptions);
141             } else {
142                 $this->_phrase = $options['phrase'];
143             }
144             if (!isset($options['output']) || empty($options['output'])) {
145                 $this->_output = 'resource';
146             } else {
147                 $this->_output = $options['output'];
148             } 
149             if (isset($options['imageOptions']) && is_array($options['imageOptions']) && count($options['imageOptions']) > 0) {
150                 $this->_imageOptions = array_merge($this->_imageOptions, $options['imageOptions']); 
151             }
152             
153             return true;
154         }
155         
156     }
157
158     /**
159      * Create random CAPTCHA phrase, Image edition (with size check)
160      *
161      * This method creates a random phrase, maximum 8 characters or width / 25, whatever is smaller
162      *
163      * @param array $options Optionally supply advanced options for the phrase creation
164      * 
165      * @access private
166      * @return void
167      */
168     function _createPhrase($options = array())
169     {
170         $len = intval(min(8, $this->_width / 25));
171         if (!is_array($options) || count($options) === 0) {
172             $this->_phrase = Text_Password::create($len);
173         } else {
174             if (count($options) === 1) {
175                 $this->_phrase = Text_Password::create($len, $options[0]);
176             } else {
177                 $this->_phrase = Text_Password::create($len, $options[0], $options[1]);
178             }
179         }
180         $this->_created = false;
181     }
182
183     /**
184      * Create CAPTCHA image
185      *
186      * This method creates a CAPTCHA image
187      *
188      * @access  private
189      * @return  void   PEAR_Error on error
190      */
191     function _createCAPTCHA()
192     {
193         if ($this->_error) {
194             return $this->_error;
195         }
196         if ($this->_created) {
197             return;
198         }
199         $options['canvas'] = array(
200             'width' => $this->_width,
201             'height' => $this->_height
202         ); 
203         $options['width'] = $this->_width - 20;
204         $options['height'] = $this->_height - 20; 
205         $options['cx'] = ceil(($this->_width) / 2 + 10);
206         $options['cy'] = ceil(($this->_height) / 2 + 10); 
207         $options['angle'] = rand(0, 30) - 15;
208         $options['font_size'] = $this->_imageOptions['font_size'];
209         $options['font_path'] = $this->_imageOptions['font_path'];
210         $options['font_file'] = $this->_imageOptions['font_file'];
211         $options['color'] = array($this->_imageOptions['text_color']);
212         $options['background_color'] = $this->_imageOptions['background_color'];
213         $options['max_lines'] = 1;
214         $options['mode'] = 'auto';
215          do {
216             $this->_imt = new Image_Text( 
217                 $this->_phrase,
218                 $options
219             );
220             
221              
222             if (PEAR::isError($e = $this->_imt->init())) {
223                 $this->_error = PEAR::staticRaiseError(
224                     sprintf('Error initializing Image_Text (%s)', $e->getMessage()));
225                 return $this->_error;
226             } else {
227                 $this->_created = true; 
228             }
229             $result = $this->_imt->measurize();
230         } while ($result === false && --$options['font_size'] > 0);
231         if ($result === false) {
232             $this->_error = PEAR::raiseError('The text provided does not fit in the image dimensions');
233             return $this->_error;
234         }
235         $this->_imt->render();
236         $this->_im =& $this->_imt->getImg(); 
237         
238         if (isset($this->_imageOptions['antialias']) && $this->_imageOptions['antialias'] === true && function_exists('imageantialias')) {
239             imageantialias($this->_im, true);
240         }
241         
242         $colors = $this->_imt->_convertString2RGB($this->_imageOptions['lines_color']);
243         $lines_color = imagecolorallocate($this->_im, $colors['r'], $colors['g'], $colors['b']);
244         //some obfuscation
245         for ($i = 0; $i < 3; $i++) {
246             $x1 = rand(0, $this->_width - 1);
247             $y1 = rand(0, round($this->_height / 10, 0));
248             $x2 = rand(0, round($this->_width / 10, 0));
249             $y2 = rand(0, $this->_height - 1);
250             imageline($this->_im, $x1, $y1, $x2, $y2, $lines_color);
251             $x1 = rand(0, $this->_width - 1);
252             $y1 = $this->_height - rand(1, round($this->_height / 10, 0));
253             $x2 = $this->_width - rand(1, round($this->_width / 10, 0));
254             $y2 = rand(0, $this->_height - 1);
255             imageline($this->_im, $x1, $y1, $x2, $y2, $lines_color);
256             $cx = rand(0, $this->_width - 50) + 25;
257             $cy = rand(0, $this->_height - 50) + 25;
258             $w = rand(1, 24);
259             imagearc($this->_im, $cx, $cy, $w, $w, 0, 360, $lines_color);
260         }
261     }
262
263     /**
264      * Return CAPTCHA as image resource
265      *
266      * This method returns the CAPTCHA depending on the output format
267      *
268      * @access  public
269      * @return  mixed        image resource or PEAR error
270      */
271     function getCAPTCHA()
272     {
273         $retval = $this->_createCAPTCHA();
274         if (PEAR::isError($retval)) {
275             return PEAR::staticRaiseError($retval->getMessage());
276         }
277         
278         if ($this->_output == 'gif' && !function_exists('imagegif')) {
279             $this->_output = 'png';
280         }
281
282         switch ($this->_output) {
283             case 'png':
284                 return $this->getCAPTCHAAsPNG();
285                 break;
286             case 'jpg': 
287             case 'jpeg':
288                 return $this->getCAPTCHAAsJPEG();
289                 break;
290             case 'gif':
291                 return $this->getCAPTCHAAsGIF();
292                 break;
293             case 'resource':
294             default:
295                 return $this->_im;
296         }
297     }
298
299     /**
300      * Return CAPTCHA as PNG
301      *
302      * This method returns the CAPTCHA as PNG
303      *
304      * @access  public
305      * @return  mixed        image contents or PEAR error
306      */
307     function getCAPTCHAAsPNG()
308     {
309         $retval = $this->_createCAPTCHA();
310         if (PEAR::isError($retval)) {
311             return PEAR::staticRaiseError($retval->getMessage());
312         }
313          if (is_object($this->_im)) {
314             ob_start();
315             imagepng($this->_im);
316             $data = ob_get_contents();
317             ob_end_clean();
318             return $data;
319         } else {
320             $this->_error = PEAR::staticRaiseError('Error creating CAPTCHA image (font missing?!)');
321             return $this->_error;
322         }
323     }
324
325     /**
326      * Return CAPTCHA as JPEG
327      *
328      * This method returns the CAPTCHA as JPEG
329      *
330      * @access  public
331      * @return  mixed        image contents or PEAR error
332      */
333     function getCAPTCHAAsJPEG()
334     {
335         $retval = $this->_createCAPTCHA();
336         if (PEAR::isError($retval)) {
337             return PEAR::raiseError($retval->getMessage());
338         }
339
340         if (is_object($this->_im)) {
341             ob_start();
342             imagejpeg($this->_im);
343             $data = ob_get_contents();
344             ob_end_clean();
345             return $data;
346         } else {
347             $this->_error = PEAR::raiseError('Error creating CAPTCHA image (font missing?!)');
348             return $this->_error;
349         }
350     }
351
352     /**
353      * Return CAPTCHA as GIF
354      *
355      * This method returns the CAPTCHA as GIF
356      *
357      * @access  public
358      * @return  mixed        image contents or PEAR error
359      */
360     function getCAPTCHAAsGIF()
361     {
362         $retval = $this->_createCAPTCHA();
363         if (PEAR::isError($retval)) {
364             return PEAR::raiseError($retval->getMessage());
365         }
366
367         if (is_object($this->_im)) {
368             ob_start();
369             imagegif($this->_im);
370             $data = ob_get_contents();
371             ob_end_clean();
372             return $data;
373         } else {
374             $this->_error = PEAR::raiseError('Error creating CAPTCHA image (font missing?!)');
375             return $this->_error;
376         }
377     }
378
379     /**
380      * __wakeup method (PHP 5 only)
381      *
382      * @return void
383      */
384     function __wakeup()
385     {
386         $this->_created = false;
387     } 
388 }