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

HcaptchaAdapter::display()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 6
c 1
b 0
f 0
nc 3
nop 2
dl 0
loc 9
rs 10
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 HcaptchaAdapter implements CaptchaInterface
9
{
10
    const CLIENT_API = 'https://hcaptcha.com/1/api.js';
11
    const VERIFY_URL = 'https://hcaptcha.com/siteverify';
12
13
    /**
14
     * The hCaptcha secret key.
15
     *
16
     * @var string
17
     */
18
    protected $secretkey;
19
20
    /**
21
     * The hCaptcha sitekey key.
22
     *
23
     * @var string
24
     */
25
    protected $sitekey;
26
27
    /**
28
     * The hCaptcha type.
29
     *
30
     * @var string
31
     */
32
    protected $type;
33
34
    /**
35
     * List of errors
36
     *
37
     * @var array
38
     */
39
    protected $errorCodes = array();
40
41
    /**
42
     * The cached verified responses.
43
     *
44
     * @var array
45
     */
46
    protected $verifiedResponses = [];
47
48
    private static $instance = null;
49
50
    /**
51
     * @var HttpClient
52
     */
53
    protected $http;
54
55
    /**
56
     * HcaptchaAdapter.
57
     *
58
     * @param array $params
59
     */
60
    private function __construct(array $params)
61
    {
62
        $this->http = new HttpClient();
63
64
        $this->secretkey = $params['secret_key'];
65
        $this->sitekey = $params['site_key'];
66
        $this->type = $params['type'];
67
    }
68
69
    /**
70
     * Get Instance
71
     * @param array $params
72
     * @return HcaptchaAdapter
73
     */
74
    public static function getInstance(array $params): HcaptchaAdapter
75
    {
76
        if (self::$instance === null) {
77
            self::$instance = new self($params);
78
        }
79
80
        return self::$instance;
81
    }
82
83
    /**
84
     * Render HTML captcha.
85
     *
86
     * @param array $attributes
87
     *
88
     * @return string
89
     */
90
    public function display(string $formIdentifier = '', array $attributes = []): string
91
    {
92
        $captchaElement = '';
93
        if (strtolower($this->type) == 'visible'){
94
            $captchaElement = $this->getVisibleElement($attributes);
95
        } elseif (strtolower($this->type) == 'invisible') {
96
            $captchaElement = $this->getInvisibleElement($formIdentifier);
97
        }
98
        return $captchaElement;
99
    }
100
101
    /**
102
     * Render js source
103
     *
104
     * @return string
105
     */
106
    public function renderJs(): string
107
    {
108
        return '<script src="'. static::CLIENT_API .'" async defer></script>' . "\n";
109
    }
110
111
    /**
112
     * Verify hCaptcha response.
113
     *
114
     * @param string $response
115
     * @param string $clientIp
116
     *
117
     * @return bool
118
     */
119
    public function verifyResponse(string $response, string $clientIp = null): bool
120
    {
121
        if (empty($response)) {
122
            return false;
123
        }
124
125
        // Return true if response already verfied before.
126
        if (in_array($response, $this->verifiedResponses)) {
127
            return true;
128
        }
129
130
        $verifyResponse = $this->sendRequestVerify([
131
            'secret'   => $this->secretkey,
132
            'response' => $response,
133
        ]);
134
135
        if (isset($verifyResponse['success']) && $verifyResponse['success'] === true) {
136
            // A response can only be verified once from hCaptcha, so we need to
137
            // cache it to make it work in case we want to verify it multiple times.
138
            $this->verifiedResponses[] = $response;
139
            return true;
140
        } else {
141
            if (isset($verifyResponse['error-codes'])) {
142
                $this->errorCodes = $verifyResponse['error-codes'];
143
            }
144
145
            return false;
146
        }
147
    }
148
149
    /**
150
     * Returns the errors encountered
151
     *
152
     * @return array Errors
153
     */
154
    public function getErrorCodes(): array
155
    {
156
        if (!empty($this->errorCodes)){
157
            return $this->errorCodes;
158
        }
159
        return [];
160
    }
161
162
    /**
163
     * Send verify request.
164
     *
165
     * @param array $query
166
     *
167
     * @return array
168
     */
169
    protected function sendRequestVerify(array $query = []): array
170
    {
171
        $this->http->createRequest(static::VERIFY_URL)->setMethod('POST')->setData($query)->start();
172
173
        return (array)$this->http->getResponseBody();
174
    }
175
176
    /**
177
     * Prepare HTML attributes and assure that the correct classes and attributes for captcha are inserted.
178
     *
179
     * @param array $attributes
180
     *
181
     * @return array
182
     */
183
    protected function prepareAttributes(array $attributes): array
184
    {
185
        $attributes['data-sitekey'] = $this->sitekey;
186
        if (!isset($attributes['class'])) {
187
            $attributes['class'] = '';
188
        }
189
        $attributes['class'] = trim('h-captcha ' . $attributes['class']);
190
191
        return $attributes;
192
    }
193
194
    /**
195
     * Build HTML attributes.
196
     *
197
     * @param array $attributes
198
     *
199
     * @return string
200
     */
201
    protected function buildAttributes(array $attributes): string
202
    {
203
        $html = [];
204
205
        foreach ($attributes as $key => $value) {
206
            $html[] = $key . '="' . $value . '"';
207
        }
208
209
        return count($html) ? ' ' . implode(' ', $html) : '';
210
    }
211
212
    private function getInvisibleElement($formIdentifier): string
213
    {
214
        $functionName = 'onSubmit' . str_replace(['-', '=', '\'', '"', '<', '>', '`'], '', $formIdentifier);
215
216
        return '<script>
217
                     document.addEventListener("DOMContentLoaded", function() {
218
                        let button = document.getElementsByTagName("button");
219
                    
220
                        button[0].setAttribute("data-sitekey", "' . $this->sitekey . '");
221
                        button[0].setAttribute("data-callback", "'. $functionName .'");
222
                        button[0].classList.add("h-captcha");
223
                     })
224
                    
225
                    function '. $functionName .'(){
226
                        document.getElementById("'. $formIdentifier .'").submit();
227
                    }
228
                </script>';
229
    }
230
231
    private function getVisibleElement($attributes): string
232
    {
233
        $attributes = $this->prepareAttributes($attributes);
234
        return '<div' . $this->buildAttributes($attributes) . '></div>';
235
    }
236
}