GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Captcha::drawText()   C
last analyzed

Complexity

Conditions 17
Paths 33

Size

Total Lines 56

Duplication

Lines 9
Ratio 16.07 %

Importance

Changes 0
Metric Value
cc 17
nc 33
nop 0
dl 9
loc 56
rs 5.2166
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace phpMyFAQ;
4
5
/**
6
 * The phpMyFAQ Captcha class.
7
 *
8
 *
9
 *
10
 * This Source Code Form is subject to the terms of the Mozilla Public License,
11
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
12
 * obtain one at http://mozilla.org/MPL/2.0/.
13
 *
14
 * @package phpMyFAQ
15
 * @author Thomas Zeithaml <[email protected]>
16
 * @author Thorsten Rinne <[email protected]>
17
 * @author Matteo Scaramuccia <[email protected]>
18
 * @copyright 2006-2019 phpMyFAQ Team
19
 * @license http://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
20
 * @link https://www.phpmyfaq.de
21
 * @since 2006-02-04
22
 */
23
24
if (!defined('IS_VALID_PHPMYFAQ')) {
25
    exit();
26
}
27
28
/**
29
 * PMF_Captcha.
30
 *
31
 * @package phpMyFAQ
32
 * @author Thomas Zeithaml <[email protected]>
33
 * @author Thorsten Rinne <[email protected]>
34
 * @author Matteo Scaramuccia <[email protected]>
35
 * @copyright 2006-2019 phpMyFAQ Team
36
 * @license http://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
37
 * @link https://www.phpmyfaq.de
38
 * @since 2006-02-04
39
 */
