Completed
Push — master ( 700ce5...16d566 )
by MeWebStudio - Muharrem
12s queued 10s
created

Captcha::generate()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 8
nop 0
dl 0
loc 34
rs 8.7537
c 0
b 0
f 0
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', __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('captcha_record_' . $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
        ]);
350
351
        return [
352
            'value' => $bag,
353
            'sensitive' => $this->sensitive,
354
            'key' => $hash
355
        ];
356
    }
357
358
    /**
359
     * Writing captcha text
360
     *
361
     * @return void
362
     */
363
    protected function text(): void
364
    {
365
        $marginTop = $this->image->height() / $this->length;
366
367
        $text = $this->text;
368
        if (is_string($text)) {
369
            $text = str_split($text);
370
        }
371
372
        foreach ($text as $key => $char) {
373
            $marginLeft = $this->textLeftPadding + ($key * ($this->image->width() - $this->textLeftPadding) / $this->length);
374
375
            $this->image->text($char, $marginLeft, $marginTop, function ($font) {
376
                /* @var Font $font */
377
                $font->file($this->font());
378
                $font->size($this->fontSize());
379
                $font->color($this->fontColor());
380
                $font->align('left');
381
                $font->valign('top');
382
                $font->angle($this->angle());
383
            });
384
        }
385
    }
386
387
    /**
388
     * Image fonts
389
     *
390
     * @return string
391
     */
392
    protected function font(): string
393
    {
394
        return $this->fonts[rand(0, count($this->fonts) - 1)];
395
    }
396
397
    /**
398
     * Random font size
399
     *
400
     * @return int
401
     */
402
    protected function fontSize(): int
403
    {
404
        return rand($this->image->height() - 10, $this->image->height());
405
    }
406
407
    /**
408
     * Random font color
409
     *
410
     * @return string
411
     */
412
    protected function fontColor(): string
413
    {
414
        if (!empty($this->fontColors)) {
415
            $color = $this->fontColors[rand(0, count($this->fontColors) - 1)];
416
        } else {
417
            $color = '#' . str_pad(dechex(mt_rand(0, 0xFFFFFF)), 6, '0', STR_PAD_LEFT);
418
        }
419
420
        return $color;
421
    }
422
423
    /**
424
     * Angle
425
     *
426
     * @return int
427
     */
428
    protected function angle(): int
429
    {
430
        return rand((-1 * $this->angle), $this->angle);
431
    }
432
433
    /**
434
     * Random image lines
435
     *
436
     * @return Image|ImageManager
437
     */
438
    protected function lines()
439
    {
440
        for ($i = 0; $i <= $this->lines; $i++) {
441
            $this->image->line(
442
                rand(0, $this->image->width()) + $i * rand(0, $this->image->height()),
443
                rand(0, $this->image->height()),
444
                rand(0, $this->image->width()),
445
                rand(0, $this->image->height()),
446
                function ($draw) {
447
                    /* @var Font $draw */
448
                    $draw->color($this->fontColor());
449
                }
450
            );
451
        }
452
453
        return $this->image;
454
    }
455
456
    /**
457
     * Captcha check
458
     *
459
     * @param string $value
460
     * @return bool
461
     */
462
    public function check(string $value): bool
463
    {
464
        if (!$this->session->has('captcha')) {
465
            return false;
466
        }
467
468
        $key = $this->session->get('captcha.key');
469
        $sensitive = $this->session->get('captcha.sensitive');
470
471
        if (!$sensitive) {
472
            $value = $this->str->lower($value);
473
        }
474
475
        if($this->encrypt) $key = Crypt::decrypt($key);
476
        $check = $this->hasher->check($value, $key);
477
        // if verify pass,remove session
478
        if ($check) {
479
            $this->session->remove('captcha');
480
        }
481
482
        return $check;
483
    }
484
485
    /**
486
     * Captcha check
487
     *
488
     * @param string $value
489
     * @param string $key
490
     * @return bool
491
     */
492
    public function check_api($value, $key): bool
493
    {
494
        if (!Cache::pull('captcha_record_' . $key)) {
495
            return false;
496
        }
497
498
        if($this->encrypt) $key = Crypt::decrypt($key);
499
        return $this->hasher->check($value, $key);
500
    }
501
502
    /**
503
     * Generate captcha image source
504
     *
505
     * @param string $config
506
     * @return string
507
     */
508
    public function src(string $config = 'default'): string
509
    {
510
        return url('captcha/' . $config) . '?' . $this->str->random(8);
511
    }
512
513
    /**
514
     * Generate captcha image html tag
515
     *
516
     * @param string $config
517
     * @param array $attrs
518
     * $attrs -> HTML attributes supplied to the image tag where key is the attribute and the value is the attribute value
519
     * @return string
520
     */
521
    public function img(string $config = 'default', array $attrs = []): string
522
    {
523
        $attrs_str = '';
524
        foreach ($attrs as $attr => $value) {
525
            if ($attr == 'src') {
526
                //Neglect src attribute
527
                continue;
528
            }
529
530
            $attrs_str .= $attr . '="' . $value . '" ';
531
        }
532
        return new HtmlString('<img src="' . $this->src($config) . '" ' . trim($attrs_str) . '>');
533
    }
534
}
535