Passed
Push — master ( 2c843b...456045 )
by Arman
03:57 queued 14s
created

RecaptchaAdapter::verifyResponse()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 32
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 17
c 1
b 0
f 0
nc 5
nop 2
dl 0
loc 32
rs 8.8333
1
<?php
2
3
namespace Quantum\Libraries\Captcha\Adapters;
4
5
use Quantum\Libraries\Captcha\CaptchaInterface;
6
use Quantum\Libraries\Curl\HttpClient;
7
8
class RecaptchaAdapter implements CaptchaInterface
9
{
10
    /**
11
     * ReCAPTCHA URL verifying
12
     *
13
     * @var string
14
     */
15
    const VERIFY_URL = 'https://www.google.com/recaptcha/api/siteverify';
16
17
    const CLIENT_API = 'https://www.google.com/recaptcha/api.js';
18
19
    /**
20
     * Public key
21
     *
22
     * @var string
23
     */
24
    private $sitekey;
25
26
    /**
27
     * Private key
28
     *
29
     * @var string
30
     */
31
    private $secretkey;
32
33
    /**
34
     * @var HttpClient
35
     */
36
    private $http;
37
38
    /**
39
     * Remote IP address
40
     *
41
     * @var string
42
     */
43
    protected $remoteIp = null;
44
45
    /**
46
     * Captcha type. Default : image
47
     *
48
     * @var string
49
     * @see https://developers.google.com/recaptcha/docs/display#config
50
     */
51
    protected $type = null;
52
53
    /**
54
     * @var RecaptchaAdapter
55
     */
56
    private static $instance = null;
57
58
    /**
59
     * List of errors
60
     *
61
     * @var array
62
     */
63
    protected $errorCodes = array();
64
65
    /**
66
     * RecaptchaAdapter
67
     *
68
     * @param array $params
69
     * @return void
70
     */
71
    private function __construct(array $params)
72
    {
73
        $this->http = new HttpClient();
74
75
        $this->secretkey = $params['secret_key'];
76
        $this->sitekey = $params['site_key'];
77
        $this->type = $params['type'];
78
    }
79
80
    /**
81
     * Get Instance
82
     * @param array $params
83
     * @return RecaptchaAdapter
84
     */
85
    public static function getInstance(array $params): RecaptchaAdapter
86
    {
87
        if (self::$instance === null) {
88
            self::$instance = new self($params);
89
        }
90
91
        return self::$instance;
92
    }
93
94
    /**
95
     * Generate the JS code of the captcha
96
     *
97
     * @return string
98
     */
99
    public function renderJs(): string
100
    {
101
        return '<script src="'. self::CLIENT_API .'"></script>';
102
    }
103
104
    /**
105
     * Generate the HTML code block for the captcha
106
     *
107
     * @param string $formIdentifier
108
     * @param array $attributes
109
     * @return string
110
     */
111
    public function display(string $formIdentifier = '', array $attributes = []): string
112
    {
113
        $captchaElement = '';
114
        if (strtolower($this->type) == 'visible'){
115
            $captchaElement = $this->getVisibleElement();
116
        } elseif (strtolower($this->type) == 'invisible') {
117
            $captchaElement = $this->getInvisibleElement($formIdentifier);
118
        }
119
        return $captchaElement;
120
    }
121
122
    /**
123
     * Checks the code given by the captcha
124
     *
125
     * @param string $response Response code after submitting form (usually $_POST['g-recaptcha-response'])
126
     * @return bool
127
     */
128
    public function verifyResponse(string $response, $clientIp = null): 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('You must set your secret key');
132
133
        if (empty($response)) {
134
135
            $this->errorCodes = array('internal-empty-response');
136
137
            return false;
138
        }
139
140
        $query = array(
141
            'secret' => $this->secretkey,
142
            'response' => $response,
143
            'remoteip' => $this->remoteIp,
144
        );
145
146
        $url = self::VERIFY_URL . '?' . http_build_query($query);
147
148
        $this->http->createRequest($url)->setMethod('GET')->start();
149
        $response = (array)$this->http->getResponseBody();
150
151
        if (empty($response) || is_null($response) || !$response) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $response of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
152
            return false;
153
        }
154
155
        if (isset($response['error-codes'])) {
156
            $this->errorCodes = $response['error-codes'];
157
        }
158
159
        return $response['success'];
160
    }
161
162
    /**
163
     * Returns the errors encountered
164
     *
165
     * @return array Errors code and name
166
     */
167
    public function getErrorCodes(): array
168
    {
169
        $errors = array();
170
171
        if (count($this->errorCodes) > 0) {
172
            foreach ($this->errorCodes as $error) {
173
                switch ($error) {
174
                    case 'timeout-or-duplicate':
175
                        $errors[] = array(
176
                            'code' => $error,
177
                            'name' => 'Timeout or duplicate.',
178
                        );
179
                        break;
180
181
                    case 'missing-input-secret':
182
                        $errors[] = array(
183
                            'code' => $error,
184
                            'name' => 'The secret parameter is missing.',
185
                        );
186
                        break;
187
188
                    case 'invalid-input-secret':
189
                        $errors[] = array(
190
                            'code' => $error,
191
                            'name' => 'The secret parameter is invalid or malformed.',
192
                        );
193
                        break;
194
195
                    case 'missing-input-response':
196
                        $errors[] = array(
197
                            'code' => $error,
198
                            'name' => 'The response parameter is missing.',
199
                        );
200
                        break;
201
202
                    case 'invalid-input-response':
203
                        $errors[] = array(
204
                            'code' => $error,
205
                            'name' => 'The response parameter is invalid or malformed.',
206
                        );
207
                        break;
208
209
                    case 'bad-request':
210
                        $errors[] = array(
211
                            'code' => $error,
212
                            'name' => 'The request is invalid or malformed.',
213
                        );
214
                        break;
215
216
                    case 'internal-empty-response':
217
                        $errors[] = array(
218
                            'code' => $error,
219
                            'name' => 'The recaptcha response is required.',
220
                        );
221
                        break;
222
223
                    default:
224
                        $errors[] = array(
225
                            'code' => $error,
226
                            'name' => $error,
227
                        );
228
                }
229
            }
230
        }
231
232
        return $errors;
233
    }
234
235
    private function getInvisibleElement(string $formIdentifier): string
236
    {
237
        return '<script>
238
                 document.addEventListener("DOMContentLoaded", function() {
239
                    let button = document.getElementsByTagName("button");
240
                
241
                    button[0].setAttribute("data-sitekey", "' . $this->sitekey . '");
242
                    button[0].setAttribute("data-callback", "onSubmit");
243
                    button[0].setAttribute("data-action", "submit");
244
                    button[0].classList.add("g-recaptcha");
245
                 })
246
                
247
                function onSubmit (){
248
                    document.getElementById("'. $formIdentifier .'").submit();
249
                }
250
            </script>';
251
    }
252
253
    private function getVisibleElement(): string
254
    {
255
        $data = 'data-sitekey="' . $this->sitekey . '"';
256
257
        return '<div class="col s1 offset-s2 g-recaptcha" ' . $data . '></div>';
258
    }
259
}