Passed
Pull Request — master (#135)
by
unknown
03:02
created

HcaptchaAdapter::getInstance()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 3
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 7
rs 10
1
<?php
2
3
namespace Quantum\Libraries\Captcha\Adapters;
4
5
use Quantum\Exceptions\CaptchaException;
6
use Quantum\Libraries\Captcha\CaptchaInterface;
7
use Quantum\Libraries\Curl\HttpClient;
8
9
class HcaptchaAdapter implements CaptchaInterface
10
{
11
    const CLIENT_API = 'https://hcaptcha.com/1/api.js';
12
    const VERIFY_URL = 'https://hcaptcha.com/siteverify';
13
14
    /**
15
     * The hCaptcha secret key.
16
     *
17
     * @var string
18
     */
19
    protected $secretkey;
20
21
    /**
22
     * The hCaptcha sitekey key.
23
     *
24
     * @var string
25
     */
26
    protected $sitekey;
27
28
    protected $type;
29
30
    /**
31
     * The cached verified responses.
32
     *
33
     * @var array
34
     */
35
    protected $verifiedResponses = [];
36
37
    private static $instance = null;
38
39
    protected $http;
40
41
    /**
42
     * HcaptchaAdapter.
43
     *
44
     * @param array $params
45
     */
46
    private function __construct(array $params)
47
    {
48
        $this->http = new HttpClient();
49
        
50
        $this->secretkey = $params['secret_key'];
51
        $this->sitekey = $params['site_key'];
52
        $this->type = $params['type'];
53
    }
54
55
    /**
56
     * Get Instance
57
     * @param array $params
58
     * @return HcaptchaAdapter
59
     */
60
    public static function getInstance(array $params): HcaptchaAdapter
61
    {
62
        if (self::$instance === null) {
63
            self::$instance = new self($params);
64
        }
65
66
        return self::$instance;
67
    }
68
69
    /**
70
     * Render HTML captcha.
71
     *
72
     * @param array $attributes
73
     *
74
     * @return string
75
     */
76
    public function display($formIdentifier = '', $attributes = [])
77
    {
78
        if (strtolower($this->type) == 'visible'){
79
            $attributes = $this->prepareAttributes($attributes);
80
            $captchaEleme = '<div' . $this->buildAttributes($attributes) . '></div>';
81
        } elseif (strtolower($this->type) == 'invisible') {
82
            $captchaEleme = '';
83
            if (!isset($attributes['data-callback'])) {
84
                $functionName = 'onSubmit' . str_replace(['-', '=', '\'', '"', '<', '>', '`'], '', $formIdentifier);
85
                $attributes['data-callback'] = $functionName;
86
                $captchaEleme = '<script>
87
                     document.addEventListener("DOMContentLoaded", function() {
88
                        let button = document.getElementsByTagName("button");
89
                    
90
                        button[0].setAttribute("data-sitekey", "' . $this->sitekey . '");
91
                        button[0].setAttribute("data-callback", "'. $functionName .'");
92
                        button[0].classList.add("h-captcha");
93
                     })
94
                    
95
                    function '. $functionName .'(){
96
                        document.getElementById("'. $formIdentifier .'").submit();
97
                    }
98
                </script>';
99
            }
100
        }else{
101
            throw CaptchaException::cantConnect();
0 ignored issues
show
Bug introduced by
The call to Quantum\Exceptions\CaptchaException::cantConnect() has too few arguments starting with name. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

101
            throw CaptchaException::/** @scrutinizer ignore-call */ cantConnect();

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
102
        }
103
        
104
        return $captchaEleme;
105
    }
106
107
    /**
108
     * Render js source
109
     *
110
     * @param null   $lang
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $lang is correct as it would always require null to be passed?
Loading history...
111
     * @param bool   $callback
112
     * @param string $onLoadClass
113
     *
114
     * @return string
115
     */
116
    public function renderJs($lang = null, $callback = false, $onLoadClass = 'onloadCallBack')
117
    {
118
        return '<script src="' . $this->getJsLink($lang, $callback, $onLoadClass) . '" async defer></script>' . "\n";
119
    }
120
121
    /**
122
     * Verify hCaptcha response.
123
     *
124
     * @param string $response
125
     * @param string $clientIp
126
     *
127
     * @return bool
128
     */
129
    public function verifyResponse($response, $clientIp = null)
130
    {
131
        if (empty($response)) {
132
            return false;
133
        }
134
135
        // Return true if response already verfied before.
136
        if (in_array($response, $this->verifiedResponses)) {
137
            return true;
138
        }
139
140
        $verifyResponse = $this->sendRequestVerify([
141
            'secret'   => $this->secretkey,
142
            'response' => $response,
143
        ]);
144
145
        if (isset($verifyResponse['success']) && $verifyResponse['success'] === true) {
146
            // A response can only be verified once from hCaptcha, so we need to
147
            // cache it to make it work in case we want to verify it multiple times.
148
            $this->verifiedResponses[] = $response;
149
            return true;
150
        } else {
151
            return false;
152
        }
153
    }
154
155
    /**
156
     * Get hCaptcha js link.
157
     *
158
     * @param string  $lang
159
     * @param boolean $callback
160
     * @param string  $onLoadClass
161
     *
162
     * @return string
163
     */
164
    public function getJsLink($lang = null, $callback = false, $onLoadClass = 'onloadCallBack')
165
    {
166
        $client_api = static::CLIENT_API;
167
        $params = [];
168
169
        $callback ? $this->setCallBackParams($params, $onLoadClass) : false;
170
        $lang ? $params['hl'] = $lang : null;
171
172
        return $client_api . '?' . http_build_query($params);
173
    }
174
175
    /**
176
     * @param $params
177
     * @param $onLoadClass
178
     */
179
    protected function setCallBackParams(&$params, $onLoadClass)
180
    {
181
        $params['render'] = 'explicit';
182
        $params['onload'] = $onLoadClass;
183
    }
184
185
    /**
186
     * Send verify request.
187
     *
188
     * @param array $query
189
     *
190
     * @return array
191
     */
192
    protected function sendRequestVerify(array $query = [])
193
    {
194
        $this->http->createRequest(static::VERIFY_URL)->setMethod('POST')->setData($query)->start();
195
196
        return (array)$this->http->getResponseBody();
197
    }
198
199
    /**
200
     * Prepare HTML attributes and assure that the correct classes and attributes for captcha are inserted.
201
     *
202
     * @param array $attributes
203
     *
204
     * @return array
205
     */
206
    protected function prepareAttributes(array $attributes)
207
    {
208
        $attributes['data-sitekey'] = $this->sitekey;
209
        if (!isset($attributes['class'])) {
210
            $attributes['class'] = '';
211
        }
212
        $attributes['class'] = trim('h-captcha ' . $attributes['class']);
213
214
        return $attributes;
215
    }
216
217
    /**
218
     * Build HTML attributes.
219
     *
220
     * @param array $attributes
221
     *
222
     * @return string
223
     */
224
    protected function buildAttributes(array $attributes)
225
    {
226
        $html = [];
227
228
        foreach ($attributes as $key => $value) {
229
            $html[] = $key . '="' . $value . '"';
230
        }
231
232
        return count($html) ? ' ' . implode(' ', $html) : '';
233
    }
234
}