Completed
Push — master ( 41a3da...c9181e )
by MeWebStudio - Muharrem
28s queued 13s
created

Captcha   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 516
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
dl 0
loc 516
rs 8.8798
c 0
b 0
f 0
wmc 44
lcom 1
cbo 11

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 17 1
A configure() 0 8 3
C create() 0 63 9
A background() 0 4 1
B generate() 0 35 6
A text() 0 23 3
A font() 0 4 1
A fontSize() 0 4 1
A fontColor() 0 10 2
A angle() 0 4 1
A lines() 0 17 2
A check() 0 23 5
A get_cache_key() 0 3 1
A check_api() 0 12 4
A src() 0 4 1
A img() 0 13 3

How to fix   Complexity   

Complex Class

Complex classes like Captcha often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Captcha, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Mews\Captcha;
4
5
/**
6
 * Laravel 5 & 6 Captcha package
7
 *
8
 * @copyright Copyright (c) 2015 MeWebStudio
9
 * @version 2.x
10
 * @author Muharrem ERİN
11
 * @contact [email protected]
12
 * @web http://www.mewebstudio.com
13
 * @date 2015-04-03
14
 * @license http://www.opensource.org/licenses/mit-license.php The MIT License
15
 */
16
17
use Exception;
18
use Illuminate\Contracts\Config\Repository;
19
use Illuminate\Hashing\BcryptHasher as Hasher;
20
use Illuminate\Filesystem\Filesystem;
21
use Illuminate\Http\File;
22
use Illuminate\Support\Str;
23
use Intervention\Image\Gd\Font;
24
use Intervention\Image\Image;
25
use Intervention\Image\ImageManager;
26
use Illuminate\Session\Store as Session;
27
use Illuminate\Support\HtmlString;
28
use Illuminate\Support\Facades\Cache;
29
use Illuminate\Support\Facades\Crypt;
30
31
/**
32
 * Class Captcha
33
 * @package Mews\Captcha
34
 */
