CaptchaTrait::getErrorMessage()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 7
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 2.9.7
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
    /**
36
     * @var string
37
     */
38
    protected $siteKey;
39
40
    /**
41
     * @var string
42
     */
43
    protected $secretKey;
44
45
    /**
46
     * @var HttpClient
47
     */
48
    protected $http;
49
50
    /**
51
     * @var mixed|null
52
     */
53
    protected $type = null;
54
55
    /**
56
     * @var array
57
     */
58
    protected $errorCodes = [];
59
60
    /**
61
     * @var array
62
     */
63
    protected $elementAttributes = [];
64
65
    /**
66
     * @return string|null
67
     */
68
    public function getType(): ?string
69
    {
70
        return $this->type;
71
    }
72
73
    /**
74
     * @param string $type
75
     * @return CaptchaInterface
76
     * @throws Exception
77
     */
78
    public function setType(string $type): CaptchaInterface
79
    {
80
        if (!$this->isValidCaptchaType($type)) {
81
            throw new Exception('Provided captcha type is not valid');
82
        }
83
84
        $this->type = $type;
85
        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...
86
    }
87
88
    /**
89
     * @return string
90
     */
91
    public function getName(): string
92
    {
93
        return $this->name;
94
    }
95
96
    /**
97
     * Generates an HTML code block for the captcha
98
     * @param string $formIdentifier
99
     * @param array $attributes
100
     * @return string
101
     * @throws AssetException
102
     * @throws Exception
103
     */
104
    public function addToForm(string $formIdentifier = '', array $attributes = []): string
105
    {
106
        if (!$this->type) {
107
            throw new Exception('Captcha type is not set');
108
        }
109
110
        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...
111
112
        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...
113
            return $this->getInvisibleElement($formIdentifier);
114
        } else {
115
            return $this->getVisibleElement($attributes);
116
        }
117
    }
118
119
    /**
120
     * Checks the code given by the captcha
121
     * @param string $code
122
     * @return bool
123
     * @throws LangException
124
     * @throws ErrorException
125
     * @throws BaseException
126
     * @throws HttpException
127
     * @throws Exception
128
     */
129
    public function verify(string $code): bool
130
    {
131
        if (is_null($this->secretKey))
0 ignored issues
show
introduced by
The condition is_null($this->secretKey) is always false.
Loading history...
132
            throw new Exception('The secret key is not set');
133
134
        if (empty($code)) {
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 (empty($formIdentifier)) {
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
        if (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
            return true;
258
        }
259
260
        return false;
261
    }
262
}