40
class Captcha
41
{
42
    /**
43
     * @var Configuration
44
     */
45
    private $_config = null;
46
47
    /**
48
     * The phpMyFAQ session id.
49
     *
50
     * @var string
51
     */
52
    private $sids;
53
54
    /**
55
     * Array of fonts.
56
     *
57
     * @var array
58
     */
59
    private $fonts = [];
60
61
    /**
62
     * The captcha code.
63
     *
64
     * @var string
65
     */
66
    private $code = '';
67
68
    /**
69
     * Array of characters.
70
     *
71
     * @var array
72
     */
73
    private $letters = array(
74
        '1', '2', '3', '4', '5', '6', '7', '8', '9',
75
        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
76
        'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
77
        'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
78
    );
79
80
    /**
81
     * Length of the captcha code.
82
     *
83
     * @var int
84
     */
85
    public $caplength = 6;
86
87
    /**
88
     * Width of the image.
89
     *
90
     * @var int
91
     */
92
    private $width = 165;
93
94
    /**
95
     * Height of the image.
96
     *
97
     * @var int
98
     */
99
    private $height = 40;
100
101
    /**
102
     * JPEG quality in percents.
103
     *
104
     * @var int
105
     */
106
    private $quality = 60;
107
108
    /**
109
     * Random background color RGB components.
110
     *
111
     * @var array
112
     */
113
    private $_backgroundColor;
114
115
    /**
116
     * Generated image.
117
     *
118
     * @var resource
119
     */
120
    private $img;
121
122
    /**
123
     * The user agent string.
124
     *
125
     * @var string
126
     */
127
    private $userAgent;
128
129
    /**
130
     * Timestamp.
131
     *
132
     * @var int
133
     */
134
    private $timestamp;
135
136
    /**
137
     * Constructor.
138
     *
139
     * @param Configuration $config
140
     */
141
    public function __construct(Configuration $config)
142
    {
143
        $this->_config = $config;
144
        $this->userAgent = $_SERVER['HTTP_USER_AGENT'];
145
        $this->ip = $_SERVER['REMOTE_ADDR'];
146
        $this->fonts = $this->getFonts();
147
        $this->timestamp = $_SERVER['REQUEST_TIME'];
148
    }
149
150
    //
151
    // public functions
152
    //
153
154
    /**
155
     * Setter for session id.
156
     *
157
     * @param int $sid session id
158
     */
159
    public function setSessionId($sid)
160
    {
161
        $this->sids = $sid;
162
    }
163
164
    /**
165
     * Setter for the captcha code length.
166
     *
167
     * @param int $length Length of captch code
168
     */
169
    public function setCodeLength($length = 6)
170
    {
171
        $this->caplength = $length;
172
    }
173
174
    /**
175
     * Gives the HTML output code for the Captcha.
176
     *
177
     * @param string $action The action parameter
178
     *
179
     * @return string
180
     */
181
    public function printCaptcha($action)
182
    {
183
        $output = sprintf(
184
            '<img id="captchaImage" src="%s?%saction=%s&amp;gen=img&amp;ck=%s" height="%d" width="%d" alt="%s" title="%s">',
185
            $_SERVER['SCRIPT_NAME'],
186
            $this->sids,
187
            $action,
188
            $_SERVER['REQUEST_TIME'],
189
            $this->height,
190
            $this->width,
191
            'Chuck Norris has counted to infinity. Twice.',
192
            'click to refresh'
193
        );
194
195
        return $output;
196
    }
197
198
    /**
199
     * Draw the Captcha.
200
     */
201
    public function showCaptchaImg()
202
    {
203
        $this->createBackground();
204
        $this->drawlines();
205
        $this->generateCaptchaCode($this->caplength);
206
        $this->drawText();
207
        if (function_exists('imagejpeg')) {
208
            header('Content-Type: image/jpeg');
209
            imagejpeg($this->img, null, (int)$this->quality);
210
        } elseif (function_exists('imagegif')) {
211
            header('Content-Type: image/gif');
212
            imagegif($this->img);
213
        }
214
        imagedestroy($this->img);
215
    }
216
217
    /**
218
     * Gets the Captcha from the DB.
219
     *
220
     * @return string
221
     */
222
    public function getCaptchaCode()
223
    {
224
        $query = sprintf('SELECT id FROM %sfaqcaptcha', Db::getTablePrefix());
225
        $result = $this->_config->getDb()->query($query);
226
        while ($row = $this->_config->fetchArray($result)) {
0 ignored issues
show
Bug introduced by
The method fetchArray() does not seem to exist on object<phpMyFAQ\Configuration>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
227
            $this->code = $row['id'];
228
        }
229
230
        return $this->code;
231
    }
232
233
    /**
234
     * Validate the Captcha.
235
     *
236
     * @param string $captchaCode Captcha code
237
     *
238
     * @return bool
239
     */
240
    public function validateCaptchaCode($captchaCode)
241
    {
242
        // Sanity check
243
        if (0 == Strings::strlen($captchaCode)) {
244
            return false;
245
        }
246
247
        $captchaCode = Strings::strtoupper($captchaCode);
248
        // Help the user: treat "0" (ASCII 48) like "O" (ASCII 79)
249
        //                if "0" is not in the realm of captcha code letters
250
        if (!in_array('0', $this->letters)) {
251
            $captchaCode = str_replace('0', 'O', $captchaCode);
252
        }
253
        // Sanity check
254
        for ($i = 0; $i < Strings::strlen($captchaCode); ++$i) {
255
            if (!in_array($captchaCode[$i], $this->letters)) {
256
                return false;
257
            }
258
        }
259
        // Search for this Captcha in the db
260
        $query = sprintf("
261
            SELECT
262
                id
263
            FROM
264
                %sfaqcaptcha
265
            WHERE
266
                id = '%s'",
267
            Db::getTablePrefix(),
268
            $this->_config->getDb()->escape($captchaCode));
269
270
        if ($result = $this->_config->getDb()->query($query)) {
271
            $num = $this->_config->getDb()->numRows($result);
272
            if ($num > 0) {
273
                $this->code = $captchaCode;
274
                $this->removeCaptcha($captchaCode);
275
276
                return true;
277
            }
278
        }
279
280
        return false;
281
    }
282
283
    /**
284
     * This function checks the provided captcha code
285
     * if the captcha code spam protection has been activated from the general PMF configuration.
286
     *
287
     * @param string $code Captcha Code
288
     *
289
     * @return bool
290
     */
291
    public function checkCaptchaCode($code)
292
    {
293
        if ($this->_config->get('spam.enableCaptchaCode')) {
294
            return $this->validateCaptchaCode($code);
295
        } else {
296
            return true;
297
        }
298
    }
299
300
    //
301
    // private functions
302
    //
303
304
    /**
305
     * Draw random lines.
306
     *
307
     * @return resource
308
     */
309
    private function drawlines()
310
    {
311
        $color1 = rand(150, 185);
312
        $color2 = rand(185, 225);
313
        $nextline = 4;
314
        $w1 = 0;
315
        $w2 = 0;
316
317
        for ($x = 0; $x < $this->width; $x += (int)$nextline) {
318 View Code Duplication
            if ($x < $this->width) {
319
                imageline($this->img, $x + $w1, 0, $x + $w2, $this->height - 1, rand($color1, $color2));
320
            }
321 View Code Duplication
            if ($x < $this->height) {
322
                imageline($this->img, 0, $x - $w2, $this->width - 1, $x - $w1, rand($color1, $color2));
323
            }
324
            if (function_exists('imagettftext') && (count($this->fonts) > 0)) {
325
                $nextline += rand(-5, 7);
326
                if ($nextline < 1) {
327
                    $nextline = 2;
328
                }
329
            } else {
330
                $nextline += rand(1, 7);
331
            }
332
            $w1 += rand(-4, 4);
333
            $w2 += rand(-4, 4);
334
        }
335
336
        return $this->img;
337
    }
338
339
    /**
340
     * Draw the Text.
341
     *
342
     * @return resource
343
     */
344
    private function drawText()
345
    {
346
        $len = Strings::strlen($this->code);
347
        $w1 = 15;
348
        $w2 = $this->width/($len + 1);
349
350
        for ($p = 0; $p < $len; ++$p) {
351
            $letter = $this->code[$p];
352
            if (count($this->fonts) > 0) {
353
                $font = $this->fonts[rand(0, count($this->fonts) - 1)];
354
            }
355
            $size = rand(20, $this->height/2.2);
356
            $rotation = rand(-23, 23);
357
            $y = rand($size + 3, $this->height - 5);
358
            // $w1 += rand(- $this->width / 90, $this->width / 40 );
359
            $x = $w1 + $w2*$p;
360
            $c1 = []; // fore char color
361
            $c2 = []; // back char color
362
            do {
363
                $c1['r'] = mt_rand(30, 199);
364
            } while ($c1['r'] == $this->_backgroundColor['r']);
365
            do {
366
                $c1['g'] = mt_rand(30, 199);
367
            } while ($c1['g'] == $this->_backgroundColor['g']);
368
            do {
369
                $c1['b'] = mt_rand(30, 199);
370
            } while ($c1['b'] == $this->_backgroundColor['b']);
371
            $c1 = imagecolorallocate($this->img, $c1['r'], $c1['g'], $c1['b']);
372 View Code Duplication
            do {
373
                $c2['r'] = ($c1['r'] < 100 ? $c1['r']*2 : mt_rand(30, 199));
374
            } while (($c2['r'] == $this->_backgroundColor['r']) && ($c2['r'] == $c1['r']));
375 View Code Duplication
            do {
376
                $c2['g'] = ($c1['g'] < 100 ? $c1['g']*2 : mt_rand(30, 199));
377
            } while (($c2['g'] == $this->_backgroundColor['g']) && ($c2['g'] == $c1['g']));
378 View Code Duplication
            do {
379
                $c2['b'] = ($c1['b'] < 100 ? $c1['b']*2 : mt_rand(30, 199));
380
            } while (($c2['b'] == $this->_backgroundColor['b']) && ($c2['b'] == $c1['b']));
381
            $c2 = imagecolorallocate($this->img, $c2['r'], $c2['g'], $c2['b']);
382
            // Add the letter
383
            if (function_exists('imagettftext') && (count($this->fonts) > 0)) {
384
                imagettftext($this->img, $size, $rotation, $x + 2, $y, $c2, $font, $letter);
385
                imagettftext($this->img, $size, $rotation, $x + 1, $y + 1, $c2, $font, $letter);
386
                imagettftext($this->img, $size, $rotation, $x, $y - 2, $c1, $font, $letter);
387
            } else {
388
                $size = 5;
389
                $c3 = imagecolorallocate($this->img, 0, 0, 255);
390
                $x = 20;
391
                $y = 12;
392
                $s = 30;
393
                imagestring($this->img, $size, $x + 1 + ($s*$p), $y + 1, $letter, $c3);
394
                imagestring($this->img, $size, $x + ($s*$p), $y, $letter, $c1);
395
            }
396
        }
397
398
        return $this->img;
399
    }
400
401
    /**
402
     * Create the background.
403
     *
404
     * @return resource
405
     */
406
    private function createBackground()
407
    {
408
        $this->img = imagecreate($this->width, $this->height);
409
        $this->_backgroundColor['r'] = rand(220, 255);
410
        $this->_backgroundColor['g'] = rand(220, 255);
411
        $this->_backgroundColor['b'] = rand(220, 255);
412
413
        $colorallocate = imagecolorallocate(
414
            $this->img,
415
            $this->_backgroundColor['r'],
416
            $this->_backgroundColor['g'],
417
            $this->_backgroundColor['b']
418
        );
419
420
        imagefilledrectangle($this->img, 0, 0, $this->width, $this->height, $colorallocate);
421
422
        return $this->img;
423
    }
424
425
    /**
426
     * Generate a Captcha Code.
427
     *
428
     * @param int $caplength Length of captch code
429
     *
430
     * @return string
431
     */
432
    private function generateCaptchaCode($caplength)
433
    {
434
        // Start garbage collector for removing old (==unresolved) captcha codes
435
        // Note that we would like to avoid performing any garbaging of old records
436
        // because these data could be used as a database for collecting ip addresses,
437
        // eventually organizing them in subnetwork addresses, in order to use
438
        // them as an input for PMF IP banning.
439
        // This because we always perform these 3 checks on the public forms
440
        // in which captcha code feature is attached:
441
        //   1. Check against IP/Network address
442
        //   2. Check against banned words
443
        //   3. Check against the captcha code
444
        // so you could ban those "users" at the address level (1.).
445
        // If you want to look over your current data you could use this SQL query below:
446
        //   SELECT DISTINCT ip, useragent, COUNT(ip) AS times
447
        //   FROM faqcaptcha
448
        //   GROUP BY ip
449
        //   ORDER BY times DESC
450
        // to find out *bots and human attempts
451
        $this->garbageCollector();
452
453
        // Create the captcha code
454
        for ($i = 1; $i <= $caplength; ++$i) {
455
            $j = floor(rand(0, 34));
456
            $this->code .= $this->letters[$j];
457
        }
458
        if (!$this->saveCaptcha()) {
459
            return $this->generateCaptchaCode($caplength);
460
        }
461
462
        return $this->code;
463
    }
464
465
    /**
466
     * Save the Captcha.
467
     *
468
     * @return bool
469
     */
470
    private function saveCaptcha()
471
    {
472
        $select = sprintf("
473
           SELECT 
474
               id 
475
           FROM 
476
               %sfaqcaptcha 
477
           WHERE 
478
               id = '%s'",
479
            Db::getTablePrefix(),
480
            $this->code
481
        );
482
483
        $result = $this->_config->getDb()->query($select);
484
485
        if ($result) {
486
            $num = $this->_config->getDb()->numRows($result);
487
            if ($num > 0) {
488
                return false;
489
            } else {
490
                $insert = sprintf("
491
                    INSERT INTO 
492
                        %sfaqcaptcha 
493
                    (id, useragent, language, ip, captcha_time) 
494
                        VALUES 
495
                    ('%s', '%s', '%s', '%s', %d)",
496
                    Db::getTablePrefix(),
497
                    $this->code,
498
                    $this->userAgent,
499
                    $this->_config->getLanguage()->getLanguage(),
500
                    $this->ip,
501
                    $this->timestamp
502
                );
503
504
                $this->_config->getDb()->query($insert);
505
506
                return true;
507
            }
508
        }
509
510
        return false;
511
    }
512
513
    /**
514
     * Remove the Captcha.
515
     *
516
     * @param string $captchaCode Captch code
517
     */
518
    private function removeCaptcha($captchaCode = null)
519
    {
520
        if ($captchaCode == null) {
521
            $captchaCode = $this->code;
522
        }
523
        $query = sprintf("DELETE FROM %sfaqcaptcha WHERE id = '%s'", Db::getTablePrefix(), $captchaCode);
524
        $this->_config->getDb()->query($query);
525
    }
526
527
    /**
528
     * Delete old captcha records.
529
     *
530
     * During normal use the <b>faqcaptcha</b> table would be empty, on average:
531
     * each record is created when a captcha image is showed to the user
532
     * and deleted upon a successful matching, so, on average, a record
533
     * in this table is probably related to a spam attack.
534
     *
535
     * @param int $time The time (sec) to define a captcha code old and ready 
536
     *                  to be deleted (default: 1 week)
537
     */
538
    private function garbageCollector($time = 604800)
539
    {
540
        $delete = sprintf('
541
            DELETE FROM 
542
                %sfaqcaptcha 
543
            WHERE 
544
                captcha_time < %d',
545
            Db::getTablePrefix(),
546
            $_SERVER['REQUEST_TIME'] - $time
547
        );
548
549
        $this->_config->getDb()->query($delete);
550
551
        $delete = sprintf("
552
            DELETE FROM
553
                %sfaqcaptcha
554
            WHERE
555
                useragent = '%s' AND language = '%s' AND ip = '%s'",
556
            Db::getTablePrefix(),
557
            $this->userAgent,
558
            $this->_config->getLanguage()->getLanguage(),
559
            $this->ip
560
        );
561
562
        $this->_config->getDb()->query($delete);
563
    }
564
565
    /**
566
     * Get Fonts.
567
     *
568
     * @return array
569
     */
570
    private function getFonts()
571
    {
572
        return glob(PMF_SRC_DIR.'/fonts/*.ttf');
573
    }
574
}
575