35
class Captcha
36
{
37
    /**
38
     * @var Filesystem
39
     */
40
    protected $files;
41
42
    /**
43
     * @var Repository
44
     */
45
    protected $config;
46
47
    /**
48
     * @var ImageManager
49
     */
50
    protected $imageManager;
51
52
    /**
53
     * @var Session
54
     */
55
    protected $session;
56
57
    /**
58
     * @var Hasher
59
     */
60
    protected $hasher;
61
62
    /**
63
     * @var Str
64
     */
65
    protected $str;
66
67
    /**
68
     * @var ImageManager->canvas
69
     */
70
    protected $canvas;
71
72
    /**
73
     * @var Image
74
     */
75
    protected $image;
76
77
    /**
78
     * @var array
79
     */
80
    protected $backgrounds = [];
81
82
    /**
83
     * @var array
84
     */
85
    protected $fonts = [];
86
87
    /**
88
     * @var array
89
     */
90
    protected $fontColors = [];
91
92
    /**
93
     * @var int
94
     */
95
    protected $length = 5;
96
97
    /**
98
     * @var int
99
     */
100
    protected $width = 120;
101
102
    /**
103
     * @var int
104
     */
105
    protected $height = 36;
106
107
    /**
108
     * @var int
109
     */
110
    protected $angle = 15;
111
112
    /**
113
     * @var int
114
     */
115
    protected $lines = 3;
116
117
    /**
118
     * @var string
119
     */
120
    protected $characters;
121
122
    /**
123
     * @var array
124
     */
125
    protected $text;
126
127
    /**
128
     * @var int
129
     */
130
    protected $contrast = 0;
131
132
    /**
133
     * @var int
134
     */
135
    protected $quality = 90;
136
137
    /**
138
     * @var int
139
     */
140
    protected $sharpen = 0;
141
142
    /**
143
     * @var int
144
     */
145
    protected $blur = 0;
146
147
    /**
148
     * @var bool
149
     */
150
    protected $bgImage = true;
151
152
    /**
153
     * @var string
154
     */
155
    protected $bgColor = '#ffffff';
156
157
    /**
158
     * @var bool
159
     */
160
    protected $invert = false;
161
162
    /**
163
     * @var bool
164
     */
165
    protected $sensitive = false;
166
167
    /**
168
     * @var bool
169
     */
170
    protected $math = false;
171
172
    /**
173
     * @var int
174
     */
175
    protected $textLeftPadding = 4;
176
177
    /**
178
     * @var string
179
     */
180
    protected $fontsDirectory;
181
182
    /**
183
     * @var int
184
     */
185
    protected $expire = 60;
186
187
    /**
188
     * @var bool
189
     */
190
    protected $encrypt = true;
191
    
192
    /**
193
     * Constructor
194
     *
195
     * @param Filesystem $files
196
     * @param Repository $config
197
     * @param ImageManager $imageManager
198
     * @param Session $session
199
     * @param Hasher $hasher
200
     * @param Str $str
201
     * @throws Exception
202
     * @internal param Validator $validator
203
     */
204
    public function __construct(
205
        Filesystem $files,
206
        Repository $config,
207
        ImageManager $imageManager,
208
        Session $session,
209
        Hasher $hasher,
210
        Str $str
211
    ) {
212
        $this->files = $files;
213
        $this->config = $config;
214
        $this->imageManager = $imageManager;
215
        $this->session = $session;
216
        $this->hasher = $hasher;
217
        $this->str = $str;
218
        $this->characters = config('captcha.characters', ['1', '2', '3', '4', '6', '7', '8', '9']);
219
        $this->fontsDirectory = config('captcha.fontsDirectory',  dirname(__DIR__) . '/assets/fonts');
220
    }
221
222
    /**
223
     * @param string $config
224
     * @return void
225
     */
226
    protected function configure($config)
227
    {
228
        if ($this->config->has('captcha.' . $config)) {
229
            foreach ($this->config->get('captcha.' . $config) as $key => $val) {
230
                $this->{$key} = $val;
231
            }
232
        }
233
    }
234
235
    /**
236
     * Create captcha image
237
     *
238
     * @param string $config
239
     * @param bool $api
240
     * @return array|mixed
241
     * @throws Exception
242
     */
243
    public function create(string $config = 'default', bool $api = false)
244
    {
245
        $this->backgrounds = $this->files->files(__DIR__ . '/../assets/backgrounds');
246
        $this->fonts = $this->files->files($this->fontsDirectory);
247
248
        if (version_compare(app()->version(), '5.5.0', '>=')) {
249
            $this->fonts = array_map(function ($file) {
250
                /* @var File $file */
251
                return $file->getPathName();
252
            }, $this->fonts);
253
        }
254
255
        $this->fonts = array_values($this->fonts); //reset fonts array index
256
257
        $this->configure($config);
258
259
        $generator = $this->generate();
260
        $this->text = $generator['value'];
261
262
        $this->canvas = $this->imageManager->canvas(
263
            $this->width,
264
            $this->height,
265
            $this->bgColor
266
        );
267
268
        if ($this->bgImage) {
269
            $this->image = $this->imageManager->make($this->background())->resize(
270
                $this->width,
271
                $this->height
272
            );
273
            $this->canvas->insert($this->image);
274
        } else {
275
            $this->image = $this->canvas;
276
        }
277
278
        if ($this->contrast != 0) {
279
            $this->image->contrast($this->contrast);
280
        }
281
282
        $this->text();
283
284
        $this->lines();
285
286
        if ($this->sharpen) {
287
            $this->image->sharpen($this->sharpen);
288
        }
289
        if ($this->invert) {
290
            $this->image->invert();
291
        }
292
        if ($this->blur) {
293
            $this->image->blur($this->blur);
294
        }
295
296
        if ($api) {
297
            Cache::put($this->get_cache_key($generator['key']), $generator['value'], $this->expire);
298
        }
299
300
        return $api ? [
301
            'sensitive' => $generator['sensitive'],
302
            'key' => $generator['key'],
303
            'img' => $this->image->encode('data-url')->encoded
304
        ] : $this->image->response('png', $this->quality);
305
    }
306
307
    /**
308
     * Image backgrounds
309
     *
310
     * @return string
311
     */
312
    protected function background(): string
313
    {
314
        return $this->backgrounds[rand(0, count($this->backgrounds) - 1)];
315
    }
316
317
    /**
318
     * Generate captcha text
319
     *
320
     * @return array
321
     * @throws Exception
322
     */
323
    protected function generate(): array
324
    {
325
        $characters = is_string($this->characters) ? str_split($this->characters) : $this->characters;
326
327
        $bag = [];
328
329
        if ($this->math) {
330
            $x = random_int(10, 30);
331
            $y = random_int(1, 9);
332
            $bag = "$x + $y = ";
333
            $key = $x + $y;
334
            $key .= '';
335
        } else {
336
            for ($i = 0; $i < $this->length; $i++) {
337
                $char = $characters[rand(0, count($characters) - 1)];
338
                $bag[] = $this->sensitive ? $char : $this->str->lower($char);
339
            }
340
            $key = implode('', $bag);
341
        }
342
343
        $hash = $this->hasher->make($key);
344
        if($this->encrypt) $hash = Crypt::encrypt($hash);
345
        
346
        $this->session->put('captcha', [
347
            'sensitive' => $this->sensitive,
348
            'key' => $hash,
349
            'encrypt' => $this->encrypt
350
        ]);
351
352
        return [
353
            'value' => $bag,
354
            'sensitive' => $this->sensitive,
355
            'key' => $hash
356
        ];
357
    }
358
359
    /**
360
     * Writing captcha text
361
     *
362
     * @return void
363
     */
364
    protected function text(): void
365
    {
366
        $marginTop = $this->image->height() / $this->length;
367
368
        $text = $this->text;
369
        if (is_string($text)) {
370
            $text = str_split($text);
371
        }
372
373
        foreach ($text as $key => $char) {
374
            $marginLeft = $this->textLeftPadding + ($key * ($this->image->width() - $this->textLeftPadding) / $this->length);
375
376
            $this->image->text($char, $marginLeft, $marginTop, function ($font) {
377
                /* @var Font $font */
378
                $font->file($this->font());
379
                $font->size($this->fontSize());
380
                $font->color($this->fontColor());
381
                $font->align('left');
382
                $font->valign('top');
383
                $font->angle($this->angle());
384
            });
385
        }
386
    }
387
388
    /**
389
     * Image fonts
390
     *
391
     * @return string
392
     */
393
    protected function font(): string
394
    {
395
        return $this->fonts[rand(0, count($this->fonts) - 1)];
396
    }
397
398
    /**
399
     * Random font size
400
     *
401
     * @return int
402
     */
403
    protected function fontSize(): int
404
    {
405
        return rand($this->image->height() - 10, $this->image->height());
406
    }
407
408
    /**
409
     * Random font color
410
     *
411
     * @return string
412
     */
413
    protected function fontColor(): string
414
    {
415
        if (!empty($this->fontColors)) {
416
            $color = $this->fontColors[rand(0, count($this->fontColors) - 1)];
417
        } else {
418
            $color = '#' . str_pad(dechex(mt_rand(0, 0xFFFFFF)), 6, '0', STR_PAD_LEFT);
419
        }
420
421
        return $color;
422
    }
423
424
    /**
425
     * Angle
426
     *
427
     * @return int
428
     */
429
    protected function angle(): int
430
    {
431
        return rand((-1 * $this->angle), $this->angle);
432
    }
433
434
    /**
435
     * Random image lines
436
     *
437
     * @return Image|ImageManager
438
     */
439
    protected function lines()
440
    {
441
        for ($i = 0; $i <= $this->lines; $i++) {
442
            $this->image->line(
443
                rand(0, $this->image->width()) + $i * rand(0, $this->image->height()),
444
                rand(0, $this->image->height()),
445
                rand(0, $this->image->width()),
446
                rand(0, $this->image->height()),
447
                function ($draw) {
448
                    /* @var Font $draw */
449
                    $draw->color($this->fontColor());
450
                }
451
            );
452
        }
453
454
        return $this->image;
455
    }
456
457
    /**
458
     * Captcha check
459
     *
460
     * @param string $value
461
     * @return bool
462
     */
463
    public function check(string $value): bool
464
    {
465
        if (!$this->session->has('captcha')) {
466
            return false;
467
        }
468
469
        $key = $this->session->get('captcha.key');
470
        $sensitive = $this->session->get('captcha.sensitive');
471
        $encrypt = $this->session->get('captcha.encrypt');
472
473
        if (!$sensitive) {
474
            $value = $this->str->lower($value);
475
        }
476
477
        if($encrypt) $key = Crypt::decrypt($key);
478
        $check = $this->hasher->check($value, $key);
479
        // if verify pass,remove session
480
        if ($check) {
481
            $this->session->remove('captcha');
482
        }
483
484
        return $check;
485
    }
486
    
487
    /**
488
     * Returns the md5 short version of the key for cache
489
     *
490
     * @param string $key
491
     * @return string
492
     */
493
    protected function get_cache_key($key) {
494
        return 'captcha_' . md5($key);
495
    }    
496
497
    /**
498
     * Captcha check
499
     *
500
     * @param string $value
501
     * @param string $key
502
     * @param string $config
503
     * @return bool
504
     */
505
    public function check_api($value, $key, $config = 'default'): bool
506
    {
507
        if (!Cache::pull($this->get_cache_key($key))) {
508
            return false;
509
        }
510
511
        $this->configure($config);
512
513
        if(!$this->sensitive) $value = $this->str->lower($value);
514
        if($this->encrypt) $key = Crypt::decrypt($key);
515
        return $this->hasher->check($value, $key);
516
    }
517
518
    /**
519
     * Generate captcha image source
520
     *
521
     * @param string $config
522
     * @return string
523
     */
524
    public function src(string $config = 'default'): string
525
    {
526
        return url('captcha/' . $config) . '?' . $this->str->random(8);
527
    }
528
529
    /**
530
     * Generate captcha image html tag
531
     *
532
     * @param string $config
533
     * @param array $attrs
534
     * $attrs -> HTML attributes supplied to the image tag where key is the attribute and the value is the attribute value
535
     * @return string
536
     */
537
    public function img(string $config = 'default', array $attrs = []): string
538
    {
539
        $attrs_str = '';
540
        foreach ($attrs as $attr => $value) {
541
            if ($attr == 'src') {
542
                //Neglect src attribute
543
                continue;
544
            }
545
546
            $attrs_str .= $attr . '="' . $value . '" ';
547
        }
548
        return new HtmlString('<img src="' . $this->src($config) . '" ' . trim($attrs_str) . '>');
549
    }
550
}
551