CaptchaTrait::addToForm()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
c 0
b 0
f 0
nc 3
nop 2
dl 0
loc 12
rs 10
1
<?php
2
3
/**
4
 * Quantum PHP Framework
5
 *
6
 * An open source software development framework for PHP
7
 *
8
 * @package Quantum
9
 * @author Arman Ag. <[email protected]>
10
 * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
11
 * @link http://quantum.softberg.org/
12
 * @since 3.0.0
13
 */
14
15
namespace Quantum\Libraries\Captcha\Traits;
16
17
use Quantum\Libraries\Captcha\Contracts\CaptchaInterface;
18
use Quantum\Libraries\Asset\Exceptions\AssetException;
19
use Quantum\Libraries\Lang\Exceptions\LangException;
20
use Quantum\Libraries\HttpClient\HttpClient;
21
use Quantum\Http\Exceptions\HttpException;
22
use Quantum\App\Exceptions\BaseException;
23
use Quantum\Libraries\Asset\AssetManager;
24
use Quantum\Libraries\Asset\Asset;
25
use ErrorException;
26
use Exception;
27
28
/**
29
 * Trait CaptchaTrait
30
 * @package Quantum\Libraries\Captcha
31
 */
32
trait CaptchaTrait
33
{
34
    /**
35
     * @var string
36
     */
37
    protected $siteKey;
38
39
    /**
40
     * @var string
41
     */
42
    protected $secretKey;
43
44
    /**
45
     * @var HttpClient
46
     */
47
    protected $http;
48
49
    /**
50
     * @var mixed|null
51
     */
52
    protected $type = null;
53
54
    /**
55
     * @var array
56
     */
57
    protected $errorCodes = [];
58
59
    /**
60
     * @var array
61
     */
62
    protected $elementAttributes = [];
63
64
    /**
65
     * @return string|null
66
     */
67
    public function getType(): ?string
68
    {
69
        return $this->type;
70
    }
71
72
    /**
73
     * @param string $type
74
     * @return CaptchaInterface
75
     * @throws Exception
76
     */
77
    public function setType(string $type): CaptchaInterface
78
    {
79
        if (!$this->isValidCaptchaType($type)) {
80
            throw new Exception('Provided captcha type is not valid');
81
        }
82
83
        $this->type = $type;
84
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type Quantum\Libraries\Captcha\Traits\CaptchaTrait which is incompatible with the type-hinted return Quantum\Libraries\Captch...tracts\CaptchaInterface.
Loading history...
85
    }
86
87
    /**
88
     * @return string
89
     */
90
    public function getName(): string
91
    {
92
        return $this->name;
93
    }
94
95
    /**
96
     * Generates an HTML code block for the captcha
97
     * @param string $formIdentifier
98
     * @param array $attributes
99
     * @return string
100
     * @throws AssetException
101
     * @throws Exception
102
     */
103
    public function addToForm(string $formIdentifier = '', array $attributes = []): string
104
    {
105
        if (!$this->type) {
106
            throw new Exception('Captcha type is not set');
107
        }
108
109
        AssetManager::getInstance()->registerAsset(new Asset(Asset::JS, static::CLIENT_API, 'captcha', -1, ['async', 'defer']));
0 ignored issues
show
Bug introduced by
The constant Quantum\Libraries\Captch...aptchaTrait::CLIENT_API was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
110
111
        if (strtolower($this->type) == self::CAPTCHA_INVISIBLE) {
0 ignored issues
show
Bug introduced by
The constant Quantum\Libraries\Captch...rait::CAPTCHA_INVISIBLE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
112
            return $this->getInvisibleElement($formIdentifier);
113
        } else {
114
            return $this->getVisibleElement($attributes);
115
        }
116
    }
117
118
    /**
119
     * Checks the code given by the captcha
120
     * @param string $code
121
     * @return bool
122
     * @throws LangException
123
     * @throws ErrorException
124
     * @throws BaseException
125
     * @throws HttpException
126
     * @throws Exception
127
     */
128
    public function verify(string $code): bool
129
    {
130
        if (is_null($this->secretKey)) {
0 ignored issues
show
introduced by
The condition is_null($this->secretKey) is always false.
Loading history...
131
            throw new Exception('The secret key is not set');
132
        }
133
134
        if ($code === '' || $code === '0') {
135
            $this->errorCodes = ['internal-empty-response'];
136
            return false;
137
        }
138
139
        $query = [
140
            'secret' => $this->secretKey,
141
            'response' => $code,
142
            'remoteip' => get_user_ip(),
143
        ];
144
145
        $response = $this->http
146
            ->createRequest(static::VERIFY_URL . '?' . http_build_query($query))
0 ignored issues
show
Bug introduced by
The constant Quantum\Libraries\Captch...aptchaTrait::VERIFY_URL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
147
            ->setMethod('GET')
148
            ->start()
149
            ->getResponseBody();
150
151
        if (empty($response)) {
152
            $this->errorCodes = ['internal-empty-response'];
153
            return false;
154
        }
155
156
        if (!is_object($response)) {
157
            $this->errorCodes = ['invalid-input-response'];
158
            return false;
159
        }
160
161
        if (isset($response->{'error-codes'}) && is_array($response->{'error-codes'})) {
162
            $this->errorCodes = $response->{'error-codes'};
163
        }
164
165
        if (isset($response->{'challenge_ts'}) && $this->detectReplayAttack($response->{'challenge_ts'})) {
166
            $this->errorCodes = ['replay-attack'];
167
            return false;
168
        }
169
170
        return isset($response->success) && $response->success;
171
    }
172
173
    /**
174
     * @return string|null
175
     */
176
    public function getErrorMessage(): ?string
177
    {
178
        if (!empty($this->errorCodes)) {
179
            return current($this->errorCodes);
180
        }
181
182
        return null;
183
    }
184
185
    /**
186
     * @param array $attributes
187
     * @return string
188
     */
189
    protected function getVisibleElement(array $attributes = []): string
190
    {
191
        $this->extractAttributes($attributes);
192
193
        return '<div class="' . implode(' ', $this->elementClasses) . '" data-sitekey="' . $this->siteKey . '" ' . implode(' ', $this->elementAttributes) . '></div>';
194
    }
195
196
    /**
197
     * @param string $formIdentifier
198
     * @return string
199
     * @throws Exception
200
     */
201
    protected function getInvisibleElement(string $formIdentifier): string
202
    {
203
        if ($formIdentifier === '' || $formIdentifier === '0') {
204
            throw new Exception('Form identifier is not provided to captcha element');
205
        }
206
207
        return '<script>
208
                 document.addEventListener("DOMContentLoaded", function() {
209
                     const form = document.getElementById("' . $formIdentifier . '");
210
                     const submitButton = form.querySelector("button[type=submit]");
211
                     submitButton.setAttribute("data-sitekey", "' . $this->siteKey . '");
212
                     submitButton.setAttribute("data-callback", "onSubmit");
213
                     submitButton.classList.add("' . reset($this->elementClasses) . '");
214
                 })
215
                function onSubmit (){
216
                    document.getElementById("' . $formIdentifier . '").submit();
217
                }
218
            </script>';
219
    }
220
221
    /**
222
     * @param $type
223
     * @return bool
224
     */
225
    protected function isValidCaptchaType($type): bool
226
    {
227
        $captchaTypes = [
228
            self::CAPTCHA_VISIBLE,
0 ignored issues
show
Bug introduced by
The constant Quantum\Libraries\Captch...aTrait::CAPTCHA_VISIBLE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
229
            self::CAPTCHA_INVISIBLE,
0 ignored issues
show
Bug introduced by
The constant Quantum\Libraries\Captch...rait::CAPTCHA_INVISIBLE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
230
        ];
231
232
        return in_array($type, $captchaTypes, true);
233
    }
234
235
    /**
236
     * @param array $attributes
237
     * @return void
238
     */
239
    protected function extractAttributes(array $attributes)
240
    {
241
        foreach ($attributes as $key => $value) {
242
            if ($key == 'class') {
243
                $this->elementClasses[] = $value;
0 ignored issues
show
Bug Best Practice introduced by
The property elementClasses does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
244
            } else {
245
                $this->elementAttributes[] = $key . '="' . $value . '"';
246
            }
247
        }
248
    }
249
250
    /**
251
     * @param string $challengeTs
252
     * @return bool
253
     */
254
    protected function detectReplayAttack(string $challengeTs): bool
255
    {
256
        return time() - strtotime($challengeTs) > self::MAX_TIME_DIFF;
0 ignored issues
show
Bug introduced by
The constant Quantum\Libraries\Captch...chaTrait::MAX_TIME_DIFF was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
257
    }
258
}
259